Implement OpenSSL AES Encryption in Python

PizzaPanther picture PizzaPanther · Dec 17, 2012 · Viewed 8k times · Source

I'm trying to implement the following in Python: openssl enc -e -aes-256-cbc -base64 -k "Secret Passphrase" -in plaintext.txt -out ciphertext.txt

openssl enc -d -aes-256-cbc -base64 -k "Secret Passphrase" -in ciphertext.txt -out verification.txt

I've tried several different modules, PyCrypto, M2Crypto, etc but can't seem to get the correct combination of changing the password to the right size key and encoding everything correctly. I've found https://github.com/nvie/SimpleAES but that basically runs OpenSSL on the command line, which I'd rather avoid.

Answer

SquareRootOfTwentyThree picture SquareRootOfTwentyThree · Dec 17, 2012

Base 64 encoding and decoding can be easily handled via the standard base64 module.

AES-256 decryption and encryption in CBC mode are supported by both PyCrypto and M2Crypto.

The only non-standard (and most difficult) part is the derivation of the IV and the key from the password. OpenSSL does it via its own EVP_BytesToKey function, which is described in this man page.

The Python equivalent is:

def EVP_BytesToKey(password, salt, key_len, iv_len):
    """
    Derive the key and the IV from the given password and salt.
    """
    from hashlib import md5
    dtot =  md5(password + salt).digest()
    d = [ dtot ]
    while len(dtot)<(iv_len+key_len):
        d.append( md5(d[-1] + password + salt).digest() )
        dtot += d[-1]
    return dtot[:key_len], dtot[key_len:key_len+iv_len]

where key_len is 32 and iv_len is 16 for AES-256. The function returns the key and the IV which you can use to decrypt the payload.

OpenSSL puts and expects the salt in the first 8 bytes of the encrypted payload.

Finally, AES in CBC mode can only work with data aligned to the 16 byte boundary. The default padding used is PKCS#7.

The steps for encrypting are therefore:

  1. Generate 8 bytes of random data as salt.
  2. Derive AES key and IV from password using the salt from step 1.
  3. Pad the input data with PKCS#7.
  4. Encrypt the padded using AES-256 in CBC mode with the key and the IV from step 2.
  5. Encode in Base64 and output the salt from step 1.
  6. Encode in Base64 and output the encrypted data from step 4.

The steps from decrypting are the reverse:

  1. Decode the input data from Base64 into a binary string.
  2. Treat the first 8 bytes of the decoded data as salt.
  3. Derive AES key and IV from password using the salt from step 1.
  4. Decrypt the remaining decoded data using the AES key and the IV from step 3.
  5. Verify and remove the PKCS#7 padding from the result.