Encrypt & Decrypt Using Pycrypto Aes 256

Encrypt & Decrypt using PyCrypto AES 256

Here is my implementation and works for me with some fixes and enhances the alignment of the key and secret phrase with 32 bytes and iv to 16 bytes:

import base64
import hashlib
from Crypto import Random
from Crypto.Cipher import AES

class AESCipher(object):

def __init__(self, key):
self.bs = AES.block_size
self.key = hashlib.sha256(key.encode()).digest()

def encrypt(self, raw):
raw = self._pad(raw)
iv = Random.new().read(AES.block_size)
cipher = AES.new(self.key, AES.MODE_CBC, iv)
return base64.b64encode(iv + cipher.encrypt(raw.encode()))

def decrypt(self, enc):
enc = base64.b64decode(enc)
iv = enc[:AES.block_size]
cipher = AES.new(self.key, AES.MODE_CBC, iv)
return self._unpad(cipher.decrypt(enc[AES.block_size:])).decode('utf-8')

def _pad(self, s):
return s + (self.bs - len(s) % self.bs) * chr(self.bs - len(s) % self.bs)

@staticmethod
def _unpad(s):
return s[:-ord(s[len(s)-1:])]

Decrypt a encrypted secret using PyCrypto AES & sha256

The bytes.decrypt() function by default expects an UTF-8 encoded string. But not every sequence of bytes is a valid UTF-8 sequence. In your case cipher.decrypt() (which may return any sequence of bytes) returned a byte-sequence, which is not a valid UTF-8 sequence. Thus the bytes.decode() function raised an error.

The actual reason why cipher.decrypt() returned a non-UTF-8 string is a bug in your code:

Your encrypted file format contains non-utf-8 data. Its format is like:

  • 16 bytes len info (unencrypted, UTF-8 encoded)
  • 16 bytes IV (unencrypted, binary i.e. non-UTF-8 encoded)
  • n bytes payload (encrypted, UTF-8 encoded)

You have to ensure that on decryption you only decode parts of your file, that are UTF-8 encoded. Furthermore you have to ensure that you decrypt only encrypted parts of your file (as mentioned in your comments)

AES encrypt in Cryptojs, decrypt in Pycrypto

I am using PBKDF2 to generate IV and key. This is good practice. We don't need to transfer IV:

Javascript:

let password = "lazydog";

let salt = "salt";

let iterations = 128;

let bytes = CryptoJS.PBKDF2(password, salt, { keySize: 48, iterations: iterations });

let iv = CryptoJS.enc.Hex.parse(bytes.toString().slice(0, 32));

let key = CryptoJS.enc.Hex.parse(bytes.toString().slice(32, 96));

let ciphertext = CryptoJS.AES.encrypt("Attack at dawn", key, { iv: iv });

console.log(ciphertext.toString());
<script src="https://cdnjs.cloudflare.com/ajax/libs/crypto-js/3.1.9-1/crypto-js.js"></script>

Decrypting AES CBC in python from OpenSSL AES

The OpenSSL statement uses PBKDF2 to create a 32 bytes key and a 16 bytes IV. For this, a random 8 bytes salt is implicitly generated and the specified password, iteration count and digest (default: SHA-256) are applied. The key/IV pair is used to encrypt the plaintext with AES-256 in CBC mode and PKCS7 padding, s. here. The result is returned in OpenSSL format, which starts with the 8 bytes ASCII encoding of Salted__, followed by the 8 bytes salt and the actual ciphertext, all Base64 encoded. The salt is needed for decryption, so that key and IV can be reconstructed.

Note that the password in the OpenSSL statement is actually passed without quotation marks, i.e. in the posted OpenSSL statement, the quotation marks are part of the password.

For the decryption in Python the salt and the actual ciphertext must first be determined from the encrypted data. With the salt the key/IV pair can be reconstructed. Finally, the key/IV pair can be used for decryption.

Example: With the posted OpenSSL statement, the plaintext

The quick brown fox jumps over the lazy dog

was encrypted into the ciphertext

U2FsdGVkX18A+AhjLZpfOq2HilY+8MyrXcz3lHMdUII2cud0DnnIcAtomToclwWOtUUnoyTY2qCQQXQfwDYotw== 

Decryption with Python is possible as follows (using PyCryptodome):

from Crypto.Protocol.KDF import PBKDF2
from Crypto.Hash import SHA256
from Crypto.Util.Padding import unpad
from Crypto.Cipher import AES
import base64

# Determine salt and ciphertext
encryptedDataB64 = 'U2FsdGVkX18A+AhjLZpfOq2HilY+8MyrXcz3lHMdUII2cud0DnnIcAtomToclwWOtUUnoyTY2qCQQXQfwDYotw=='
encryptedData = base64.b64decode(encryptedDataB64)
salt = encryptedData[8:16]
ciphertext = encryptedData[16:]

# Reconstruct Key/IV-pair
pbkdf2Hash = PBKDF2(b'"mypassword"', salt, 32 + 16, count=100000, hmac_hash_module=SHA256)
key = pbkdf2Hash[0:32]
iv = pbkdf2Hash[32:32 + 16]

# Decrypt with AES-256 / CBC / PKCS7 Padding
cipher = AES.new(key, AES.MODE_CBC, iv)
decrypted = unpad(cipher.decrypt(ciphertext), 16)

print(decrypted)

Edit - Regarding your comment: 16 MB should be possible, but for larger data the ciphertext would generally be read from a file and the decrypted data would be written to a file, in contrast to the example posted above.

Whether the data can be decrypted in one step ultimately depends on the available memory. If the memory is not sufficient, the data must be processed in chunks.

When using chunks it would make more sense not to Base64 encode the encrypted data but to store them directly in binary format. This is possible by omitting the -a option in the OpenSSL statement. Otherwise it must be ensured that always integer multiples of the block size (relative to the undecoded ciphertext) are loaded, where 3 bytes of the undecoded ciphertext correspond to 4 bytes of the Base64 encoded ciphertext.

In the case of the binary stored ciphertext: During decryption only the first block (16 bytes) should be (binary) read in the first step. From this, the salt can be determined (the bytes 8 to 16), then the key and IV (analogous to the posted code above).

The rest of the ciphertext can be (binary) read in chunks of suitable size ( = a multiple of the block size, e.g. 1024 bytes). Each chunk is encrypted/decrypted separately, see multiple encrypt/decrypt-calls. For reading/writing files in chunks with Python see e.g. here.
Further details are best answered within the scope of a separate question.

AES 256 Encryption with PyCrypto using CBC mode - any weaknesses?

I've seen the code posted on the internet. There are - in principle - not too many things wrong with it, but there is no need to invent your own padding. Furthermore, I don't see why the first padding character is called INTERRUPT. I presume that INTERRUPT and PAD is handled as a single byte (I'm not a Python expert).

The most common padding is PKCS#5 padding. It consists of N bytes with the value of the number of padding bytes. The padding used here looks more like 'ISO' padding, which consists of a single bit set to 1 to distinguish it from the data and other padding bits, and the rest is zero's. That would be code point \u0080 in code.

So the encryption (which can provide confidentiality of data) seems to be used correctly. It depends on the use case if you also need integrity protection and/or authentication, e.g. by using a MAC or HMAC. Of course, no legal guarantees or anything provided.

AES 256 bits CBC PKCS#5 encrypt / decrypt in Python

The following code does what you want. It is basically the highest voted answer of your linked question with the IV set externally.

Python 2.x code (if you want Python 3. x code you have to do the Hex decoding differently):

import base64
from Crypto.Cipher import AES
from Crypto import Random

BS = 16
pad = lambda s: s + (BS - len(s) % BS) * chr(BS - len(s) % BS)
unpad = lambda s : s[:-ord(s[len(s)-1:])]

class AESCipher:
def __init__( self, key ):
self.key = key

def encrypt( self, raw, iv ):
raw = pad(raw)
cipher = AES.new( self.key, AES.MODE_CBC, iv )
return base64.b64encode( cipher.encrypt( raw ) )

def decrypt( self, enc, iv ):
enc = base64.b64decode(enc)
cipher = AES.new(self.key, AES.MODE_CBC, iv )
return unpad(cipher.decrypt( enc ))

a = AESCipher('fce4aa4dcf0d2b27fe4ffdafa602c81d1930c410f48ada5c763d4c4052a939eb'.decode('hex_codec'))
print a.encrypt("This bloody encryption engine won't work !", 'c75271d593ca86ca785e3bb25e8d02cb'.decode('hex_codec'))

b = AESCipher('fce4aa4dcf0d2b27fe4ffdafa602c81d1930c410f48ada5c763d4c4052a939eb'.decode('hex_codec'))
print b.decrypt('44FsQIcqM412+YXZBwwoQSCz2uB9QPQMXJ410Xpw1f/M5RTRS7N6yfziAGq/Fd/E', 'c75271d593ca86ca785e3bb25e8d02cb'.decode('hex_codec'))

Output


44FsQIcqM412+YXZBwwoQSCz2uB9QPQMXJ410Xpw1f/M5RTRS7N6yfziAGq/Fd/E
This bloody encryption engine won't work !

The IV must be unpredictable (read: random). Don't use a static IV, because that makes the cipher deterministic and therefore not semantically secure. An attacker who observes ciphertexts can determine when the same message prefix was sent before. The IV is not secret, so you can send it along with the ciphertext. Usually, it is simply prepended to the ciphertext and sliced off before decryption.



Related Topics



Leave a reply



Submit