Encrypting and Decrypting with python and nodejs
aes, encryption, node.js, python
Solution
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)
Problem
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?