• Najnowsze pytania
  • Bez odpowiedzi
  • Zadaj pytanie
  • Kategorie
  • Tagi
  • Zdobyte punkty
  • Ekipa ninja
  • IRC
  • FAQ
  • Regulamin
  • Książki warte uwagi

Szyfrowanie danych z pliku i zapis do drugiego pliku.

Object Storage Arubacloud
0 głosów
562 wizyt
pytanie zadane 10 maja 2022 w C i C++ przez xTMx3 Obywatel (1,560 p.)

Witam,

ostatnio dostałem takie takie zadanie do wykonania: 

"Proszę napisać program dokonujący szyfrowania/deszyfrowania zawartości pliku jednoznakowym kluczem (Program powinien być uruchamiany z poziomu procesora poleceń ).

Wymaganie 1: wykorzystanie argumentów funkcji main. Program powinien być wywołany z czterema parametrami: rodzaj operacji: s – szyfrowanie, d –deszyfrowanie, drugi - nazwa pliku do szyfrowania/deszyfrowania, trzeci - nazwa pliku zaszyfrowanego/odszyfrowanego, czwarty - jednoznakowy klucz.

Wywołanie powinno wyglądać w następujący sposób:

NazwaProgramu s plik1 plik2 X lub NazwaProgramu d plik1 plik2 X 

Każde inne wywołanie powinno zostać uznane za nieprawidłowe i spowodować wyprowadzenie informacji o prawidłowym sposobie wywołania programu.

 

Program ma działać w pewien specyficzny sposób: 

Wymaganie 2 i 3 (dynamiczna alokacja pamięci oraz blokowy i znakowy odczyt i zapis pliku): Jeżeli tylko się to uda, program powinien najpierw załadować zawartość pliku do dynamicznie zaalokowanej tablicy znaków. Potem dokonać operacji szyfrowania/deszyfrowania znaków zapisanych w tablicy i zapis wyników do pliku wynikowego. Może się jednak zdarzyć, że plik będzie tak duży, że nie uda się zaalokować tablicy równiej rozmiarowi pliku. Wtedy program powinien wykonać operację szyfrowania/deszyfrowania odczytując zawartości pliku znak po znaku. Wymaganie 4: Proszę ładnie podzielić program na funkcje, zadbać o czytelność kodu."

Niestety profesor nie zrealizował tego tematu na wykładzie, a dał jedynie materiały do samodzielnej nauki. Bazując na tych materiałach napisałem taki kod:

#include <stdio.h>
#include <string.h>

char err_mess[] = "\nBlad wywolania programu\n"
                  "\nparametry: [s|d]  nazwa_pliku_z_danymi  nazwapliku_wynikowego  jednoznakowy_klucz\nWszystkie parametry sa obowiazkowe"
                  "\n";

long int ilosc_znakowPliku(FILE * file) //Funkcja zliczajaca znaki w pliku
{
    long int counter = 0;

    while( ! feof( file ) )
    {
        putchar( fgetc( file ) );
        counter++;
    }
    return counter;
}

void szyfrowanie( FILE * file, FILE * file2, long int dl, char k ) // Funkcja szyfrujaca
{
    char * buffer = new char [dl];

    if( dl == ilosc_znakowPliku(file) /* ?? */ ) //Jesli uda sie zmiescic zawartosc calego pliku w tablicy
    {
        while( fgets( buffer, dl, file ) != NULL )
        {
            for(int i = 0; buffer[i] != '\0'; ++i)
            {
                buffer[i] = buffer[i] ^ k;
            }
            fputs(buffer, file2);
            printf( "Napis po wykonaniu zadanej operacji: %s", buffer );
        }
    }
    else //Jesli nie uda sie zmiescic calego pliku w tablicy
    {
        char znak;

        while( fgetc( file ) != NULL )
        {
            znak = fgetc(file);
            znak = pow(znak, k);
            fputc(znak, file2);
        }
    }
    delete [] buffer;
}

int main(int argc, char *args[] )
{
    char file_name[ 256 ];    /* Zapamietuje nazwe pierwszego pliku */
    char file_name2[ 256 ];   /* Zapamietuje nazwe drugiego pliku   */
    FILE * file;              /* Wskaznik pliku pierwszego          */
    FILE * file2;             /* Wskaznik pliku drugiego            */

    if( argc != 5 || args[1][1] != '\0' || args[4][1] != '\0') //Sprawdzam poprawnosc paramterow
    {
        printf( err_mess );
        return EXIT_FAILURE;
    }
    else //Jesli sa poprawne to zapisuje nazwe plikow do zmiennych
    {
        strcpy( file_name, args[ 2 ] );
        strcpy( file_name2, args[ 3 ] );
    }
    if( ( file = fopen( file_name, "r+t")) != NULL && ( file2 = fopen( file_name2, "r+b")) != NULL) //Otwieram oba pliki
    {
        printf("\nNapis przed zaszyfrowaniem: "); //Wyswietlam napis przed zaszyfrowaniem i wypisuje jego dlugosc

        switch( args[1][0] )
        {
        case 's' :
            printf("%d",ilosc_znakowPliku(file));
            fclose(file);
            fopen ( file_name, "r+t" );
            printf("\n\nNapis po zaszyfrowaniu: ");
            szyfrowanie(file, file2, ilosc_znakowPliku(file), args[4][0]); //Szyfruje plik1 i zapisuje wynik do pliku2
            break;
        case 'd' :
            printf("%d",ilosc_znakowPliku(file));
            fclose(file);
            fopen ( file_name, "r+t" );
            szyfrowanie(file2, file, ilosc_znakowPliku(file), args[4][0]); //Szyfruje plik2 i zapisuje wynik do pliku1
            break;
        default:
            printf( err_mess ); // W przypadku podania zlych parametrow wyswietlam blad
            break;
        }
        fclose( file ); //Zamykam oba pliki
        fclose( file2 );

        puts( "\n---- Press Enter to quit ----" );
        ( void )getchar();
        return EXIT_SUCCESS;
    }
    else //Jesli wczesniej nie udalo sie otworzyc pliku, wyswietlam blad i koncze dzialanie programu
    {
        printf( err_mess );
        puts( "\n---- Press Enter to quit ----" );
        ( void )getchar();
        return EXIT_FAILURE;
    }
}

Niestety z racji tego, że dosyć kiepsko idzie mi nauka z samej teorii, program nie działa tak jak bym chciał. Kiedy go uruchamiam z wiersza poleceń, dzieje się takie coś:

Samo wyświetlenie napisu z pliku działa poprawnie, ilość znaków obok również, ale niestety nic poza tym nie jest takie jak powinno.  

Póki co, jedyne co może być błędem i przychodzi mi do głowy, to pierwszy warunek w funkcji szyfrowanie (tam gdzie znaki zapytania w kodzie), bo jeśli się nie mylę to chyba zawsze będzie on spełniony w takiej postaci, a miało to mieć za zadanie sprawdzać, czy cała zawartość pliku zmieściła się do tablicy. Nie wiem niestety co jeszcze zrobiłem źle skoro nie działa.

W związku z tym chciałbym prosić o pomoc kogoś bardziej rozeznanego, żeby spojrzał na ten kod i wskazał co zrobiłem źle, ewentualnie podpowiadając jeszcze co powinienem zrobić, żeby to poprawić.

Dziękuję z góry za wszelką pomoc, pozdrawiam. 

 

1 odpowiedź

0 głosów
odpowiedź 10 maja 2022 przez j23 Mędrzec (194,920 p.)

W funkcji ilosc_znakowPliku zapomniałeś przewinąć strumień na początek. Teraz po użyciu tej funkcji każda próba odczytu zakończy się niepowodzeniem. Daj przed return wywołania:

clearerr(file);
rewind(file);

Funkcja szyfrowanie to jakieś poplątanie z pomieszaniem. Po co alokujesz bufor na cały plik, skoro i tak czytasz tylko po linii?

Z drugiej strony wymóg 2/3 jest bez sensu, bo co to znaczy, że plik się nie zmieści w buforze? Ten bufor ma jakąś ograniczoną wielkości, czy może plik do zaszyfrowania będzie taki ogromny, że RAM-u nie starczy?

Jeśli piszesz w C, to tego new[] i delete[] nie powinno tam być.

 

komentarz 10 maja 2022 przez Oscar Nałogowiec (29,320 p.)
edycja 11 maja 2022 przez Oscar

Czytanie pliku po znaku by znaleźć jego koniec jest bez sensu - mógłbyś już przy okazji go za/szyfrować i nie tracić czasu i pamięci na ponowne wczytywanie. No i w jakim celu wypisujesz co wczytujesz? Przecież tylko sprawdzasz wielkość.

Sprawdzenie rozmiaru otwartego pliku w klasyczny sposób to:

fseek(f, 0, SEEK_END);  /**< ustawienie pliku na koniec  */
fsize = ftell(f);       /**< odczytanie pozycji   */
fseek(f, 0, SEEK_SET);  /**< powrót na początek   */

Na końcu może być rewind, to dokładnie to samo.

Po za tym warunek zmieszczenie się pliku w pamięci jest z poprzedniego wieku - dzisiaj pamięć jest wirtualna, jak za mało będzie fizycznego ramu, system sobie dobierze z dysku i będzie paradoks - plik jest duży, więc zabieramy z dysku by utworzyć wirtualny ram i na ten dysk wrzucamy plik jeszcze raz. W linuxie allokacja pamięci działa tak, że zwykle się udaje, najwyżej potem jest segfault. Musiałbyś pobrać z systemu szczegóły ile ma ramu, ile zajęte itp a to i tak bez gwarancji, że się zmieści bo jest przecież wielozadaniowość.

Do wczytania całego pliku użyj funkcji fread a do zapisu fwrite.

Ale rozumiem na tyle, że w zadaniu ma być tak:

1, Sprawdzenie wielkości pliku.

2. Allokacja bufora o rozmiarze pliku.

3. Jeśli w 2. się udało to praca blokowa, jeśli się nie udała znakowa.

 

Nie  używaj feof - ta funkcja zwraca koniec już po "walnięciu w niego". fgetc zwraca EOF jak wychodzić poza plik. Taka pętla daje czytanie znak po znaku całego pliku.

int znak;

while ((znak = fgetc(plik_we)) != EOF)
{
   ...
}

 

 

Na razie tyle ...

komentarz 11 maja 2022 przez xTMx3 Obywatel (1,560 p.)

Po wdrożeniu kilku poprawek według podpowiedzi wyszło mi takie coś:

#include <stdio.h>
#include <string.h>
#include <math.h>

char err_mess[] = "\nBlad wywolania programu\n"
                  "\nparametry: [s|d]  nazwa_pliku_z_danymi  nazwapliku_wynikowego  jednoznakowy_klucz\nWszystkie parametry sa obowiazkowe"
                  "\n";

long int rozmiarPliku(FILE * file)
{
    long int rozmiar;
    fseek(file, 0, SEEK_END);
    rozmiar = ftell(file);
    fseek(file, 0, SEEK_SET);
    return rozmiar;
}

void szyfrowanie( FILE * file, FILE * file2, long int dl, char k )
{
    char * buffer = new char [dl];

    if( dl == rozmiarPliku(file))
    {
        fread(buffer, dl, 1, file);
        for(int i = 0; buffer[i] != '\0'; ++i)
        {
            {
                buffer[i] = buffer[i] ^ k;
            }
        }
        fwrite(buffer, dl, 1, file2 );
    }
    else
    {
        int znak;

        while ((znak = fgetc(file)) != EOF)
        {
            znak = znak ^ k;
            putc(znak, file2);
        }
    }

    delete [] buffer;
}

int main( int argc, char *args[] )
{
    char file_name[ 256 ];
    char file_name2[ 256 ];
    FILE * file;
    FILE * file2;

    if( argc != 5 || args[1][1] != '\0' || args[4][1] != '\0')
    {
        printf( err_mess );
        return EXIT_FAILURE;
    }
    else
    {
        strcpy( file_name, args[ 2 ] );
        strcpy( file_name2, args[ 3 ] );
    }
    if( ( file = fopen( file_name, "r+t")) != NULL && ( file2 = fopen( file_name2, "r+b")) != NULL)
    {
        switch( args[1][0] )
        {
        case 's' :
            szyfrowanie(file, file2, rozmiarPliku(file), args[4][0]);
            printf("Zaszyfrowano plik: %s", file_name);
            break;
        case 'd' :
            szyfrowanie(file, file2, rozmiarPliku(file), args[4][0]);
            break;
        default:
            printf( err_mess );
            break;
        }
        fclose( file );
        fclose( file2 );
        puts( "\n---- Press Enter to quit ----" );
        ( void )getchar();
        return EXIT_SUCCESS;
    }
    else
    {
        printf( err_mess );
        puts( "\n---- Press Enter to quit ----" );
        ( void )getchar();
        return EXIT_FAILURE;
    }
}

Teraz program szyfruje i deszyfruje tak jak powinien, jednak nie mam pewności czy wszystko jest poprawnie zapisane i czy nie zrobiłem jakiegoś rażącego błędu.

No i mimo to, że działa nadal nie wiem jak ten warunek w funkcji szyfrowanie, mający ustalić, czy ma być praca blokowa, czy też znakowa, powinien zostać napisany. Ciągle wydaje mi się, że to co mam teraz chyba za każdym razem będzie wykonywało się w sposób pierwszy. Mam tu rację, czy może źle rozumuję coś w temacie tego co sam napisałem?

Rozumiem wyjaśnienia co samego wymogu, ale jednak nie zmienia to faktu, że takie jest polecenie, więc nie mogę raczej tak po prostu go "olać".

komentarz 11 maja 2022 przez Oscar Nałogowiec (29,320 p.)

Szyfrowanie blokowe ma być wykonane na całym wczytanym pliku a nie zatrzymywać się po napotkaniu bajtu zerowego. Tamta pętla powinna się wykonywać tyle razy, ile wczytano danych, niezależnie od zawartości tych danych. Wielkość plików może być większa niż int, pętle lepiej dać na long.

W tej wersji wywołujesz funkcję podając jej wielkość pliku, a następnie, na początku funkcji, sprawdzasz czy ten parametr równa się wielkości pliku. Nie będzie równy tylko, gdy w tej krótkiej chwili plik został powiększony np. przez inny program. Trochę bez sensu. Przypuszczam, że raczej chodziło o sprawdzenie wyniku new/malloc, czy jest NULL czy nie. Jak NULL - nie mamy bufora szyfrujemy znakowo, różne od NULL - bufor zallokowany, jedziemy blokowo. Tylko trzeba pamiętać, że normalnie new rzuca wyjątkiem, by tak nie robiło trzeba podać parametr nothrow, wtedy może zwrócić NULL/nullptr.

Dlaczego otwierasz pliki w trybie r+t/r+b?

komentarz 11 maja 2022 przez j23 Mędrzec (194,920 p.)

@xTMx3, tak to widzę:

void szyfrowanie( FILE * file, FILE * file2,  char k)
{
    long int dl = rozmiarPliku(file);
    char * buffer = malloc(dl);
 
    if( buffer ) {
        fread(buffer, dl, 1, file);
        for(int i = 0; i < dl; ++i) buffer[i] = buffer[i] ^ k;
        fwrite(buffer, dl, 1, file2 );
        free(buffer);
    } else {
        int znak;
 
        while ((znak = fgetc(file)) != EOF) {
            znak = znak ^ k;
            putc(znak, file2);
        }
    }
}

 

komentarz 11 maja 2022 przez xTMx3 Obywatel (1,560 p.)

@Oscar, a czy napotkanie bajtu zerowego nie oznacza czasem końca pliku? Jeśli dobrze pamiętam, to ktoś na uczelni mi w ten sposób to tłumaczył, że bajt zerowy oznacza koniec ciągu znaków, więc zawsze będzie na końcu pliku. Jest to błędne tłumaczenie w takim razie?

Spróbuję jeszcze poprawić tą funkcję według tego co napisałeś.

Natomiast co do samego r+t / r+b, napisałem tak wzorując się na materiałach z uczelni. r+ jest tam jako tryb do odczytu i zapisu istniejącego pliku, t jako tryb tekstowy, a b jako tryb binarny. Pytałem również o to prowadzącego zajęcia na swojej uczelni i powiedział, że ta część jest poprawnie. Dokładnego tłumaczenia nie pamiętam i szczerze mówiąc niewiele z niego zrozumiałem, ale powiedziano mi coś w stylu, że kiedy będę szyfrował dane z pliku to pobieram jako tekst, a je zaszyfruje, wynik już nie będzie tekstem, tylko liczbą bo używam operatora XOR, dlatego też do drugiego pliku r+b. 

Skoro prowadzący uznał mi że jest poprawnie, to uznałem, że tak jest i nie zagłębiałem się w to. 

@j23, dzięki, tą opcję również wypróbuję. 

komentarz 12 maja 2022 przez j23 Mędrzec (194,920 p.)

(...) że bajt zerowy oznacza koniec ciągu znaków, więc zawsze będzie na końcu pliku. Jest to błędne tłumaczenie w takim razie?

Jest. Zero na końcu masz zawsze w c-stringach. Plik nie potrzebuje tego zera, ponieważ plik ma określony rozmiar, więc wiadomo, ile trzeba czytać, by przeczytać całą zawartość.

komentarz 12 maja 2022 przez Oscar Nałogowiec (29,320 p.)
edycja 12 maja 2022 przez Oscar

@xTMx3, A propos trybu otwarcia, manual podaje tak:

r      Open text file for reading.  The stream is positioned at the beginning of the file.

r+     Open for reading and writing.  The stream is positioned at the beginning of the file.

w      Truncate file to zero length or create text file for writing.  The stream is positioned at the beginning of the file.

w+     Open for reading and writing.  The file is created if it does not exist, otherwise it is truncated.  The stream is positioned at the beginning of the file.

a      Open for appending (writing at end of file).  The file is created if it does not exist.  The stream is positioned at the end of the file.

a+     Open  for reading and appending (writing at end of file).  The file is created if it does not exist.  Output is always appended to the end of the file. 

 

O ile plik wejściowy może być otwarty w r+ (aczkolwiek ten + jest niepotrzebny - lepiej plik otwierać w minimalnym potrzebnym trybie, bo np. jak nie masz praw zapisu to nie otworzysz w r+), to dla pliku wyjściowego jest to bez sensu.  Po pierwsze wtedy plik musi istnieć - a to program szyfrujący ma go utworzyć gdy go nie ma, po drugie, nawet jeśli istnieje to nie zostanie obcięty - będziesz go nadpisywał, ale końcówka może zostać stara.

Po prostu plik wejściowy otwieraj w trybie r a wyjściowy w trybie w.

komentarz 12 maja 2022 przez xTMx3 Obywatel (1,560 p.)

Zmieniłem tryby i funkcję szyfrowanie również poprawiłem. Jednak to malloc, którego użył @j23, w swoim przykładzie, jest mi zupełnie obce i nie chce w dodatku działać (poczytałem o tym na tej stronce: https://www.cplusplus.com/reference/cstdlib/malloc/ i nawet pomimo, że wzorowałem się na zarówno przykładzie na stronce jak i wizji funkcji do mojego zadania napisanej przez j23 wyświetla mi się błąd "error: invalid conversion from 'void*' to 'char*' [-fpermissive]"). 

Kiedy jednak robię to tak, jak jest w materiałach z uczelni, czyli tak samo jak w mojej wersji ( char * buffer = new char[dl]; i na końcu delete [] buffer ) wszystko działa jak bez zarzutu. 

Czy pomimo, że to rozwiązanie działa jest ono błędne i powinienem znaleźć sposób na to, żeby malloc zadziałało? Jeśli tak, to dlaczego wersja z new i delete jest zła?

komentarz 12 maja 2022 przez j23 Mędrzec (194,920 p.)

Gdybyś kompilował w kompilatorze C a nie C++, to problemu by nie było, a tak musisz jawnie rzutować:

char * buffer = (char*)malloc(dl);

Dlaczego C? Bo twój kod wygląda na kod napisany w C.

komentarz 12 maja 2022 przez Oscar Nałogowiec (29,320 p.)

@xTMx3,  Tak jak wspomniałem, jeśli chcesz używać new musisz użyć parametru nothrow.

new(std::nothrow) char[dl]

Bez takiego parametry new rzuci wyjątkiem - czyli program od razu zostanie zakończony (bo nie obsługujesz wyjątków) - gdy nie będzie mógł zaallokować pamięci. Normalnie to nie jest wielki problem - jak nie daje się zaalokować pamięci zwykle nie ma sensu dalej działać, ale akurat w twoim przypadku kontynuowanie ma sens.

Ale zgadzam się z @j23 - lepiej zostań przy malloc/free. Masz kod zasadniczo w czystym C, niech już takim zostanie.

komentarz 12 maja 2022 przez xTMx3 Obywatel (1,560 p.)
Na prawdę jest to pisane w C? Wyjaśniało by to w sumie skąd jakieś dziwne problemy ze zrozumieniem wszystkiego u mnie, bo od początku semestru zajęcia z programowania miały na celu nauczenie nas C++'a.

Tydzień po tygodniu, wszystko było z C++, potem dostałem to zadanie z szyfrowaniem (i tutaj ta dziwna anomalia, bo materiały które dostałem były pisane, tak jak to twierdzicie, w C). A dwa dni temu, na kolejnych zajęciach przeszliśmy do dalszej części, wprowadzenie do obiektów, ale już z powrotem w C++.

Teraz już kompletnie pogubiłem się, w jakiej wersji to powinienem w ogóle wysłać, bo skoro uczyć mieli nas przez cały semestr C++, to z tego by wynikało, że ma być C++, ale z kolei to jedno zadanie różni się "wyglądem" od reszty.

Albo ja czegoś nie rozumiem i ominąłem jakiś duży przeskok w nauce, pomimo że nie opuściłem ani jednych zajęć, albo coś tu nie gra i jest namotane.

W każdym razie... Coś spróbuję wykombinować.

Dziękuję wam serdecznie za pomoc.
komentarz 12 maja 2022 przez Oscar Nałogowiec (29,320 p.)
C jest podzbiorem C++ (oczywiście chronologicznie to C++ jest rozszerzeniem C), kod w C jest też automatycznie kodem w C++, jedynie pewne restrykcje są silniejsze - coś co w C jest warningiem, w C++ może być już błędem. Ale "ładny" kod w C kompiluje się jako C++ bez problemu.
komentarz 13 maja 2022 przez j23 Mędrzec (194,920 p.)

@xTMx3, 

Na prawdę jest to pisane w C?

W typowym C++ użyłbys strumieni std::cout, std::ifstream i std::ofstream zamiast printf, fopen, fwrite, fread itd. std::string zamiast tablic char[] i std::vector zamiast ręcznego zarządzania pamięcią przez malloc/free.

Podobne pytania

0 głosów
1 odpowiedź 744 wizyt
pytanie zadane 14 października 2015 w C i C++ przez C☺ndzi Stary wyjadacz (12,100 p.)
0 głosów
1 odpowiedź 415 wizyt
pytanie zadane 12 stycznia 2023 w C# przez DominikPie Użytkownik (770 p.)
+1 głos
1 odpowiedź 573 wizyt

92,669 zapytań

141,567 odpowiedzi

320,033 komentarzy

62,034 pasjonatów

Motyw:

Akcja Pajacyk

Pajacyk od wielu lat dożywia dzieci. Pomóż klikając w zielony brzuszek na stronie. Dziękujemy! ♡

Oto polecana książka warta uwagi.
Pełną listę książek znajdziesz tutaj.

Akademia Sekuraka

Niedawno wystartował dodruk tej świetnej, rozchwytywanej książki (około 940 stron). Mamy dla Was kod: pasja (wpiszcie go w koszyku), dzięki któremu otrzymujemy 10% zniżki - dziękujemy zaprzyjaźnionej ekipie Sekuraka za taki bonus dla Pasjonatów! Książka to pierwszy tom z serii o ITsec, który łagodnie wprowadzi w świat bezpieczeństwa IT każdą osobę - warto, polecamy!

...