Encrypting and Decrypting with python and nodejs
Solution 1
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)
Solution 2
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)
Related videos on Youtube
Comments
-
Syed Saifuddin almost 2 years
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?
-
Itay almost 12 yearsThere's a small bug in your Node.js decrypt function. It won't handle multiple
-
or multiple/
. Also, in decrypt, you need to replace_
with/
, not the other way around. You can simply replace that line with:var input = input.replace(/\-/g, '+').replace(/_/g, '/');
-
Syed Saifuddin almost 12 yearsThanks, I've fixed it in the answer now
-
Ryan Wheale about 10 yearsThank you so much for the examples. I was receiving a "wrong final block length" error with complex input, and I found an update described in this SO post which solved my problem. I applied the changes above.
-
Nathan Romano almost 9 yearsIn python3 I noticed you need to change this line from ''' pad = ord(padded[-1]) ''' to ''' pad = ord(chr(padded[-1])) '''
-
abcd over 8 yearsthanks for the example; helped a lot. One small change I needed to make: in JavaScript's encrypt(), after
var data = new Buffer(input, 'utf8').toString('binary');
one shouldn't specify 'utf8' to createencrypted
but instead, stick with 'binary' -
Bhupat Bheda almost 5 years@dave This python code is not working in 3.5 version, can you please help me ?
-
user2589273 over 2 years@BhupatBheda I just submitted an edit to correct for that (pending approval). Changes are mainly adding
.encode('utf-8')
to password and password+key strings