Me gustaría descifrar una cadena cifrada con AES (modo CCM) en Python 3.
El siguiente código JavaScript que utiliza la biblioteca sjcl funciona correctamente:
const sjcl = require('sjcl'); const key = "ef530e1d82c154170296467bfe40cdb47b9ad77e685bbf8336b145dfa0e85640"; const keyArray = sjcl.codec.hex.toBits(key); const iv = sjcl.codec.base64.fromBits(sjcl.codec.hex.toBits(key.substr(0,16))); const params = { "iv": iv, "v": 1, "iter": 1000, "ks": 256, "ts": 128, "mode": "ccm", "adata": "", "cipher": "aes", "salt": "", }; function encrypt(data) { const ct = JSON.parse(sjcl.encrypt(keyArray, data, params)).ct; return sjcl.codec.hex.fromBits(sjcl.codec.base64.toBits(ct)); } function decrypt(data) { const ct = sjcl.codec.base64.fromBits(sjcl.codec.hex.toBits(data)); const paramsWithCt = JSON.stringify({ ...params, ...{ "ct": ct } }); return sjcl.decrypt(keyArray, paramsWithCt); } let ct = encrypt("my secret string"); console.log("Cipher Text: " + ct); let plain = decrypt(ct); console.log("Plain Text: " + plain);Producción:
$ npm i sjcl $ node index.js Cipher Text: fa90bcdedbfe7ba89b69216e352a90fa57a63871fc4da7e69ab7f897f427f8e3 Plain Text: my secret string¿Qué biblioteca puedo usar para hacer lo mismo en Python?
Intenté usar la biblioteca pycryptodome , pero acepta un conjunto diferente de parámetros:
El sjcl opera en arreglos de palabras de 4 bytes. Con sjcl.codec.hex.toBits() la clave codificada hexadecimal se convierte en una matriz de este tipo. Los primeros 8 bytes (16 dígitos hexadecimales) de la clave se utilizan como nonce.
El tamaño de la clave, el tamaño de la etiqueta, el algoritmo y el modo se determinan a partir del objeto params . El objeto params contiene además parámetros para la derivación de la clave, por ejemplo, iter , salt , etc.), pero estos se ignoran aquí ya que la clave se pasa como una matriz y no como una cadena.
Nonce y el texto cifrado se pasan codificados en Base64 dentro del objeto params .
El texto cifrado es la concatenación del texto cifrado real y la etiqueta en este orden, que también debe pasarse al descifrado en este formato.
Mientras que sjcl procesa el texto cifrado y la etiqueta concatenada, PyCryptodome maneja ambos por separado. Aparte de eso, el cifrado y descifrado en Python es sencillo con PyCryptodome :
from Crypto.Cipher import AES data = b'my secret string' key = bytes.fromhex('ef530e1d82c154170296467bfe40cdb47b9ad77e685bbf8336b145dfa0e85640') nonce = bytes.fromhex('ef530e1d82c154170296467bfe40cdb47b9ad77e685bbf8336b145dfa0e85640')[:8] # Encryption cipher = AES.new(key, AES.MODE_CCM, nonce) ciphertext, tag = cipher.encrypt_and_digest(data) ciphertextTagHex = ciphertext.hex() + tag.hex() print(ciphertextTagHex) # fa90bcdedbfe7ba89b69216e352a90fa57a63871fc4da7e69ab7f897f427f8e3 # Decryption ciphertextTag = bytes.fromhex(ciphertextTagHex) ciphertext = ciphertextTag[:-16] tag = ciphertextTag[-16:] cipher = AES.new(key, AES.MODE_CCM, nonce) try: decrypted = cipher.decrypt_and_verify(ciphertext, tag) print(decrypted.decode('utf-8')) # my secret string except ValueError: print('Decryption failed')Tenga en cuenta que es inseguro derivar el nonce de la clave. Esto es especialmente cierto para CCM, sec RFC4309, p. 3, última sección :
AES CCM emplea el modo contador para el cifrado. Al igual que con cualquier cifrado de flujo, la reutilización del mismo valor IV con la misma clave es catastrófica.
En cambio, el nonce debe generarse aleatoriamente para cada cifrado. El nonce no es secreto y generalmente se concatena con el texto cifrado a nivel de byte, normalmente nonce|ciphertext|tag .