AES decryption padding with PKCS5 Python - python

I have been trying to implement AES CBC decryption in Python. Since the ciphered text is not a multiple of 16bytes, padding was necessary. Without padding, this error surfaced
"TypeError: Odd-length string"
But I could not find a proper reference for implementing PKCS5 in PyCrypto Python.
Are there any commands to implement this?
Thanks
After looking into Marcus's suggestion I did this.
My goal actually is to decrypt a hex message(128bytes) using this code. However, the output is " ?:" which is very small and the unpad command is deleting those bytes. This is the code.
from Crypto.Cipher import AES
BS = 16
pad = lambda s: s + (BS - len(s) % BS) * chr(BS - len(s) % BS)
unpad = lambda s : s[0:-ord(s[-1])]
class AESCipher:
def __init__( self, key ):
self.key = key
def encrypt( self, raw ):
raw = pad(raw)
iv = raw[:16]
raw=raw[16:]
#iv = Random.new().read( AES.block_size )
cipher = AES.new( self.key, AES.MODE_CBC, iv )
return ( iv + cipher.encrypt( raw ) ).encode("hex")
def decrypt( self, enc ):
iv = enc[:16]
enc= enc[16:]
cipher = AES.new(self.key, AES.MODE_CBC, iv )
return unpad(cipher.decrypt( enc))
mode = AES.MODE_CBC
key = "140b41b22a29beb4061bda66b6747e14"
ciphertext = "4ca00ff4c898d61e1edbf1800618fb2828a226d160dad07883d04e008a7897ee2e4b7465d5290d0c0e6c6822236e1daafb94ffe0c5da05d9476be028ad7c1d81";
key=key[:32]
decryptor = AESCipher(key)
decryptor.__init__(key)
plaintext = decryptor.decrypt(ciphertext)
print plaintext

You need to decode your hex encoded value before decryption. If you want to work with hex encoded keys, decode it as well..
Here, this should work.
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[0:-ord(s[-1])]
class AESCipher:
def __init__( self, key ):
"""
Requires hex encoded param as a key
"""
self.key = key.decode("hex")
def encrypt( self, raw ):
"""
Returns hex encoded encrypted value!
"""
raw = pad(raw)
iv = Random.new().read(AES.block_size);
cipher = AES.new( self.key, AES.MODE_CBC, iv )
return ( iv + cipher.encrypt( raw ) ).encode("hex")
def decrypt( self, enc ):
"""
Requires hex encoded param to decrypt
"""
enc = enc.decode("hex")
iv = enc[:16]
enc= enc[16:]
cipher = AES.new(self.key, AES.MODE_CBC, iv )
return unpad(cipher.decrypt( enc))
if __name__== "__main__":
key = "140b41b22a29beb4061bda66b6747e14"
ciphertext = "4ca00ff4c898d61e1edbf1800618fb2828a226d160dad07883d04e008a7897ee2e4b7465d5290d0c0e6c6822236e1daafb94ffe0c5da05d9476be028ad7c1d81"
key=key[:32]
decryptor = AESCipher(key)
plaintext = decryptor.decrypt(ciphertext)
print "%s" % plaintext

Related

Lua AES Encrypting with \xhh encoding

in python I implementing AES Encrypting
that encrypt string or binary string with AES-128 and encode the output to regular hex literals like \xhh I want to implementing this function with lua
class AESCipher(object):
def __init__(self, key):
self.bs = 16
self.key = key
def encrypt(self, raw, use_base64=True):
if Crypto:
raw = self._pad(raw)
cipher = AES.new(self.key, mode=AES.MODE_ECB)
crypted_text = cipher.encrypt(raw)
else:
_ = self._pad(raw)
cipher = pyaes.blockfeeder.Encrypter(
pyaes.AESModeOfOperationECB(self.key)) # no IV, auto pads to 16
crypted_text = cipher.feed(raw)
crypted_text += cipher.feed() # flush final block
if use_base64:
return base64.b64encode(crypted_text)
else:
return crypted_text
def decrypt(self, enc, use_base64=True):
if use_base64:
enc = base64.b64decode(enc)
if Crypto:
cipher = AES.new(self.key, AES.MODE_ECB)
raw = cipher.decrypt(enc)
return self._unpad(raw).decode('utf-8')
else:
cipher = pyaes.blockfeeder.Decrypter(
pyaes.AESModeOfOperationECB(self.key)) # no IV, auto pads to 16
plain_text = cipher.feed(enc)
plain_text += cipher.feed() # flush final block
return plain_text
def _pad(self, s):
padnum = self.bs - len(s) % self.bs
return s + padnum * chr(padnum).encode()
#staticmethod
def _unpad(s):
return s[:-ord(s[len(s)-1:])]
localkey = "30a91c993c721aa9"
localkey = localkey.encode('latin1')
print(localkey)
test_cipher = AESCipher(localkey)
payload = b'{"gwId":"315716865002915c2717","devId":"315716865002915c2717","uid":"315716865002915c2717","t":"1633628358"}'
#payload = payload.encode("utf-8")
encrypted = test_cipher.encrypt(payload,False)
print(encrypted)
The Output is:
b'30a91c993c721aa9'
b'\xe15\xf8\xc3\xc1)eW\x19\x0b/\x8d\xaf\xa8\xe0\xb6\xd4\xb7\xf7\xcf\xe4\x02\xa4\xab\xfft\x00]B\xea\xf5\r\xcbI\xef\xf9\xd8\xd6v\x02\xa6&\x0e\xe7\x19\x14\xde\xa3\xd0\xc4\xfb\xcb\xfa\x8c\xb8\x9fd\xa6v#\xdf\x03\xc7\xb8\xe5fe\xaf_\xdc\x931\xff\xfc\xfc\x85\x0b3\x92w\xe7k\x1c~}\xb2"\xe9\xc1\xe0\x9a\x12\xb3\xcak\x8en<t\xdd\x12n9Q\x8eX!\xaba2\x16\x9c'
How can i implemneting this encrypting function in lua with this encoding
i tried some ways and many libraries but the output encoding is not like above
what can i do?

Probles with AES encrypting. cant decrypt with a right key

I'm trying to decrypt encrypdet data. Data was encrypted with AES CBC mode using pycryptodome lib.
Have a such error - "ValueError: Incorrect AES key length (256 bytes)
import os
from Crypto import Random
from Crypto.Cipher import AES
class AESCipher:
def __init__(self, key):
pass
def pad(self, s):
return s + b"\0" * (AES.block_size - len(s) % AES.block_size)
def encrypt(self, message, key, key_size=256):
message = self.pad(message)
iv = Random.new().read(AES.block_size)
cipher = AES.new(key, AES.MODE_CBC, iv)
return iv + cipher.encrypt(message)
def decrypt(self, ciphertext, key):
iv = ciphertext[:AES.block_size]
cipher = AES.new(key, AES.MODE_CBC, iv)
plaintext = cipher.decrypt(ciphertext[AES.block_size:])
return plaintext.rstrip(b"\0")
def send_data(data)
key = os.urandom(16)
cipher = AESCipher(key)
ciphertext = cipher.encrypt(data, key)
return key, ciphertext
def receive_data(key, data):
cipher = AESCipher(key)
decrypted = cipher.decrypt(data, key)
return decrypted
data = b'12 43 42 46 af'
key, ciphertext = send_data(data)
decrypted = receive_data(key, data)
I think that what you want to decrypt is the ciphered text, not the original data (non-encrypted):
decrypted = receive_data(key, ciphertext)

TypeError: Object type <class 'str'> cannot be passed to C code

I am trying to implement aes encryption and decryption in python. When I execute code, it returns error. I have installed anaconda on my machine. I am running scripts in jupyter notebook.
!pip install pycryptodome
import base64
from Crypto import Random
from Crypto.Cipher import AES
BS = 16
pad = lambda s: s + (BS - len(s) % BS) * chr(BS - len(s) % BS)
unpad = lambda s : s[0:-ord(s[-1])]
class AESCipher:
def __init__( self, key ):
self.key = key
def encrypt( self, raw ):
raw = 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 ) )
def decrypt( self, enc ):
enc = base64.b64decode(enc)
iv = enc[:16]
cipher = AES.new(self.key, AES.MODE_CBC, iv )
return unpad(cipher.decrypt( enc[16:] ))
cipher = AESCipher('mysecretpassword')
encrypted = cipher.encrypt('Secret')
decrypted = cipher.decrypt(encrypted)
print(encrypted)
print(decrypted)
How to solve this ?
just update unpad to be unpad = lambda s : s[0:-ord(s[-1:])]
the main issue that ord() expects string of length one if you try to print value of s[-1] it prints 10 which not one char but s[-1:] printed value is b'\n' which is one char
also encode key to be bytes bytes(key, 'utf-8') and pad
pad = lambda s: bytes(s + (BS - len(s) % BS) * chr(BS - len(s) % BS), 'utf-8')
to make sure all inputs are bytes
from hashlib import sha256
import base64
from Crypto import Random
from Crypto.Cipher import AES
BS = 16
pad = lambda s: bytes(s + (BS - len(s) % BS) * chr(BS - len(s) % BS), 'utf-8')
unpad = lambda s : s[0:-ord(s[-1:])]
class AESCipher:
def __init__( self, key ):
self.key = bytes(key, 'utf-8')
def encrypt( self, raw ):
raw = 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 ) )
def decrypt( self, enc ):
enc = base64.b64decode(enc)
iv = enc[:16]
cipher = AES.new(self.key, AES.MODE_CBC, iv )
return unpad(cipher.decrypt( enc[16:] )).decode('utf8')
cipher = AESCipher('mysecretpassword')
encrypted = cipher.encrypt('Secret')
decrypted = cipher.decrypt(encrypted)
print(encrypted)
print(decrypted)
//First pip install pycryptodome -- (pycrypto is obsolete and gives issues)
// pip install pkcs7
from Crypto import Random
from Crypto.Cipher import AES
import base64
from pkcs7 import PKCS7Encoder
from app_settings.views import retrieve_settings # my custom settings
app_secrets = retrieve_settings(file_name='secrets');
def encrypt_data(text_data):
#limit to 16 bytes because my encryption key was too long
#yours could just be 'abcdefghwhatever'
encryption_key = app_secrets['ENCRYPTION_KEY'][:16];
#convert to bytes. same as bytes(encryption_key, 'utf-8')
encryption_key = str.encode(encryption_key);
#pad
encoder = PKCS7Encoder();
raw = encoder.encode(text_data) # Padding
iv = Random.new().read(AES.block_size ) #AES.block_size defaults to 16
# no need to set segment_size=BLAH
cipher = AES.new( encryption_key, AES.MODE_CBC, iv )
encrypted_text = base64.b64encode( iv + cipher.encrypt( str.encode(raw) ) )
return encrypted_text;

AES Decryption using Pycrypto Python Exception : 'builtins.UnicodeDecodeError: 'utf-8' codec can't decode byte 0x80 in position 0: invalid start byte'

I am using following implementation of AES cipher :-
import hashlib
from Crypto.Cipher import AES
class AESCipher:
def __init__(self, key):
self.BS = 128
try:
self.key = hashlib.sha256(key.encode()).digest()[:self.BS]
except:
self.key = hashlib.sha256(key).digest()[:self.BS]
self.iv = Random.new().read(AES.block_size)
def encrypt(self, raw):
raw = self._pad(raw)
cipher = AES.new(self.key, AES.MODE_CBC, self.iv)
return base64.b64encode(self.iv + cipher.encrypt(raw))
def decrypt(self, enc):
enc = base64.b64decode(enc)
self.iv = enc[:AES.block_size]
cipher = AES.new(self.key, AES.MODE_CBC, self.iv)
return self._unpad(cipher.decrypt(enc[AES.block_size:])).decode()
def _pad(self, s):
return s + (self.BS - len(s) % self.BS) * chr(self.BS - len(s) % self.BS).encode()
#staticmethod
def _unpad(s):
return s[:-ord(s[len(s)-1:])]
Encryption for a binary encoded dictionary object causes no errors but when I try to decrypt the same encrypted object, following exception is raised :-
return self._unpad(cipher.decrypt(enc[AES.block_size:])).decode()
builtins.UnicodeDecodeError: 'utf-8' codec can't decode byte 0x80 in position 0: invalid start byte
I tried to use 'ISO' and 'latin' encoding and decoding functions. But after that the socket on other side of the LAN recognizes it as a string and not as a dictionary object.
My question :- What I am doing wrong here ?
Additional information :-
key = 'SHSJDS-DSJBSJDS-DSKNDS' # some thing following this pattern
bin_json_object = pickle.dumps(dict_object)
enc_json_object = AESenc(bin_json_object, key)
def AESenc(self, data, key):
return AESCipher(key).encrypt(data)
def AESdec(self, data, key):
return AESCipher(key).decrypt(data)
For example If I use "ISO-8859-1" encoding in the above code :-
binary encoded representation of dictionary object :-
b'\x80\x03}q\x00(X\x02\x00\x00\x00idq\x01X$\x00\x00\x0096e09f6c-1e80-4cd1-9225-159e35bcacb4q\x02X\x0c\x00\x00\x00request_codeq\x03K\x01X\x0e\x00\x00\x00payload_lengthq\x04K!X\x0b\x00\x00\x00session_keyq\x05Nu.'
encrypted representation of binary encoded dictionary object :-
b'cZi+L4Wi51B5oDGQKlFb9bioxKH3TFRO1piECklafwTe6GYm/VeVjJaCDKiI+o6f6CcUnMvx+2EfEwcHCH/KDDeHTivIUou7WGVrd1P++HxfYNutY/aOn30Y/yiICvwWRHBn/3zU3xXvr/4XrtoVddM2cQEgXupIcC99TIxurrr8CCZd74ZnWj6QB8quCtHD'
But if I now try to decrypt the same on other node on same LAN via socket. I get following decrypted representation :-
}q(XidqX$96e09f6c-1e80-4cd1-9225-159e35bcacb4qX
request_codeqKXpayload_lengthqK!X
session_keyqNu.
which is completely different from original binary representation of the same dictionary object. And produces the following exception :-
data = pickle.loads(data)
builtins.TypeError: 'str' does not support the buffer interface
Finally after hours of debugging I came with a working code, but I am not able to understand, why this is working. Please if someone could explain this in comments.
Modified version AES cipher code :-
class AESCipher:
def __init__(self, key):
self.BS = AES.block_size
try:
self.key = hashlib.sha256(key.encode('ISO-8859-1')).digest()[:self.BS]
except:
self.key = hashlib.sha256(key).digest()[:self.BS]
self.iv = Random.new().read(AES.block_size)
def encrypt(self, raw):
raw = self._pad(raw)
cipher = AES.new(self.key, AES.MODE_CBC, self.iv)
return base64.b64encode(self.iv + cipher.encrypt(raw))
def decrypt(self, enc):
enc = base64.b64decode(enc)
self.iv = enc[:AES.block_size]
cipher = AES.new(self.key, AES.MODE_CBC, self.iv)
return self._unpad(cipher.decrypt(enc[AES.block_size:])).decode('ISO-8859-1')
def _pad(self, s):
return s + (self.BS - len(s) % self.BS) * chr(self.BS - len(s) % self.BS).encode('ISO-8859-1')
#staticmethod
def _unpad(s):
print('returning : ', s[:-ord(s[len(s)-1:])])
return s[:-ord(s[len(s)-1:])]
Now without modifying the AES encryption and decryption functions. I introduced a following variation in the code. Whenever another node receives a binary stream it first decrypts it with the AES decrypt function. But after decryption encoded dictionary object has to be encoded again with 'ISO-8859-1' as shown below :-
dict_object = self.AESdecryption(binary_stream, self.session_key)
dict = pickle.loads(dict_object.encode('ISO-8859-1'))
print(dict)
The above produces correct dictionary object. But what I don't understand is when a dictionary object was encrypted in 'ISO-8859-1' encoding, and and then decrypted on other node in 'ISO-8859-1' encoding, then why before passing it to the pickle.loads() I have to encode it again to get the original dictionary object. Please if someone could explain why it is happening ?

Encrypting and Decrypting with python and nodejs

I'm trying to encrypt some content in Python and decrypt it in a nodejs application.
I'm struggling to get the two AES implementations to work together though. Here is where I am at.
In node:
var crypto = require('crypto');
var password = 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa';
var input = 'hello world';
var encrypt = function (input, password, callback) {
var m = crypto.createHash('md5');
m.update(password)
var key = m.digest('hex');
m = crypto.createHash('md5');
m.update(password + key)
var iv = m.digest('hex');
// add padding
while (input.length % 16 !== 0) {
input += ' ';
}
var data = new Buffer(input, 'utf8').toString('binary');
var cipher = crypto.createCipheriv('aes-256-cbc', key, iv.slice(0,16));
var encrypted = cipher.update(data, 'binary') + cipher.final('binary');
var encoded = new Buffer(encrypted, 'binary').toString('base64');
callback(encoded);
};
var decrypt = function (input, password, callback) {
// Convert urlsafe base64 to normal base64
var input = input.replace('-', '+').replace('/', '_');
// Convert from base64 to binary string
var edata = new Buffer(input, 'base64').toString('binary')
// Create key from password
var m = crypto.createHash('md5');
m.update(password)
var key = m.digest('hex');
// Create iv from password and key
m = crypto.createHash('md5');
m.update(password + key)
var iv = m.digest('hex');
// Decipher encrypted data
var decipher = crypto.createDecipheriv('aes-256-cbc', key, iv.slice(0,16));
var decrypted = decipher.update(edata, 'binary') + decipher.final('binary');
var plaintext = new Buffer(decrypted, 'binary').toString('utf8');
callback(plaintext);
};
encrypt(input, password, function (encoded) {
console.log(encoded);
decrypt(encoded, password, function (output) {
console.log(output);
});
});
This produces the output:
BXSGjDAYKeXlaRXVVJGuREKTPiiXeam8W9e96Nknt3E=
hello world
In python
from Crypto.Cipher import AES
from hashlib import md5
import base64
password = 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'
input = 'hello world'
def _encrypt(data, nonce, password):
m = md5()
m.update(password)
key = m.hexdigest()
m = md5()
m.update(password + key)
iv = m.hexdigest()
# pad to 16 bytes
data = data + " " * (16 - len(data) % 16)
aes = AES.new(key, AES.MODE_CBC, iv[:16])
encrypted = aes.encrypt(data)
return base64.urlsafe_b64encode(encrypted)
def _decrypt(edata, nonce, password):
edata = base64.urlsafe_b64decode(edata)
m = md5()
m.update(password)
key = m.hexdigest()
m = md5()
m.update(password + key)
iv = m.hexdigest()
aes = AES.new(key, AES.MODE_CBC, iv[:16])
return aes.decrypt(edata)
output = _encrypt(input, "", password)
print(output)
plaintext = _decrypt(output, "", password)
print(plaintext)
This produces the output
BXSGjDAYKeXlaRXVVJGuRA==
hello world
Clearly they are very close, but node seems to be padding the output with something. Any ideas how I can get the two to interoperate?
OK, I've figured it out, node uses OpenSSL which uses PKCS5 to do padding. PyCrypto doesn't handle the padding so I was doing it myself just add ' ' in both.
If I add PKCS5 padding in the python code and remove the padding in the node code, it works.
So updated working code.
Node:
var crypto = require('crypto');
var password = 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa';
var input = 'hello world';
var encrypt = function (input, password, callback) {
var m = crypto.createHash('md5');
m.update(password)
var key = m.digest('hex');
m = crypto.createHash('md5');
m.update(password + key)
var iv = m.digest('hex');
var data = new Buffer(input, 'utf8').toString('binary');
var cipher = crypto.createCipheriv('aes-256-cbc', key, iv.slice(0,16));
// UPDATE: crypto changed in v0.10
// https://github.com/joyent/node/wiki/Api-changes-between-v0.8-and-v0.10
var nodev = process.version.match(/^v(\d+)\.(\d+)/);
var encrypted;
if( nodev[1] === '0' && parseInt(nodev[2]) < 10) {
encrypted = cipher.update(data, 'binary') + cipher.final('binary');
} else {
encrypted = cipher.update(data, 'utf8', 'binary') + cipher.final('binary');
}
var encoded = new Buffer(encrypted, 'binary').toString('base64');
callback(encoded);
};
var decrypt = function (input, password, callback) {
// Convert urlsafe base64 to normal base64
var input = input.replace(/\-/g, '+').replace(/_/g, '/');
// Convert from base64 to binary string
var edata = new Buffer(input, 'base64').toString('binary')
// Create key from password
var m = crypto.createHash('md5');
m.update(password)
var key = m.digest('hex');
// Create iv from password and key
m = crypto.createHash('md5');
m.update(password + key)
var iv = m.digest('hex');
// Decipher encrypted data
var decipher = crypto.createDecipheriv('aes-256-cbc', key, iv.slice(0,16));
// UPDATE: crypto changed in v0.10
// https://github.com/joyent/node/wiki/Api-changes-between-v0.8-and-v0.10
var nodev = process.version.match(/^v(\d+)\.(\d+)/);
var decrypted, plaintext;
if( nodev[1] === '0' && parseInt(nodev[2]) < 10) {
decrypted = decipher.update(edata, 'binary') + decipher.final('binary');
plaintext = new Buffer(decrypted, 'binary').toString('utf8');
} else {
plaintext = (decipher.update(edata, 'binary', 'utf8') + decipher.final('utf8'));
}
callback(plaintext);
};
encrypt(input, password, function (encoded) {
console.log(encoded);
decrypt(encoded, password, function (output) {
console.log(output);
});
});
Python:
from Crypto.Cipher import AES
from hashlib import md5
import base64
password = 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'
input = 'hello world'
BLOCK_SIZE = 16
def pad (data):
pad = BLOCK_SIZE - len(data) % BLOCK_SIZE
return data + pad * chr(pad)
def unpad (padded):
pad = ord(chr(padded[-1]))
return padded[:-pad]
def get_key_iv (password):
m = md5()
m.update(password.encode('utf-8'))
key = m.hexdigest()
m = md5()
m.update((password + key).encode('utf-8'))
iv = m.hexdigest()
return [key,iv]
def _encrypt(data, password):
key,iv = get_key_iv(password)
data = pad(data)
aes = AES.new(key, AES.MODE_CBC, iv[:16])
encrypted = aes.encrypt(data)
return base64.urlsafe_b64encode(encrypted)
def _decrypt(edata, password):
edata = base64.urlsafe_b64decode(edata)
key,iv = get_key_iv(password)
aes = AES.new(key, AES.MODE_CBC, iv[:16])
return unpad(aes.decrypt(edata))
output = _encrypt(input, password)
print(output)
plaintext = _decrypt(output, password)
print(plaintext)
while trying to run the Python script using Python 3.8 I encountered the following error:
m.update(password)
TypeError: Unicode-objects must be encoded before hashing
the password should be :
password = b'abcd'
I also got the following error :
m.update(password + key)
TypeError: can't concat str to bytes
I was able to fix it by adding the following line after key:
key = bytes.fromhex(key_)
The python script should work this way :
from Crypto.Cipher import AES
from hashlib import md5
import base64
password = b'abcd'
input = 'hello world'
BLOCK_SIZE = 16
def pad (data):
pad = BLOCK_SIZE - len(data) % BLOCK_SIZE
return data + pad * chr(pad)
def unpad (padded):
pad = ord(chr(padded[-1]))
return padded[:-pad]
def _encrypt(data, nonce, password):
m = md5()
m.update(password)
key_ = m.hexdigest()
key = bytes.fromhex(key_)
m = md5()
m.update(password + key)
iv = m.hexdigest()
iv = bytes.fromhex(iv)
data = pad(data)
aes = AES.new(key, AES.MODE_CBC, iv[:16])
encrypted = aes.encrypt(data.encode('utf-8'))
return base64.urlsafe_b64encode(encrypted)
def _decrypt(edata, nonce, password):
edata = base64.urlsafe_b64decode(edata)
m = md5()
m.update(password)
key = m.hexdigest()
key = bytes.fromhex(key)
m = md5()
m.update(password + key)
iv = m.hexdigest()
iv = bytes.fromhex(iv)
aes = AES.new(key, AES.MODE_CBC, iv[:16])
return unpad(aes.decrypt(edata))
output = _encrypt(input, "", password)
print(output)
plaintext = _decrypt(output, "", password)
print(plaintext)
Just for any one that is similar to me, who was finding a simple way to do the encryption and decryption for AES in python that is doing the same thing in node.js. The class here supports different bits of AES and both hex and base64 encoding that produces same result in node.js.
Also noted that if you are missing the package Crypto, you can simply install it by
pip install pycrypto
The code for python is as follows:
import base64
import hashlib
from Crypto.Cipher import AES
class AESCrypto(object):
def __init__(self, algorithm, password):
self.algorithm = filter(lambda x: not x.isdigit(), algorithm).lower()
self.bits = int(filter(str.isdigit, algorithm))
self.bs = 16
if not self.algorithm == 'aes':
raise Exception('Only AES crypto is supported')
if not self.bits % 8 == 0:
raise Exception('Bits of crypto must be a multiply of 8.')
self.bytes = self.bits / 8
self.password = password
self.generateKeyAndIv()
def generateKeyAndIv(self):
last = ''
allBytes = ''
maxBytes = self.bytes + self.bs
while len(allBytes) < maxBytes:
last = hashlib.md5(last + self.password).digest()
allBytes += last
self.key = allBytes[:self.bytes]
self.iv = allBytes[self.bytes:maxBytes]
def encrypt(self, raw, outputEncoding):
outputEncoding = outputEncoding.lower()
raw = self._pad(raw)
cipher = AES.new(self.key, AES.MODE_CBC, self.iv)
encrypted = cipher.encrypt(raw)
if outputEncoding == 'hex':
return encrypted.encode('hex')
elif outputEncoding == 'base64':
return base64.b64encode(encrypted)
else:
raise Exception('Encoding is not supported.')
def decrypt(self, data, inputEncoding):
inputEncoding = inputEncoding.lower()
if inputEncoding == 'hex':
data = ''.join(map(chr, bytearray.fromhex(data)))
elif inputEncoding == 'base64':
data = base64.b64decode(data)
cipher = AES.new(self.key, AES.MODE_CBC, self.iv)
return self._unpad(cipher.decrypt(data))
def _pad(self, data):
padding = self.bs - len(data) % self.bs
return data + padding * chr(padding)
#staticmethod
def _unpad(data):
return data[0:-ord(data[-1])]
The following are examples to use the class:
Encryption Example:
password = 'some_random_password'
content = 'content_to_be_encrypted'
cipher = AESCrypto('aes192', password)
encrypted = cipher.encrypt(content, 'hex')
Decryption Example:
password = 'some_random_password'
content = 'encrypted_content'
cipher = AESCrypto('aes192', password)
decrypted = cipher.decrypt(content, 'hex')
Because I spent way too much time on this with Python 3.10.7 and Node.js v18.6.0.
Here is a working code totally compatible between two languages with examples.
Only the secret is needed for getting same values as expected :)
Note pycryptodome is needed for Python. Code should be tweaked for supporting different algorithms.
const crypto = require('crypto')
function get_crypto(secret, encode) {
// Create hashed key from password/key
let m = crypto.createHash('md5').update(secret)
const key = m.digest('hex')
m = crypto.createHash('md5').update(secret + key)
const iv = m.digest('hex').slice(0, 16) // only in aes-256
return encode
? crypto.createCipheriv('aes-256-cbc', key, iv)
: crypto.createDecipheriv('aes-256-cbc', key, iv)
}
const secret = 'f8abb29f13cb932704badb0de414ab08ca9f6c63' // crypto.randomBytes(20).toString('hex')
const value = 'hello world'
const data = Buffer.from(value, 'utf8').toString('binary')
const cipher = get_crypto(secret, true)
const encrypted = Buffer.concat([cipher.update(data, 'utf8'), cipher.final()]).toString('binary')
const encoded = Buffer.from(encrypted, 'binary').toString('base64')
console.log('encoded:', encoded)
const edata = Buffer.from(encoded, 'base64').toString('binary')
const decipher = get_crypto(secret, false)
const decoded = Buffer.concat([decipher.update(edata, 'binary'), decipher.final()]).toString('utf-8')
console.log('decoded:', decoded)
# This script needs pycryptodome dependency
# pip install pycryptodome
from Crypto.Cipher import AES
from hashlib import md5
import base64
BLOCK_SIZE = AES.block_size
def get_aes(s):
m = md5()
m.update(s.encode('utf-8'))
key = m.hexdigest()
m = md5()
m.update((s + key).encode('utf-8'))
iv = m.hexdigest()
return AES.new(key.encode("utf8"), AES.MODE_CBC, iv.encode("utf8")[:BLOCK_SIZE])
# pkcs5 padding
def pad(byte_array):
pad_len = BLOCK_SIZE - len(byte_array) % BLOCK_SIZE
return byte_array + (bytes([pad_len]) * pad_len)
# pkcs5 - unpadding
def unpad(byte_array):
return byte_array[:-ord(byte_array[-1:])]
def _encrypt(s, data):
data = pad(data.encode("UTF-8"))
aes = get_aes(s)
encrypted = aes.encrypt(data)
return base64.urlsafe_b64encode(encrypted).decode('utf-8')
def _decrypt(s, edata):
edata = base64.urlsafe_b64decode(edata)
aes = get_aes(s)
return unpad(aes.decrypt(edata)).decode('utf-8')
if __name__ == '__main__':
secret = 'f8abb29f13cb932704badb0de414ab08ca9f6c63'
value = 'hello world'
encoded = _encrypt(secret, value)
print('encoded:', encoded)
decoded = _decrypt(secret, encoded)
print('decoded:', decoded)
Help from:
Implementing AES/ECB/PKCS5 padding in Python
Node.js - Set padding in crypto module
Python Encrypting with PyCrypto AES

Categories