使用python和nodejs加密和解密

时间:2012-05-11 09:40:00

标签: python node.js encryption aes

我正在尝试加密Python中的一些内容并在nodejs应用程序中解密它。

我很难让两个AES实现一起工作。我就在这里。

在节点中:

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);
    });
});

这会产生输出:

BXSGjDAYKeXlaRXVVJGuREKTPiiXeam8W9e96Nknt3E=
hello world 

在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)

这会产生输出

BXSGjDAYKeXlaRXVVJGuRA==
hello world 

显然它们非常接近,但节点似乎在填充输出。任何想法如何让两者互操作?

3 个答案:

答案 0 :(得分:21)

好的,我已经弄明白了,节点使用OpenSSL,它使用PKCS5来填充。 PyCrypto没有处理填充,所以我自己这样做只是在两者中添加'。

如果我在python代码中添加PKCS5填充并删除节点代码中的填充,则可以正常工作。

如此更新的工作代码。 节点:

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(padded[-1])
    return padded[:-pad]

def _encrypt(data, nonce, password):
    m = md5()
    m.update(password)
    key = m.hexdigest()

    m = md5()
    m.update(password + key)
    iv = m.hexdigest()

    data = pad(data)

    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 unpad(aes.decrypt(edata))

output = _encrypt(input, "", password) 
print(output)
plaintext = _decrypt(output, "", password)
print("'" + plaintext + "'")

答案 1 :(得分:0)

对于与我相似的任何人,他都在寻找一种简单的方法来对python中的AES进行加密和解密,而这在node.js中也做同样的事情。这里的类支持AES的不同位以及十六进制和base64编码,它们在node.js中产生相同的结果。

还请注意,如果您缺少Crypto软件包,则只​​需通过以下方式安装

pip install pycrypto

python的代码如下:

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])]

以下是使用该类的示例:

加密示例:

password = 'some_random_password'
content = 'content_to_be_encrypted'
cipher = AESCrypto('aes192', password)
encrypted = cipher.encrypt(content, 'hex')

解密示例:

password = 'some_random_password'
content = 'encrypted_content'
cipher = AESCrypto('aes192', password)
decrypted = cipher.decrypt(content, 'hex')

答案 2 :(得分:0)

尝试使用Python 3.8运行Python脚本时,遇到以下错误:

  m.update(password) 
  TypeError: Unicode-objects must be encoded before hashing

密码应为:

  password = b'abcd'

我也遇到以下错误:

m.update(password + key) 
TypeError: can't concat str to bytes

我可以通过在密钥后面添加以下行来修复它:

    key = bytes.fromhex(key_)

python脚本应以这种方式工作:

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)