Sicurezza della chiave bcrypt / sha256 utilizzata con AES per crittografare un file

6

Sto cercando di crittografare i file usando algoritmi di hashing e crittografia sicuri in Python. Avendo usato bcrypt in passato, ho deciso di usarlo per il mio calcolatore passphrase, quindi passare l'output tramite SHA256 per ottenere i miei 32 byte di dati, quindi usarlo con AES per crittografare / decodificare un file:

#!/usr/bin/env python

from argparse import ArgumentParser
from bcrypt import gensalt, hashpw
from Crypto.Cipher import AES
from hashlib import sha256

import os, struct, sys

def main():
    parser = ArgumentParser(description = "Encrypts or decrypts a file using " +
            "bcrypt for the password and triple AES for file encryption.")
    parser.add_argument('-p', '--passphrase', required = True, 
            help = "The passphrase to use for encryption.")
    parser.add_argument('-i', '--input', required = True,
            help = "The input file for encryption / decryption.")
    parser.add_argument('-o', '--output', required = True,
            help = "The output file for encryption / decryption.")
    parser.add_argument('-r', '--rounds', default = 10, 
            help = "The number of bcrypt rounds to use.")
    parser.add_argument('-s', '--salt', default = None,
            help = "The salt to use with bcrypt in decryption.")
    parser.add_argument('operation', choices = ('encrypt', 'decrypt'),
            help = "The operation to apply, whether to encrypt or decrypt data.")

    parameters = parser.parse_args()

    if parameters.operation == 'encrypt':
        encrypt(parameters.input, parameters.output, parameters.passphrase,
                parameters.rounds)
    elif parameters.operation == 'decrypt':
        decrypt(parameters.input, parameters.output, parameters.passphrase,
                parameters.salt)

def encrypt(input_file, output_file, passphrase, rounds):
    bcrypt_salt = gensalt(rounds)
    bcrypt_passphrase = hashpw(passphrase, bcrypt_salt)
    passphrase_hash = sha256(bcrypt_passphrase).digest()

    print "Salt: %s" % (bcrypt_salt, )

    iv = os.urandom(16)

    cipher = AES.new(passphrase_hash, AES.MODE_CBC, iv)

    with open(input_file, 'rb') as infile:
        infile.seek(0, 2)
        input_size = infile.tell()

        infile.seek(0)  

        with open(output_file, 'wb') as outfile:
            outfile.write(struct.pack('<Q', input_size))
            outfile.write(iv)

            while True:
                chunk = infile.read(64 * 1024)
                if len(chunk) == 0:
                    break
                elif len(chunk) % 16 != 0:
                    chunk += ' ' * (16 - len(chunk) % 16)

                outfile.write(cipher.encrypt(chunk))

    return bcrypt_salt

def decrypt(input_file, output_file, passphrase, salt):
    print "Salt: %s" % (salt,)

    bcrypt_passphrase = hashpw(passphrase, salt)
    passphrase_hash = sha256(bcrypt_passphrase).digest()

    with open(input_file, 'rb') as infile:
        input_size = struct.unpack('<Q', infile.read(struct.calcsize('Q')))[0]
        iv = infile.read(16)

        cipher = AES.new(passphrase_hash, AES.MODE_CBC, iv)

        with open(output_file, 'wb') as outfile:
            while True:
                chunk = infile.read(64 * 1024)
                if len(chunk) == 0:
                    break

                outfile.write(cipher.decrypt(chunk))

            outfile.truncate(input_size)

if __name__ == "__main__":
    main()

Quali sono i possibili punti deboli di un'implementazione come questa?

Quello che ho determinato è che sarebbe facile per un utente malintenzionato determinare la dimensione del file originale, tuttavia questo non rivela molto sul file. SHA-256 non è il miglior algoritmo di hashing al mondo, ma il wrapping di una password bcrypt mi porterebbe a credere che tutte le minacce sarebbero mitigate lì. A causa della capacità di bcrypt di aumentare la sicurezza nel tempo aggiungendo ulteriori round all'algoritmo, sembra una scommessa abbastanza sicura usare bcrypt per ora.

Ci sono lacune in questa implementazione? Non sono un crittografo, ma conosco le basi e gli scopi di ciascuno dei tre algoritmi utilizzati qui.

    
posta Naftuli Kay 22.01.2013 - 05:03
fonte

1 risposta

9

Da un'occhiata per due minuti:

  • Usare bcrypt è suono. SHA-256 è una delle funzioni di hash più conosciute (perché è in circolazione da più di 10 anni, ampiamente distribuita e non danneggiata).

  • Poiché si genera un nuovo salt per ogni file crittografato (e questo è buono!), avrebbe senso memorizzarlo in un'intestazione di file. In questo modo, non è necessario fornirlo come parametro per la routine di decodifica. Un sale non deve essere segreto (è un salt , non un key ) ma deve essere generato un nuovo salt per ogni crittografia, quindi la gestione esterna potrebbe essere ingombrante. Hai già un'intestazione personalizzata per IV, puoi fare lo stesso per il sale.

    Alternativa: memorizza salt come intestazione e genera entrambi chiave di crittografia e IV da bcrypt output (dividi SHA-256 output in due blocchi di 128 bit; il primo è la chiave, il secondo è IV). Ciò manterrebbe la dimensione dell'intestazione a 16 byte. Ma un IV casuale (da os.urandom() , come fai tu) non è male neanche.

  • Utilizzi la crittografia ma non c'è MAC . Non ci sono molti modelli di attacco in cui l'attaccante può osservare i dati (cioè è necessario riservatezza ) ma non alterare i dati (cioè non è necessario integrità ). In pratica, sarebbe meglio aggiungere un MAC. Idealmente, sostituisci CBC con una modalità che gestisce sia la crittografia sia l'integrità ( GCM , EAX ).

    Se la tua libreria di supporto non fornisce tale modalità, dovrai farlo "vecchio stile" combinando la crittografia con HMAC . Questo è non del tutto banale da fare . La migliore pratica sarebbe quella di generare una chiave MAC extra dall'output di bcrypt (se produci la chiave di crittografia, la IV e la chiave MAC, avrai bisogno di una funzione hash più grande, ad es. SHA-512) e calcoli il MAC sulla concatenazione della IV e dei dati criptati (si può saltare la IV solo se la si genera dalla passphrase, ma è più sicuro includerla nell'input MAC). Al momento della decrittografia, verificare prima il MAC, quindi procedere alla decifratura solo se il MAC corrisponde.

  • potresti voler includere alcune disposizioni per l'agilità dell'algoritmo, ovvero aggiungere nell'intestazione un valore di byte che identifica la combinazione di algoritmi che usi (bcrypt, SHA-256, AES + CBC , HMAC / SHA-256). Ciò consentirebbe di passare in seguito a un altro insieme di algoritmi senza rompere la compatibilità del codice con i file esistenti. In tal caso, non dimenticare di aggiungere questo "byte di identificatore dell'algoritmo" all'ingresso per il MAC.

  • Il tuo padding è ambiguo: alla decrittazione troverai alcuni caratteri spaziali alla fine, ma non saprai se gli spazi extra facevano parte del file di input o aggiunti dalla funzione di crittografia. E, in effetti, dopo la decifratura, lasci gli spazi. Questo potrebbe non essere un problema per il tuo specifico scenario di utilizzo, ma, come strumento generale di crittografia / decrittografia, questo è solitamente considerato un problema se i file non sopravvivono a un viaggio di crittografia / decodifica senza modifiche. Padding PKCS # 7 (noto anche come riempimento PKCS # 5) è comunemente usato: aggiungi k byte (almeno 1, massimo 16, in modo che la lunghezza totale sia un multiplo di 16) e tutti i byte abbiano valore k . Dopo la decrittazione, guarda l'ultimo byte, che ti indicherà quanti byte di padding sono stati aggiunti.

risposta data 22.01.2013 - 12:44
fonte

Leggi altre domande sui tag