python - tutorial - todo sobre elasticsearch
¿Cómo descifrar los archivos cifrados AES de OpenSSL en Python? (5)
OpenSSL proporciona una interfaz de línea de comando popular (pero insegura - ¡vea abajo!) Para el cifrado AES:
openssl aes-256-cbc -salt -in filename -out filename.enc
Python tiene soporte para AES en la forma del paquete PyCrypto, pero solo proporciona las herramientas. ¿Cómo usar Python / PyCrypto para descifrar archivos que han sido encriptados usando OpenSSL?
darse cuenta
Esta pregunta también se refería al cifrado en Python utilizando el mismo esquema. Desde entonces, he eliminado esa parte para desalentar a cualquiera de usarla. NO cifre más datos de esta forma, ya que NO está seguro para los estándares actuales. ÚNICAMENTE debe utilizar el descifrado, sin más razones que la COMPATIBILIDAD RETROCESO, es decir, cuando no tiene otra opción. ¿Quieres encriptar? Usa NaCl / libsodium si puedes.
Nota: este método no es compatible con OpenSSL
Pero es adecuado si todo lo que quiere hacer es cifrar y descifrar archivos.
Una auto respuesta que copié desde here . Creo que esta es, quizás, una opción más simple y más segura. Aunque me interesaría una opinión experta sobre cuán seguro es.
Utilicé Python 3.6 y SimpleCrypt para encriptar el archivo y luego lo cargué.
Creo que este es el código que utilicé para encriptar el archivo:
from simplecrypt import encrypt, decrypt
f = open(''file.csv'',''r'').read()
ciphertext = encrypt(''USERPASSWORD'',f.encode(''utf8'')) # I am not certain of whether I used the .encode(''utf8'')
e = open(''file.enc'',''wb'') # file.enc doesn''t need to exist, python will create it
e.write(ciphertext)
e.close
Este es el código que uso para descifrar en tiempo de ejecución, ejecuto getpass("password: ")
como argumento para no tener que almacenar una variable de password
en la memoria
from simplecrypt import encrypt, decrypt
from getpass import getpass
# opens the file
f = open(''file.enc'',''rb'').read()
print(''Please enter the password and press the enter key /n Decryption may take some time'')
# Decrypts the data, requires a user-input password
plaintext = decrypt(getpass("password: "), f).decode(''utf8'')
print(''Data have been Decrypted'')
Tenga en cuenta que el comportamiento de codificación UTF-8 es diferente en Python 2.7, por lo que el código será ligeramente diferente.
Dada la popularidad de Python, al principio me decepcionó que no hubiera una respuesta completa a esta pregunta. Me llevó bastante leer diferentes respuestas en este foro, así como otros recursos, para hacerlo bien. Pensé que podría compartir el resultado para referencia futura y tal vez revisión; ¡De ninguna manera soy un experto en criptografía! Sin embargo, el siguiente código parece funcionar sin problemas:
from hashlib import md5
from Crypto.Cipher import AES
from Crypto import Random
def derive_key_and_iv(password, salt, key_length, iv_length):
d = d_i = ''''
while len(d) < key_length + iv_length:
d_i = md5(d_i + password + salt).digest()
d += d_i
return d[:key_length], d[key_length:key_length+iv_length]
def decrypt(in_file, out_file, password, key_length=32):
bs = AES.block_size
salt = in_file.read(bs)[len(''Salted__''):]
key, iv = derive_key_and_iv(password, salt, key_length, bs)
cipher = AES.new(key, AES.MODE_CBC, iv)
next_chunk = ''''
finished = False
while not finished:
chunk, next_chunk = next_chunk, cipher.decrypt(in_file.read(1024 * bs))
if len(next_chunk) == 0:
padding_length = ord(chunk[-1])
chunk = chunk[:-padding_length]
finished = True
out_file.write(chunk)
Uso:
with open(in_filename, ''rb'') as in_file, open(out_filename, ''wb'') as out_file:
decrypt(in_file, out_file, password)
Si ve la posibilidad de mejorar esto o ampliarlo para ser más flexible (por ejemplo, hacer que funcione sin sal, o proporcionar compatibilidad con Python 3), no dude en hacerlo.
darse cuenta
Esta respuesta solía también referirse al cifrado en Python usando el mismo esquema. Desde entonces, he eliminado esa parte para desalentar a cualquiera de usarla. NO cifre más datos de esta forma, ya que NO está seguro para los estándares actuales. ÚNICAMENTE debe utilizar el descifrado, sin más razones que la COMPATIBILIDAD RETROCESO, es decir, cuando no tiene otra opción. ¿Quieres encriptar? Usa NaCl / libsodium si puedes.
El siguiente código debe ser compatible con Python 3 con los pequeños cambios documentados en el código. También quería usar os.urandom en lugar de Crypto.Random. ''Salted__'' se reemplaza con salt_header que se puede personalizar o dejar en blanco si es necesario.
from os import urandom
from hashlib import md5
from Crypto.Cipher import AES
def derive_key_and_iv(password, salt, key_length, iv_length):
d = d_i = b'''' # changed '''' to b''''
while len(d) < key_length + iv_length:
# changed password to str.encode(password)
d_i = md5(d_i + str.encode(password) + salt).digest()
d += d_i
return d[:key_length], d[key_length:key_length+iv_length]
def encrypt(in_file, out_file, password, salt_header='''', key_length=32):
# added salt_header=''''
bs = AES.block_size
# replaced Crypt.Random with os.urandom
salt = urandom(bs - len(salt_header))
key, iv = derive_key_and_iv(password, salt, key_length, bs)
cipher = AES.new(key, AES.MODE_CBC, iv)
# changed ''Salted__'' to str.encode(salt_header)
out_file.write(str.encode(salt_header) + salt)
finished = False
while not finished:
chunk = in_file.read(1024 * bs)
if len(chunk) == 0 or len(chunk) % bs != 0:
padding_length = (bs - len(chunk) % bs) or bs
# changed right side to str.encode(...)
chunk += str.encode(
padding_length * chr(padding_length))
finished = True
out_file.write(cipher.encrypt(chunk))
def decrypt(in_file, out_file, password, salt_header='''', key_length=32):
# added salt_header=''''
bs = AES.block_size
# changed ''Salted__'' to salt_header
salt = in_file.read(bs)[len(salt_header):]
key, iv = derive_key_and_iv(password, salt, key_length, bs)
cipher = AES.new(key, AES.MODE_CBC, iv)
next_chunk = ''''
finished = False
while not finished:
chunk, next_chunk = next_chunk, cipher.decrypt(
in_file.read(1024 * bs))
if len(next_chunk) == 0:
padding_length = chunk[-1] # removed ord(...) as unnecessary
chunk = chunk[:-padding_length]
finished = True
out_file.write(bytes(x for x in chunk)) # changed chunk to bytes(...)
Estoy volviendo a publicar tu código con un par de correcciones (no quería ocultar tu versión). Mientras tu código funciona, no detecta algunos errores en el relleno. En particular, si la clave de descifrado proporcionada es incorrecta, su lógica de relleno puede hacer algo extraño. Si está de acuerdo con mi cambio, puede actualizar su solución.
from hashlib import md5
from Crypto.Cipher import AES
from Crypto import Random
def derive_key_and_iv(password, salt, key_length, iv_length):
d = d_i = ''''
while len(d) < key_length + iv_length:
d_i = md5(d_i + password + salt).digest()
d += d_i
return d[:key_length], d[key_length:key_length+iv_length]
# This encryption mode is no longer secure by today''s standards.
# See note in original question above.
def obsolete_encrypt(in_file, out_file, password, key_length=32):
bs = AES.block_size
salt = Random.new().read(bs - len(''Salted__''))
key, iv = derive_key_and_iv(password, salt, key_length, bs)
cipher = AES.new(key, AES.MODE_CBC, iv)
out_file.write(''Salted__'' + salt)
finished = False
while not finished:
chunk = in_file.read(1024 * bs)
if len(chunk) == 0 or len(chunk) % bs != 0:
padding_length = bs - (len(chunk) % bs)
chunk += padding_length * chr(padding_length)
finished = True
out_file.write(cipher.encrypt(chunk))
def decrypt(in_file, out_file, password, key_length=32):
bs = AES.block_size
salt = in_file.read(bs)[len(''Salted__''):]
key, iv = derive_key_and_iv(password, salt, key_length, bs)
cipher = AES.new(key, AES.MODE_CBC, iv)
next_chunk = ''''
finished = False
while not finished:
chunk, next_chunk = next_chunk, cipher.decrypt(in_file.read(1024 * bs))
if len(next_chunk) == 0:
padding_length = ord(chunk[-1])
if padding_length < 1 or padding_length > bs:
raise ValueError("bad decrypt pad (%d)" % padding_length)
# all the pad-bytes must be the same
if chunk[-padding_length:] != (padding_length * chr(padding_length)):
# this is similar to the bad decrypt:evp_enc.c from openssl program
raise ValueError("bad decrypt")
chunk = chunk[:-padding_length]
finished = True
out_file.write(chunk)
Sé que esto es un poco tarde, pero esta es una solución que blogueé en 2013 sobre cómo usar el paquete python python para cifrar / descifrar de una manera compatible con openssl. Ha sido probado en python2.7 y python3.x. El código fuente y un script de prueba se pueden encontrar here .
Una de las principales diferencias entre esta solución y las excelentes soluciones presentadas anteriormente es que diferencia entre E / S de canal y de archivo, lo que puede causar problemas en algunas aplicaciones.
Las funciones clave de ese blog se muestran a continuación.
# ================================================================
# get_key_and_iv
# ================================================================
def get_key_and_iv(password, salt, klen=32, ilen=16, msgdgst=''md5''):
''''''
Derive the key and the IV from the given password and salt.
This is a niftier implementation than my direct transliteration of
the C++ code although I modified to support different digests.
CITATION: http://.com/questions/13907841/implement-openssl-aes-encryption-in-python
@param password The password to use as the seed.
@param salt The salt.
@param klen The key length.
@param ilen The initialization vector length.
@param msgdgst The message digest algorithm to use.
''''''
# equivalent to:
# from hashlib import <mdi> as mdf
# from hashlib import md5 as mdf
# from hashlib import sha512 as mdf
mdf = getattr(__import__(''hashlib'', fromlist=[msgdgst]), msgdgst)
password = password.encode(''ascii'', ''ignore'') # convert to ASCII
try:
maxlen = klen + ilen
keyiv = mdf(password + salt).digest()
tmp = [keyiv]
while len(tmp) < maxlen:
tmp.append( mdf(tmp[-1] + password + salt).digest() )
keyiv += tmp[-1] # append the last byte
key = keyiv[:klen]
iv = keyiv[klen:klen+ilen]
return key, iv
except UnicodeDecodeError:
return None, None
# ================================================================
# encrypt
# ================================================================
def encrypt(password, plaintext, chunkit=True, msgdgst=''md5''):
''''''
Encrypt the plaintext using the password using an openssl
compatible encryption algorithm. It is the same as creating a file
with plaintext contents and running openssl like this:
$ cat plaintext
<plaintext>
$ openssl enc -e -aes-256-cbc -base64 -salt //
-pass pass:<password> -n plaintext
@param password The password.
@param plaintext The plaintext to encrypt.
@param chunkit Flag that tells encrypt to split the ciphertext
into 64 character (MIME encoded) lines.
This does not affect the decrypt operation.
@param msgdgst The message digest algorithm.
''''''
salt = os.urandom(8)
key, iv = get_key_and_iv(password, salt, msgdgst=msgdgst)
if key is None:
return None
# PKCS#7 padding
padding_len = 16 - (len(plaintext) % 16)
if isinstance(plaintext, str):
padded_plaintext = plaintext + (chr(padding_len) * padding_len)
else: # assume bytes
padded_plaintext = plaintext + (bytearray([padding_len] * padding_len))
# Encrypt
cipher = AES.new(key, AES.MODE_CBC, iv)
ciphertext = cipher.encrypt(padded_plaintext)
# Make openssl compatible.
# I first discovered this when I wrote the C++ Cipher class.
# CITATION: http://projects.joelinoff.com/cipher-1.1/doxydocs/html/
openssl_ciphertext = b''Salted__'' + salt + ciphertext
b64 = base64.b64encode(openssl_ciphertext)
if not chunkit:
return b64
LINELEN = 64
chunk = lambda s: b''/n''.join(s[i:min(i+LINELEN, len(s))]
for i in range(0, len(s), LINELEN))
return chunk(b64)
# ================================================================
# decrypt
# ================================================================
def decrypt(password, ciphertext, msgdgst=''md5''):
''''''
Decrypt the ciphertext using the password using an openssl
compatible decryption algorithm. It is the same as creating a file
with ciphertext contents and running openssl like this:
$ cat ciphertext
# ENCRYPTED
<ciphertext>
$ egrep -v ''^#|^$'' | //
openssl enc -d -aes-256-cbc -base64 -salt -pass pass:<password> -in ciphertext
@param password The password.
@param ciphertext The ciphertext to decrypt.
@param msgdgst The message digest algorithm.
@returns the decrypted data.
''''''
# unfilter -- ignore blank lines and comments
if isinstance(ciphertext, str):
filtered = ''''
nl = ''/n''
re1 = r''^/s*$''
re2 = r''^/s*#''
else:
filtered = b''''
nl = b''/n''
re1 = b''^//s*$''
re2 = b''^//s*#''
for line in ciphertext.split(nl):
line = line.strip()
if re.search(re1,line) or re.search(re2, line):
continue
filtered += line + nl
# Base64 decode
raw = base64.b64decode(filtered)
assert(raw[:8] == b''Salted__'' )
salt = raw[8:16] # get the salt
# Now create the key and iv.
key, iv = get_key_and_iv(password, salt, msgdgst=msgdgst)
if key is None:
return None
# The original ciphertext
ciphertext = raw[16:]
# Decrypt
cipher = AES.new(key, AES.MODE_CBC, iv)
padded_plaintext = cipher.decrypt(ciphertext)
if isinstance(padded_plaintext, str):
padding_len = ord(padded_plaintext[-1])
else:
padding_len = padded_plaintext[-1]
plaintext = padded_plaintext[:-padding_len]
return plaintext