Python Language
Sicurezza e crittografia
Ricerca…
introduzione
Python, essendo uno dei linguaggi più popolari nella sicurezza di computer e di rete, ha un grande potenziale in sicurezza e crittografia. Questo argomento tratta le funzionalità e le implementazioni crittografiche in Python dai suoi usi in sicurezza di computer e di rete a algoritmi di hashing e crittografia / decrittografia.
Sintassi
- hashlib.new (nome)
- hashlib.pbkdf2_hmac (nome, password, sale, colpi, dklen = Nessuno)
Osservazioni
Molti dei metodi in hashlib
richiedono il passaggio di valori interpretabili come buffer di byte anziché stringhe. Questo è il caso di hashlib.new().update()
e di hashlib.pbkdf2_hmac
. Se si dispone di una stringa, è possibile convertirla in un buffer di byte anteponendo il carattere b
all'inizio della stringa:
"This is a string"
b"This is a buffer of bytes"
Calcolo di un digest di messaggi
Il modulo hashlib
consente di creare generatori di digest di messaggi tramite il new
metodo. Questi generatori trasformeranno una stringa arbitraria in un digest a lunghezza fissa:
import hashlib
h = hashlib.new('sha256')
h.update(b'Nobody expects the Spanish Inquisition.')
h.digest()
# ==> b'.\xdf\xda\xdaVR[\x12\x90\xff\x16\xfb\x17D\xcf\xb4\x82\xdd)\x14\xff\xbc\xb6Iy\x0c\x0eX\x9eF-='
Nota che puoi chiamare update
un numero arbitrario di volte prima di chiamare digest
che è utile per cancellare un grosso file di blocco. Puoi anche ottenere il digest in formato esadecimale usando hexdigest
:
h.hexdigest()
# ==> '2edfdada56525b1290ff16fb1744cfb482dd2914ffbcb649790c0e589e462d3d'
Algoritmi di hash disponibili
hashlib.new
richiede il nome di un algoritmo quando lo chiami per produrre un generatore. Per scoprire quali algoritmi sono disponibili nell'attuale interprete Python, usa hashlib.algorithms_available
:
import hashlib
hashlib.algorithms_available
# ==> {'sha256', 'DSA-SHA', 'SHA512', 'SHA224', 'dsaWithSHA', 'SHA', 'RIPEMD160', 'ecdsa-with-SHA1', 'sha1', 'SHA384', 'md5', 'SHA1', 'MD5', 'MD4', 'SHA256', 'sha384', 'md4', 'ripemd160', 'sha224', 'sha512', 'DSA', 'dsaEncryption', 'sha', 'whirlpool'}
L'elenco restituito varierà in base alla piattaforma e all'interprete; assicurati di controllare che l'algoritmo sia disponibile.
Ci sono anche alcuni algoritmi che sono garantiti per essere disponibili su tutte le piattaforme e gli interpreti, che sono disponibili usando hashlib.algorithms_guaranteed
:
hashlib.algorithms_guaranteed
# ==> {'sha256', 'sha384', 'sha1', 'sha224', 'md5', 'sha512'}
Hash password sicura
L' algoritmo PBKDF2 esposto dal modulo hashlib
può essere utilizzato per eseguire l'hashing della password sicura. Sebbene questo algoritmo non possa impedire attacchi di forza bruta per recuperare la password originale dall'hash memorizzato, rende tali attacchi molto costosi.
import hashlib
import os
salt = os.urandom(16)
hash = hashlib.pbkdf2_hmac('sha256', b'password', salt, 100000)
PBKDF2 può funzionare con qualsiasi algoritmo di digest, l'esempio precedente usa SHA256 che è generalmente raccomandato. Il sale casuale deve essere memorizzato insieme alla password con hash, sarà necessario nuovamente per confrontare una password inserita con l'hash memorizzato. È essenziale che ogni password sia sottoposta a hash con un diverso salt. Per quanto riguarda il numero di round, si consiglia di impostarlo il più alto possibile per la tua applicazione .
Se si desidera il risultato in formato esadecimale, è possibile utilizzare il modulo binascii
:
import binascii
hexhash = binascii.hexlify(hash)
Nota : mentre PBKDF2 non è male, bcrypt e soprattutto scrypt sono considerati più forti contro gli attacchi a forza bruta. Al momento non fanno parte della libreria standard Python.
File Hashing
Un hash è una funzione che converte una sequenza di lunghezza variabile di byte in una sequenza di lunghezza fissa. I file di hash possono essere vantaggiosi per molte ragioni. Gli hash possono essere usati per verificare se due file sono identici o per verificare che il contenuto di un file non sia stato corrotto o modificato.
È possibile utilizzare hashlib
per generare un hash per un file:
import hashlib
hasher = hashlib.new('sha256')
with open('myfile', 'r') as f:
contents = f.read()
hasher.update(contents)
print hasher.hexdigest()
Per i file più grandi, è possibile utilizzare un buffer di lunghezza fissa:
import hashlib
SIZE = 65536
hasher = hashlib.new('sha256')
with open('myfile', 'r') as f:
buffer = f.read(SIZE)
while len(buffer) > 0:
hasher.update(buffer)
buffer = f.read(SIZE)
print(hasher.hexdigest())
Crittografia simmetrica usando pycrypto
La funzionalità crittografica incorporata di Python è attualmente limitata all'hashing. La crittografia richiede un modulo di terze parti come pycrypto . Ad esempio, fornisce l' algoritmo AES che è considerato lo stato dell'arte per la crittografia simmetrica. Il seguente codice crittograferà un dato messaggio usando una passphrase:
import hashlib
import math
import os
from Crypto.Cipher import AES
IV_SIZE = 16 # 128 bit, fixed for the AES algorithm
KEY_SIZE = 32 # 256 bit meaning AES-256, can also be 128 or 192 bits
SALT_SIZE = 16 # This size is arbitrary
cleartext = b'Lorem ipsum'
password = b'highly secure encryption password'
salt = os.urandom(SALT_SIZE)
derived = hashlib.pbkdf2_hmac('sha256', password, salt, 100000,
dklen=IV_SIZE + KEY_SIZE)
iv = derived[0:IV_SIZE]
key = derived[IV_SIZE:]
encrypted = salt + AES.new(key, AES.MODE_CFB, iv).encrypt(cleartext)
L'algoritmo AES accetta tre parametri: chiave di crittografia, vettore di inizializzazione (IV) e il messaggio effettivo da crittografare. Se si dispone di una chiave AES generata casualmente, è possibile utilizzarla direttamente e generare semplicemente un vettore di inizializzazione casuale. Tuttavia una passphrase non ha le dimensioni giuste, né sarebbe raccomandabile utilizzarla direttamente dato che non è veramente casuale e quindi ha un'entropia relativamente piccola. Al contrario, utilizziamo l' implementazione incorporata dell'algoritmo PBKDF2 per generare un vettore di inizializzazione a 128 bit e una chiave di crittografia a 256 bit dalla password.
Si noti il sale casuale che è importante avere un vettore di inizializzazione e una chiave diversi per ogni messaggio crittografato. Ciò garantisce in particolare che due messaggi uguali non determinino un testo crittografato identico, ma impedisce anche agli autori degli attacchi di riutilizzare il lavoro trascorso a indovinare una passphrase sui messaggi crittografati con un'altra passphrase. Questo sale deve essere memorizzato insieme al messaggio crittografato per ricavare lo stesso vettore di inizializzazione e la chiave per decrittografare.
Il seguente codice decodificherà nuovamente il nostro messaggio:
salt = encrypted[0:SALT_SIZE]
derived = hashlib.pbkdf2_hmac('sha256', password, salt, 100000,
dklen=IV_SIZE + KEY_SIZE)
iv = derived[0:IV_SIZE]
key = derived[IV_SIZE:]
cleartext = AES.new(key, AES.MODE_CFB, iv).decrypt(encrypted[SALT_SIZE:])
Generazione di firme RSA usando pycrypto
RSA può essere utilizzato per creare una firma del messaggio. Una firma valida può essere generata solo con l'accesso alla chiave RSA privata, la convalida d'altra parte è possibile solo con la chiave pubblica corrispondente. Quindi, se l'altra parte conosce la tua chiave pubblica, può verificare che il messaggio sia firmato da te e invariato, ad esempio un approccio utilizzato per la posta elettronica. Attualmente, per questa funzionalità è richiesto un modulo di terze parti come pycrypto .
import errno
from Crypto.Hash import SHA256
from Crypto.PublicKey import RSA
from Crypto.Signature import PKCS1_v1_5
message = b'This message is from me, I promise.'
try:
with open('privkey.pem', 'r') as f:
key = RSA.importKey(f.read())
except IOError as e:
if e.errno != errno.ENOENT:
raise
# No private key, generate a new one. This can take a few seconds.
key = RSA.generate(4096)
with open('privkey.pem', 'wb') as f:
f.write(key.exportKey('PEM'))
with open('pubkey.pem', 'wb') as f:
f.write(key.publickey().exportKey('PEM'))
hasher = SHA256.new(message)
signer = PKCS1_v1_5.new(key)
signature = signer.sign(hasher)
La verifica della firma funziona in modo simile ma utilizza la chiave pubblica anziché la chiave privata:
with open('pubkey.pem', 'rb') as f:
key = RSA.importKey(f.read())
hasher = SHA256.new(message)
verifier = PKCS1_v1_5.new(key)
if verifier.verify(hasher, signature):
print('Nice, the signature is valid!')
else:
print('No, the message was signed with the wrong private key or modified')
Nota : gli esempi precedenti utilizzano l'algoritmo di firma PKCS # 1 v1.5 che è molto comune. pycrypto implementa anche il nuovo algoritmo PKCS # 1 PSS, sostituendo PKCS1_v1_5
con PKCS1_PSS
negli esempi dovrebbe funzionare se si desidera utilizzare quello. Attualmente sembra che ci siano poche ragioni per usarlo comunque.
Crittografia asimmetrica RSA usando pycrypto
La crittografia asimmetrica ha il vantaggio che un messaggio può essere crittografato senza scambiare una chiave segreta con il destinatario del messaggio. Il mittente ha semplicemente bisogno di conoscere la chiave pubblica dei destinatari, ciò consente di crittografare il messaggio in modo tale che solo il destinatario designato (che ha la chiave privata corrispondente) possa decrittografarlo. Attualmente, per questa funzionalità è richiesto un modulo di terze parti come pycrypto .
from Crypto.Cipher import PKCS1_OAEP
from Crypto.PublicKey import RSA
message = b'This is a very secret message.'
with open('pubkey.pem', 'rb') as f:
key = RSA.importKey(f.read())
cipher = PKCS1_OAEP.new(key)
encrypted = cipher.encrypt(message)
Il destinatario può decifrare il messaggio, se ha la chiave privata giusta:
with open('privkey.pem', 'rb') as f:
key = RSA.importKey(f.read())
cipher = PKCS1_OAEP.new(key)
decrypted = cipher.decrypt(encrypted)
Nota : gli esempi sopra riportati utilizzano lo schema di crittografia OAEP PKCS # 1. pycrypto implementa anche lo schema di crittografia PKCS # 1 v1.5, questo non è raccomandato per i nuovi protocolli, tuttavia a causa di avvertimenti noti .