Puncte:0

Criptare unică cu pad în C

drapel vn

M-am încurcat cu criptografia (pentru uz recreațional) și mi-am creat propriul script de criptare cu un singur pad în C. Acum, acestea fiind spuse, recunosc fără îndoială că nu sunt deloc un expert în criptografie. Știu că regula nr. 1 a criptografiei este să nu o faci singur. Cu toate acestea, sunt cu adevărat interesat dacă scriptul meu de criptare este (teoretic) sigur.

În primul rând, iată o prezentare de bază a ceea ce încerc să obțin cu metoda mea de criptare:

Scopul este de a realiza One Time Pad Encryption, în care sunt utilizate atât (1) un tabel hash, cât și (2) o cheie de criptare. Tabelul hash (în acest caz) este precodat, unde valorile sunt 01-98.

Cheia de criptare se calculează după cum urmează:

  1. Obține intrare aleatorie de utilizator (cel puțin aceeași lungime ca mesajul)
  2. Obțineți indexul corespunzător pentru char în validChars[] (codul sursă de mai jos)
  3. Obțineți numărul din defaultHashTable care corespunde indexului #2

Mesajul este criptat după cum urmează:

  1. Luați mesajul și convertiți-l în valorile defaultHashTable corespunzătoare
  2. Luați cheia generată anterior și adăugați-o la mesajul convertit
  3. Acum este criptat (presupunând că aceeași cheie nu este niciodată folosită din nou)

De exemplu:

  1. Mesaj: salut
  2. Convertiți în valorile defaultHashTable corespunzătoare: salut -> 0805121215
  3. Obțineți caractere aleatorii pentru cheia: abkdh
  4. Convertiți în valorile defaultHashTable corespunzătoare: abkdh -> 0102110408
  5. Adăugați cheia: 0805121215 + 0102110408 = 0907231623
  6. Mesaj criptat: 0907231623

Iată codul sursă (NOTĂ: Aceasta este o combinație de funcții care erau în fișiere de antet C separate, de aceea nu postez o funcție principală ()):

// Cheia (pentru mesaj) va fi maximum [50.000][3] (deci o matrice în care fiecare are 2 caractere):
typedef struct {
    cheie char[MAX_SIZE][3];
} Cheie;
Cheie globalKey;

// Mesajul criptat va fi returnat în această structură (deoarece este doar o modalitate ușoară de a returna o matrice bidimensională dintr-o funcție în C):
typedef struct {
    char encryptedMessage[MAX_SIZE][3];
} EncryptedMessage;

// Tabelul hash este o matrice bidimensională precodata (care este încărcată în initDefaultHashTable()), care va fi folosită împreună cu cheia pentru a cripta mesajele:
// NOTĂ: Există, de asemenea, un alt mod de criptare pe care nu îl includ (în încercarea de a face acest lucru concis) în care trebuie să tastați manual numere aleatorii cu două cifre (97 dintre ele) pentru tabelul hash
typedef struct {
    char hashtable[HASHTABLE_CAPACITY][3];
} DefaultHashTable;
DefaultHashTable defaultHashTable; // Declarați un defaultHashTable global care va stoca acest hashtable

// Încărcați defaultHashTable cu 1-98, respectiv:
void initDefaultHashTable(){
    pentru (int i = 0; i < HASHTABLE_CAPACITY; i++){
        char s[3];
        sprintf(s, "%d", (i+1));

        dacă (i < 10){
            char tmp = s[0];
            s[0] = '0';
            s[1] = tmp;
        }

        pentru (int j = 0; j < 2; j++){
            defaultHashTable.hashtable[i][j] = s[j];
        }
    }
}

// Caractere valide pe care le poate conține mesajul (97 dintre ele):
char validChars[] = {'a','b','c','d','e','f','g','h','i','j','k', 'l','m','n','o','p','q','r','s','t','u','v','w','x ','y','z','A','B','C','D','E','F','G','H','I','J', "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W" ','X','Y','Z','!','@','#','$','%','^','&','*','(', ')','-','+',' ',',','.',':',';','\'','\"','[',']',' {','}','_','=','|','\','/','<','>','?',''','~','\ n','\t','0','1','2','3','4','5','6','7','8','9'};

// Pentru că returnarea caracterului eșuează (simt că există o modalitate mai bună de a face această parte):
char FAILED = (car)255;

// Găsiți indexul unui caracter valid (din validChars) sau FALSE dacă nu există:
int findChar(char c){
    pentru (int i = 0; i < strlen(validChars); i++){
        dacă (validChars[i] == c){
            returnează i;
        }
    }
    returnează FALSE;
}

// Returnează caracterul din indexul dat validChars:
char returnChar(index int){
    returnează caractere valide[index];
}

// Obține indexul unei valori date de tabel hash (de la defaultHashTable) și apoi, dacă este cazul, caracterul corespunzător în validChars:
char findHashTableValue(char n[3], char hashtable[][3]){
    pentru (int i = 0; i < HASHTABLE_CAPACITY; i++){
        dacă (hashtable[i][0] == n[0] && hashtable[i][1] == n[1])
            return returnChar(i);
    }
    return FAILED;
}

// Partea PRINCIPALĂ a codului (funcția principală c ar numi acest lucru): Criptează folosind o singură criptare cu pad, dar folosind un tabel hash implicit pentru a economisi timp:
void goThroughLightEnryptionProcess(char * str, char * write_to_file){
     // Încărcați defaultHashTable:
    initDefaultHashTable();

    // Folosește funcția pentru a crea chei aleatorii (pe baza intrărilor aleatorii ale utilizatorului):
    generateRandomKey(strlen(str), MAX_SIZE, FALSE);

    // Scrieți mesajul:
    EncryptedMessage encryptMsg = otpEncrypt(defaultHashTable.hashtable, str, globalKey.key);

    // Circulați și imprimați conținutul (dacă nu scrieți în fișier):
    dacă (scrie_în_fișier == NULL){
        pentru (int i = 0; i < strlen(str); i++){
            pentru (int j = 0; j < 2; j++){
                printf("%c", encryptMsg.encryptedMessage[i][j]);
            }
        }
        printf("\n");
    } altfel {
        // Scrieți mesajul criptat în fișier:
        // NOTĂ: acesta este un alt parametru pe care îl puteți trece în programul actual (care este mult mai mare decât acesta) unde îl puteți scrie într-un fișier în loc să îl afișați în terminal:
        // NOTĂ: Nu am inclus acest cod de matrice writeFileWithTwoDimensionalArray() aici, deoarece este irelevant, deoarece pur și simplu scrie într-un fișier.
        writeFileWithTwoDimensionalArray(encryptMsg.encryptedMessage, HASHTABLE_CAPACITY, write_to_file);
    }
}

// (Funcția de ajutor) Încărcați matricea cu două caractere în cheie:
void loadIntoKeyForRandoKey(int at, char n[3]){
    pentru (int i = 0; i < 2; i++){
        globalKey.key[at][i] = n[i];
    }
}

// Generați o cheie pe baza intrărilor aleatorii ale utilizatorului:
void generateRandomKey(int password_length, int max_size, bool use_global){
    // @Folosește globalHashTable | defaultHashTable
    // @Folosește globalKey
    răspuns char[max_size];

    printf("Introduceți caractere aleatorii pentru cheie (a-z,A-Z,!@#$%%^&*()-+=, lungime minimă de %d): ", parola_lungime);
    fgets(răspuns, max_size, stdin);

    // Eliminați caracterul „\n” de la sfârșit:
    char * p;
    dacă ((p = strchr(răspuns, '\n')) != NULL){
        *p = '\0';
    } altfel {
        scanf("%*[^\n]");
        scanf("%*c");
    }

    // Asigurați-vă că introducerea utilizatorului este >= password_length:
    if (strlen(răspuns) <parolă_lungime){
        printf("\n[ EROARE ]: caracterele aleatoare trebuie să fie mai mari sau egale cu %d.\n", lungime_parolă);
        return generateRandomKey(lungime_parolă, dimensiune_max, utilizare_global);
    }

    // Convertiți caracterele aleatoare în echivalența lor în tabelul hash, respectiv:
    pentru (int i = 0; i < lungime_parolă; i++){
        int getCharIndex = findChar(răspuns[i]);
        
        // Asigurați-vă că a fost găsit cu succes:
        dacă (getCharIndex == FALSE){
            printf("\n[ EROARE ] Caracterul '%c' este nevalid. Încercaţi din nou.\n", răspuns[i]);
            return generateRandomKey(lungime_parolă, dimensiune_max, utilizare_global); // Fă-o din nou
        }

        // Încărcați valoarea hashtable corespunzătoare în cheie:
        dacă (use_global == TRUE){
            loadIntoKeyForRandoKey(i, globalHashTable.hashtable[getCharIndex]);
        } altfel {
            loadIntoKeyForRandoKey(i, defaultHashTable.hashtable[getCharIndex]);
        }
    }

    // Scrie cheia aleatorie într-un fișier:
    createFileWithTwoDimensionalArray(globalKey.key, password_length, "key");
}

// (Funcția de ajutor) Pentru încărcare în structura EncryptedMessage:
void loadIntoEncryptedMessage(int at, char n[3], EncryptedMessage *encryptedMsg){
    dacă (strlen(n) == 1){
        // Adăugați un „0”:
        char tmp = n[0];
        n[0] = '0';
        n[1] = tmp;
    }

    pentru (int i = 0; i < 2; i++){
        encryptedMsg->encryptedMessage[at][i] = n[i];
    }
}

/*
    Criptează un mesaj având o cheie și un hashtable valide
*/
EncryptedMessage otpEncrypt(char hashtable[][3], char * msg, char key[MAX_SIZE][3]){
    EncryptedMessage encryptedMsg;

    pentru (int i = 0; i < strlen(msg); i++){
        // Convertiți valoarea cheii în număr întreg:
        int convertedKeyValueIntoInt = safelyConvertToInt(key[i]);

        // Asigurați-vă că a fost convertit corect:
        dacă (convertedKeyValueIntoInt == FALSE){
            printf("[ EROARE ]: Cheia este coruptă la %d (valoare = %s).\n", i, cheia[i]);
            ieșire(1);
        }

        // Convertiți mesajul utilizatorului în echivalența acestuia în tabelul hash:
        int indexOfMsgChar = findChar(msg[i]);

        // Asigurați-vă că findChar() a găsit valoarea corect:
        dacă (indexOfMsgChar == FALSE){
            printf("[ EROARE ]: parola (msg) este coruptă la %d (valoare = %s). Este posibil să fi avut loc deoarece caracterul „%c” nu este permis.\n", i, msg, msg[i ]);
            ieșire(1);
        }

        char * correspondingEncryptMsgChars = hashtable[indexOfMsgChar];

        // Convertiți caracterele encryptMsg corespunzătoare în int:
        int convertedEncryptMsgCharsIntoInt = safelyConvertToInt(correspondingEncryptMsgChars);

        // Asigurați-vă că a fost convertit corect:
        dacă (convertedEncryptMsgCharsIntoInt == FALSE){
            printf("[ EROARE ]: Tabelul Hash este corupt la %d (valoare = %s).\n", indexOfMsgChar, corespunzatorEncryptMsgChars);
            ieșire(1);
        } 

        // Faceți calculul:
        int encryptedFrag = otpeAdd(convertedEncryptMsgCharsIntoInt, convertedKeyValueIntoInt);
        
        // Convertiți-l într-un șir:
        char encryptedFragStr[3];
        sprintf(encryptedFragStr, "%d", encryptedFrag);
        
        loadIntoEncryptedMessage(i, encryptedFragStr, &encryptedMsg);
    }
    return encryptedMsg;
}

Întrebarea mea imediată ar fi: dacă folosesc o tabelă hash pre-codificată (pe care oricine ar putea deduce), aceasta face criptarea nesigură (chiar dacă cheia care corespunde valorilor tabelului hash este complet aleatorie prin introducerea utilizatorului)? Este sigur doar dacă randomizez numerele tabelului hash (01-98) (și potențial randomizez validChars[])?

Sunt sincer interesat dacă logica mea este valabilă, așa că orice comentarii, sugestii sau critici ar fi foarte apreciate.

Paul Uszak avatar
drapel cn
Bună. Alegere excelentă pentru utilizarea OTP-urilor. Dar, _"(1) Obține intrare aleatorie de utilizator (cel puțin aceeași lungime ca mesajul)"_ . Cum? Dacă codific un mesaj de dimensiunea Tweet, de unde vine intrarea aleatorie? Amintiți-vă că un OTP **trebuie** să fie generat fizic prin hardware mecanic sau biologic, **nu** software.
drapel vn
@PaulUszak Bună! Primesc input aleatoriu de către utilizator în prezent, solicitând utilizatorului să introducă în mod spontan caractere aleatorii care au lungimea sau mai mare decât mesajul. Este sigur?
drapel zw
Oamenii sunt indiscutabil îngrozitori în a crea intrări aleatorii. OTP *necesită, prin definiție*, intrare cu adevărat aleatorie ca taste.
Puncte:1
drapel cn

Trei probleme apar imediat:

  1. Vechea castană a integrității mesajului. Tampoanele pur o singură dată nu au niciun mijloc de a se autentifica, adică sunt maleabil.

  2. Cheia (pentru mesaj) va fi de maximum [50.000]. Am vorbit în mod special despre mesajele de dimensiunea Tweet pentru un motiv.OTP-urile au fost create inițial prin mașini de scris și au avut mare succes. Dar erau mici și ar fi fost multe dactilografe. Nu există nici un test statistic care să infirme caracterul aleatoriu a 160 de caractere (tactilate cu grijă). Dar există pentru 50.000. Este foarte puțin probabil ca 50.000 de caractere tastate la întâmplare pe o tastatură să fie doar atât. Analiza de frecvență și euristica vor degrada sever presupusa securitate bazată pe informații a OTP. Și utilizatorii chiar vor introduce 50.000 de litere? Un dispozitiv hardware (TRNG) este necesar pentru cheile de această dimensiune.

  3. Cum va decripta destinatarul mesajul, cu excepția cazului în care [pronume] are aceeași cheie care a fost folosită pentru a-l cripta? Deci cum va ajunge acolo?

3½.Tabelul de hash este aproape inutil. Utilizați doar valorile ASCII, deoarece securitatea într-un OTP vine de la cheie. Merită să citești câteva dintre întrebările etichetate OTP Aici.

Puncte:0
drapel zw

Acesta este un lot de cod pentru ceva care se rezumă la:

void otp(size_t len, uint8_t *key, uint8_t *message) {
    pentru (mărimea_t i = 0; i < len; i++) {
        mesaj[i] ^= cheie[i];
    }
}

Orice altceva (în afară de asigurarea len(cheie) == len(mesaj) nu este doar inutilă, ci degradează în mod activ faptul că este o implementare reală a unui tampon unic. Chiar și a spune „se reduce la” aici este probabil înșelător: aceasta nu este doar o versiune simplificată; în afară de a lua o cheie de lungime adecvată dintr-o sursă cu adevărat aleatorie, este complet complet. Acea parte omisă este probabil puțin mai mult decât

char *key = malloc(len);

assert(cheie != NULL);
assert(read(fd, key, len) == len);

Nu m-am uitat la ceea ce face „tabelul de hash” al tău, deoarece orice ar face este complet inutil, extrem de probabil să fie o sursă de erori care cauzează pierderi catastrofale de securitate și aproape sigur încalcă definiția fundamentală a ceea ce constituie un bloc unic. .

Un tampon de o singură dată trebuie sa au chei cu adevărat aleatorii. Caracterele tastate pe o tastatură nu sunt cu adevărat aleatorii. Ieșirea de /dev/random nu este cu adevărat întâmplător. Obținerea de biți cu adevărat aleatorii este dificilă, dar există dispozitive externe care le vor colecta pentru tine (presupunând că ai încredere în dispozitiv). Cheile alea trebuie sa să aibă aceeași lungime cu mesajul dvs. (sau mai lung, presupun).

Și, în sfârșit, deși nu este o necesitate strictă, criptografia este, în general, cel mai bine efectuată biți și octeți și nu vreo noțiune de „personaje”. Încercarea de a face aceasta din urmă înseamnă să te pregătești pentru erori grave care duc la eșec catastrofal.

Mark avatar
drapel ng
Merită menționat faptul că „pad-ul unic trebuie să aibă chei cu adevărat aleatorii” nu este strict adevărat --- puteți înlocui sursa aleatorie cu un PRG (în modul contor să spunem) pentru a obține $\mathsf{CTR}\$$ criptare. Desigur, acest lucru nu este perfect sigur, dar poate merită menționat unui nou venit pentru implementări de criptare deosebit de simple.
drapel zw
În acel moment, este un stream cipher și nu un bloc unic. Aceasta nu este doar o distincție academică, înseamnă că miezul întregii sarcini este acum fundamental diferit. Nu este vorba doar de biți XORing, partea grea este proiectarea și scrierea unui CSPRNG.
Mark avatar
drapel ng
Da, dar este totuși util din punct de vedere conceptual să înțelegem cifrurile de flux. OTP este simplu din punct de vedere conceptual, dar gestionarea cheilor este dificilă. CTR$ înlocuiește pur și simplu managementul cheii (hard) cu problema (grea) a proiectării unui PRG. Din fericire, deși ambele sunt dificile, se pare că putem de fapt să proiectăm PRG-uri, în timp ce gestionarea cheilor OTP la scară pare (în mare parte) fără speranță.
Paul Uszak avatar
drapel cn
Interesant. De ce crezi că `/dev/random` nu este cu adevărat aleatoriu? Mă refer la cel care a blocat(e). Vorbesti despre cel nou? În plus, nu este atât de greu, deoarece poți face asta fără nici un kit extern - jitter CPU - `System.nanoTime()` sau `haveged`, sau biblioteca de entropie Arduino.
drapel zw
`/dev/random` și `/dev/urandom` [sunt ambele rezultate din același CSPRNG] (https://www.2uo.de/myths-about-urandom/). Chiar dacă ați vrut să argumentați că nucleul estimează un raport de 1:1 între entropia de intrare și entropia de ieșire din primul, acea garanție iese din fereastra odată ce o trimiteți printr-un CSPRNG care garantează doar securitatea computațională și nu are o dovadă. de perfectă securitate. `haveged` adaugă entropie la estimatorul de entropie a nucleului, dar aceasta încă se confruntă cu aceeași problemă fundamentală.
drapel zw
Oricum, ideea nu este că este imposibil să obții numere cu adevărat aleatorii. Ideea este că OTP în sine este fundamental banal, partea „grea” este obținerea unor numere care sunt de fapt aleatorii și nu „mi se pare bine” aleatorii. Și apoi, desigur, dacă aveți un canal prin care le puteți partaja în siguranță cu o terță parte, ați putea la fel de bine să schimbați mesajul în sine prin acel canal.

Postează un răspuns

Majoritatea oamenilor nu înțeleg că a pune multe întrebări deblochează învățarea și îmbunătățește legătura interpersonală. În studiile lui Alison, de exemplu, deși oamenii își puteau aminti cu exactitate câte întrebări au fost puse în conversațiile lor, ei nu au intuit legătura dintre întrebări și apreciere. În patru studii, în care participanții au fost implicați în conversații ei înșiși sau au citit transcrieri ale conversațiilor altora, oamenii au avut tendința să nu realizeze că întrebarea ar influența – sau ar fi influențat – nivelul de prietenie dintre conversatori.