Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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?

like image 769
dave Avatar asked May 11 '12 09:05

dave


People also ask

How do I encrypt and decrypt in node JS?

Using Clean architecture for Node.NodeJS provides inbuilt library crypto to encrypt and decrypt data in NodeJS. We can use this library to encrypt data of any type. You can do the cryptographic operations on a string, buffer, and even a stream of data. The crypto also holds multiple crypto algorithms for encryption.

Can Python be used for encryption?

Conclusion. Cryptography library is one of the many libraries and modules that Python offers for secure communication and encryption. The fernet module of the library provides a built-in key generator and provides encryption and decryption functions for a string of data and large files.


2 Answers

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)
like image 135
dave Avatar answered Oct 04 '22 07:10

dave


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)
like image 38
Gayel Abou Imad Avatar answered Oct 04 '22 09:10

Gayel Abou Imad