Encrypting and Decrypting with python and nodejs

16,186

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)
Share:
16,186

Related videos on Youtube

Syed Saifuddin
Author by

Syed Saifuddin

A programmer on the internet.

Updated on June 15, 2022

Comments

  • Syed Saifuddin
    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
    Itay almost 12 years
    There'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
    Syed Saifuddin almost 12 years
    Thanks, I've fixed it in the answer now
  • Ryan Wheale
    Ryan Wheale about 10 years
    Thank 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
    Nathan Romano almost 9 years
    In python3 I noticed you need to change this line from ''' pad = ord(padded[-1]) ''' to ''' pad = ord(chr(padded[-1])) '''
  • abcd
    abcd over 8 years
    thanks 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 create encrypted but instead, stick with 'binary'
  • Bhupat Bheda
    Bhupat Bheda almost 5 years
    @dave This python code is not working in 3.5 version, can you please help me ?
  • user2589273
    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