Domanda Risolto E' sicuro questo HKDF che ho scritto?

Stato
Discussione chiusa ad ulteriori risposte.

JunkCoder

Moderatore
5 Giugno 2020
1,682
25
1,500
653
Ultima modifica:
Vorrei avere un vostro parere se questo codice presenta qualche vulnerabilita' a livello crittoanalitico considerando key sicura.
E se e' sicuro generare uno stream da questa e fare uno XOR con un messaggio per proteggerlo, in caso contrario perche'?.

C#:
byte[] MyHKDF(byte[] key, int outputLen)
{
    // Compute needed HMACs for outputLength
    uint blockCount = (uint)Math.Ceil((decimal)outputLen / 32);
    byte[] outputBuff = new byte[blockCount * 32];
    for (UInt32 i = 0; i < blockCount; i++)
    {
        byte[] counter = UI32_ToBytes(i);
        byte[] block = HMAC_SHA256(key, counter);
        Buffer.BlockCopy(block, 0, outputBuff, i * 32, 32);
    }
    // Truncate exceeding bytes
    outputBuff.SetLength(outputLen);
    return outputBuff;
}
 
È banalmente suscettibile a un attacco chosen-plaintext:
  • ho un testo T sufficientemente lungo;
  • tu fai MyHKDF(key, len(T)) ^ T = C e mi invii C, che sarebbe la versione cifrata di T con una chiave key che io, in quanto attaccante, non conosco;
  • calcolo T ^ C = P.
Ora posso usare P per decifrare tutti i crittotesti c cifrati con key, facendo c ^ P = t. Il messaggio viene decifrato completamente a patto che len(t) <= len(T) e parzialmente (ma comunque correttamente!) in caso contrario.

Non ho recuperato la password, ma questo non ha frenato nel decifrare i messaggi.
 
Ultima modifica:
È banalmente suscettibile a un attacco chosen-plaintext:
  • ho un testo T sufficientemente lungo;
  • tu fai MyHKDF(key, len(T)) ^ T = C e mi invii C, che sarebbe la versione cifrata di T con una chiave key che io, in quanto attaccante, non conosco;
  • calcolo T ^ C = P.
Ora posso usare P per decifrare tutti i crittotesti c cifrati con key, facendo c ^ P = t. Il messaggio viene decifrato completamente a patto che len(t) <= len(T) e parzialmente (ma comunque correttamente!) in caso contrario.

Non ho recuperato la password, ma questo non ha frenato nel decifrare i messaggi.

Si, in ogni caso sarebbe vulnerabile riutilizzando la chiave perche' genererei sempre lo stesso stream (quello che ricavi facendo xor con il plaintext conosciuto), ma usando una chiave sempre diversa per ogni messaggio e' vulnerabile? Perche' per generare quella chiave pensavo di fare HMAC_SHA256(MasterKey, Salt) ed appendere il sale random prima del ciphertext (una sorta di IV), invece la masterkey e' statica e viene riutilizzata. Cosi' c'e' qualche altro problema o posso usarlo tranquillamente?
 
Si, in ogni caso sarebbe vulnerabile riutilizzando la chiave perche' genererei sempre lo stesso stream, ma usando una chiave sempre diversa per ogni messaggio e' vulnerabile?
Sarebbe impratico, come ogni OTP. Se sei interessato a sviluppare un protocollo di questo tipo è importante anche concepire un meccanismo di key exchange. Non puoi semplicemente chiedermi: "è sicuro se questo requisito del tutto non scontato e difficile da garantire si verifica?".

Perche' per generare quella chiave pensavo di fare HMAC_SHA256(MasterKey, Salt) ed appendere il sale random prima del ciphertext (una sorta di IV), invece la masterkey e' statica e viene riutilizzata.
Riesci a spiegarmelo meglio e in modo più approfondito?

Cosi' c'e' qualche altro problema o posso usarlo tranquillamente?
Indipendentemente da tutto, le uniche cose che mi sento di approvare con "puoi usarlo tranquillamente" sono gli algoritmi standard e poco più. In crittografia tutto ciò che non è stato approfonditamente studiato è da considerarsi insicuro. Ho fatto notare più volte in questo forum come sia facile cadere in un falso senso di sicurezza. Don't roll your own crypto. Ciò non toglie che non ci si possa divertire e che non si possa imparare qualcosa. Ti invito ad andare avanti, ma non avere la presunzione di creare qualcosa di veramente sicuro.

Se vuoi avere una relativa tranquillità e sicurezza: usa algoritmi conosciuti, non implementarli tu, non prendere implementazioni closed-source e verifica che la libreria che vai ad usare abbia una buona reputazione.
 
Sarebbe impratico, come ogni OTP. Se sei interessato a sviluppare un protocollo di questo tipo è importante anche concepire un meccanismo di key exchange. Non puoi semplicemente chiedermi: "è sicuro se questo requisito del tutto non scontato e difficile da garantire si verifica?".


Riesci a spiegarmelo meglio e in modo più approfondito?


Indipendentemente da tutto, le uniche cose che mi sento di approvare con "puoi usarlo tranquillamente" sono gli algoritmi standard e poco più. In crittografia tutto ciò che non è stato approfonditamente studiato è da considerarsi insicuro. Ho fatto notare più volte in questo forum come sia facile cadere in un falso senso di sicurezza. Don't roll your own crypto. Ciò non toglie che non ci si possa divertire e che non si possa imparare qualcosa. Ti invito ad andare avanti, ma non avere la presunzione di creare qualcosa di veramente sicuro.

Se vuoi avere una relativa tranquillità e sicurezza: usa algoritmi conosciuti, non implementarli tu, non prendere implementazioni closed-source e verifica che la libreria che vai ad usare abbia una buona reputazione.

Grazie, si conosco il detto, volevo fare questo esperimento un po' per curiosita' ed anche per riuscire a fare un cipher simmetrico con un semplice hash.

Riesci a spiegarmelo meglio e in modo più approfondito?

Volentieri, aggiungo il codice C# completo:

C#:
byte[] MyHKDF(byte[] key, int outputLen)
{
    // Compute needed HMACs for outputLength
    uint blockCount = (uint)Math.Ceil((decimal)outputLen / 32);
    byte[] outputBuff = new byte[blockCount * 32];
    for (UInt32 i = 0; i < blockCount; i++)
    {
        byte[] counter = UI32_ToBytes(i);
        byte[] block = HMAC_SHA256(key, counter);
        Buffer.BlockCopy(block, 0, outputBuff, i * 32, 32);
    }
    // Truncate exceeding bytes
    outputBuff.SetLength(outputLen);
    return outputBuff;
}

byte[] SecureRandom(int len)
{
    // ... CSPRNG Windows API
}

byte[] Encrypt(byte[] MasterKey, byte[] message)
{
    // Genero il sale (IV) random
    byte[] salt = SecureRandom(16);
    // Genero la chiave unica
    byte[] messageKey = HMAC_SHA256(MasterKey, salt);
   
    byte[] cipherStream = MyHKDF(messageKey, message.Length);
    byte[] cipherText = new byte[16 + message.Length];
   
    // Copio il sale all'inizio del cipherText
    Buffer.BlockCopy(salt, 0, cipherText, 0, 16);
    // Faccio lo XOR
    for (int i = 0; i < message.Length; i++)
        cipherText[i + 16] = cipherStream[i] ^ message[i];
   
    return cipherText;
}

// Decrypt legge il salt, deriva la stessa chiave e fa nuovamente lo xor col cipherText
 
Considerando MasterKey sicura mi sembra okay, ma da quanto ho capito MasterKey è la password scelta dall'utente quindi dovresti aggiungere un meccanismo di rehashing (tipo h(h( ... h(MasterKey) ... )) che si ripete per tante volte) per evitare che provino ad ricavare messageKey partendo salt trasmesso in chiaro. Meglio ancora se generi messageKey usando una KDF seria, che previene anche gli attacchi paralleli fatti con GPU e quant'altro.

Comunque sì... oltre a quanto ti ho riportato, al momento non vedo particolari problematiche. Se mi dovesse venire in mente qualcosa ti faccio sapere, ma alla fine è il classico CTR con IV random e a cui gli applichi una funzione di hash crittograficamente sicura e poche altre varianti. Dovrebbe essere un algoritmo decente.
 
  • Mi piace
Reazioni: JunkCoder
Considerando MasterKey sicura mi sembra okay, ma da quanto ho capito MasterKey è la password scelta dall'utente quindi dovresti aggiungere un meccanismo di rehashing (tipo h(h( ... h(MasterKey) ... )) che si ripete per tante volte) per evitare che provino ad ricavare messageKey partendo salt trasmesso in chiaro. Meglio ancora se generi messageKey usando una KDF seria, che previene anche gli attacchi paralleli fatti con GPU e quant'altro.

Comunque sì... oltre a quanto ti ho riportato, al momento non vedo particolari problematiche. Se mi dovesse venire in mente qualcosa ti faccio sapere, ma alla fine è il classico CTR con IV random e a cui gli applichi una funzione di hash crittograficamente sicura e poche altre varianti. Dovrebbe essere un algoritmo decente.

Grazie, si non ho ancora deciso come derivare la MasterKey ma pensavo a PBKDF2 SHA256 della password a con parecchie iterazioni

Meglio ancora se generi messageKey usando una KDF seria, che previene anche gli attacchi paralleli fatti con GPU e quant'altro.

Fatto cosi' com'e' anche con le GPU in parallelo dovrebbero ricavare messageKey dall'hash_hmac con 0x00000001 (il primo blocco), essendo messageKey 256 bit ad alta entropia non e' gia' pressoche' impossibile (con la tecnologia attuale)?
 
Fatto cosi' com'e' anche con le GPU in parallelo dovrebbero ricavare messageKey dall'hash_hmac con 0x00000001, essendo messageKey 256 bit ad alta entropia non e' gia' pressoche' impossibile (con la tecnologia attuale)?
Assumevo che MasterKey fosse la password e che HMAC_SHA256(MasterKey, salt) fosse una tua versione di una KDF quindi, visto che la KDF non è dispendiosa, partendo da salt e da messageKey puoi ricavare MasterKey (e.g., con un attacco al dizionario fatto in parallelo). Se MasterKey non è scelta dall'utente ma è generata in modo sicuro il discorso cambia.
 
Assumevo che MasterKey fosse la password e che HMAC_SHA256(MasterKey, salt) fosse una tua versione di una KDF quindi, visto che la KDF non è dispendiosa, partendo da salt e da messageKey puoi ricavare MasterKey (e.g., con un attacco al dizionario fatto in parallelo). Se MasterKey non è scelta dall'utente ma è generata in modo sicuro il discorso cambia.

Bene, HMAC_SHA256 e' semplicemente un hash hmac secondo specifiche classiche, volevo tenere il KDF piu' leggero possibile proprio perche' cosi' posso generare uno stream abbastanza lungo da criptare anche qualche MB di messaggio senza starci troppo. Alla fine dove si perde tempo qua e' solo con il PBKDF2 ma essendo al login e' accettabile anche con 100k iterazioni se ci sta un paio di secondi. Ok quindi penso di poterla usare, non cifrero' il mio numero di carta di credito ma comunque per fare una chat e scambiare dei file non mi sembra male.

Aspetto domani per segnare risolto in caso viene qualche idea per "rompere" l'algoritmo
 
Bene, HMAC_SHA256 e' semplicemente un hash hmac secondo specifiche classiche, volevo tenere il KDF piu' leggero possibile proprio perche' cosi' posso generare uno stream abbastanza lungo da criptare anche qualche MB di messaggio senza starci troppo. Alla fine dove si perde tempo qua e' solo con il PBKDF2 ma essendo al login e' accettabile anche con 100k iterazioni se ci sta un paio di secondi. Ok quindi penso di poterla usare, non cifrero' il mio numero di carta di credito ma comunque per fare una chat e scambiare dei file non mi sembra male.

Aspetto domani per segnare risolto in caso viene qualche idea per "rompere" l'algoritmo
Attenzione che quella roba che hai chiamato MyHKDF non è una KDF, è la struttura dello stream cipher a blocchi (una variante di CTR) ed è anche piuttosto costosa. Gli algoritmi veri quella roba li la fanno in modo molto più semplice (per la CPU) perché la sicurezza non è tutto... c'è anche l'efficienza e la power consumption, anche perché non necessariamente vengono usate su PC, smartphone o device con grandi risorse hardware. La tua KDF sarà PBKDF2.
 
Attenzione che quella roba che hai chiamato MyHKDF non è una KDF, è la struttura dello stream cipher a blocchi (una variante di CTR) ed è anche piuttosto costosa. Gli algoritmi veri quella roba li la fanno in modo molto più semplice (per la CPU) perché la sicurezza non è tutto... c'è anche l'efficienza e la power consumption, anche perché non necessariamente vengono usate su PC, smartphone o device con grandi risorse hardware. La tua KDF sarà PBKDF2.

Si, non e' proprio una KDF ma solo per l'uso che ne faccio, non e' costosissimo fare 2 hash sha256 ogni 32 byte, con 32k di hash faccio 1 MB, sta ben sotto il secondo, certo che con AES con ottimizzazione hardware non c'e' paragone in velocita'.
 
Stato
Discussione chiusa ad ulteriori risposte.