I am currently trying to use AES cryptography to encrypt and decrypt a string that always has a length of 9 characters. What I am trying to do is to encrypt the string in swift and then decrypt that encrypted string in python. I am using AES encryption with CryptoSwift and decrypting with PyCryptodome.
This is what my function in swift looks like:
import CryptoSwift
func crypto_testing() {
print("Cryptography!")
let ivString = "0000000000000000"
let keyString = "This is a key123"
let key = [UInt8](keyString.utf8)
let iv = [UInt8](ivString.utf8)
let stringToEncrypt = "123456789"
let enc = try! aesEncrypt(stringToEncrypt: stringToEncrypt, key: key, iv: iv)
print("ENCRYPT:",enc)
}
func aesEncrypt(stringToEncrypt: String, key: Array<UInt8>, iv: Array<UInt8>) throws -> String {
let data = stringToEncrypt.data(using: String.Encoding.utf8)
let encrypted = try AES(key: key, blockMode: CFB(iv: iv), padding: .noPadding).encrypt((data?.bytes)!)
return encrypted.toHexString() //result
}
The result I get from running the crypto_testing function is:
Cryptography!
ENCRYPT: 5d02105a49e55d2ff7
Furthermore, this is what my decryption function looks like in python:
import binascii
from Crypto.Cipher import AES
KEY = b'This is a key123'
IV = b'0000000000000000'
MODE = AES.MODE_CFB
def decrypt(key, iv, encrypted_text):
aes = AES.new(key, MODE, iv)
encrypted_text_bytes = binascii.a2b_hex(encrypted_text)
decrypted_text = aes.decrypt(encrypted_text_bytes)
return decrypted_text
decrypted_text = decrypt(KEY, IV, encrypted_text)
print(decrypted_text)
And the result from plugging in the encrypted message into the decrypt function like so:
>>> decrypt(b'This is a key123', b'0000000000000000', '5d02105a49e55d2ff7')
b'1%\xdc\xc8\xa0\r\xbd\xb8\xf0'
If anyone has any clue as to what is going wrong here that would be a great help.
Try this:
let stringToEncrypt = "123456789"
var aes: AES
var encrypted: [UInt8]
do {
aes = try AES(key: key, blockMode: CBC(iv: iv), padding: . noPadding)
encrypted = try aes.encrypt(stringToEncrypt.bytes)
}
let base64Encypted = encrypted.toBase64()```
I am trying to translate the program below to Python.
In the end the Python code should produce the same passphrase output as the Perl variant.
#!/usr/bin/env perl
use Crypt::CBC;
my $key = 'key to the gates';
my $cipher = Crypt::CBC->new(
-key => $key,
-cipher => 'Blowfish',
-salt => '12341234'
);
my $pass_phrase = "secret text";
print $cipher->encrypt_hex($pass_phrase),"\n";
print unpack('H*', $cipher->key()), "\n";
print unpack('H*', $cipher->iv()), "\n";
print unpack('H*', $cipher->salt()), "\n";
print unpack('H*', $cipher->keysize()), "\n";
#output:
#pass:53616c7465645f5f31323334313233344c0ad60f0eb9fdffc46b5cc02d76d473 <- hex enc "Salted__12341234<gibberish>"
#key:031f2cc96d063cf836ce42c77a8a3d25bdd959659d00a892a02b13930e92f47c82a7054256be4a0f1b3771bd36c07fe3ea4f6900f8ddebe5
#iv:f4d50b2385a2a996
#salt:3132333431323334
#keysize:3536
Below is my python code than that decrypts successfully, but encrypts unsuccessfully.
The successfull decryption of the perl passphrase was mostly for verify input params. (Taking IV straight from perl and removing Crypt::CBC's added 16 char of salt before decrypting made it work, various posts on SO helped me..).
Then is the unsuccessful attempt to encryption passhphrase and generate the same output as perl.
I think i just need to use the right combination of the verified input params to get it working...maybe the salt should be padded? Or padding in general is wrong? Or iv input should not be hexlified? (it need to be 8 chars..)
Any input is appreciated!
!/usr/bin/env python
from Crypto.Cipher import Blowfish
from binascii import hexlify, unhexlify
from struct import pack
import base64
# working decryption
passphrase = unhexlify("53616c7465645f5f31323334313233344c0ad60f0eb9fdffc46b5cc02d76d473"[32:])
key = unhexlify("031f2cc96d063cf836ce42c77a8a3d25bdd959659d00a892a02b13930e92f47c82a7054256be4a0f1b3771bd36c07fe3ea4f6900f8ddebe5")
iv = unhexlify('f4d50b2385a2a996')
num_padding = ord(Blowfish.new(key, Blowfish.MODE_CBC, iv).decrypt(passphrase)[-1])
print Blowfish.new(key, Blowfish.MODE_CBC, iv).decrypt(passphrase)[:(-1*num_padding)]
# --- non working encryption!
passphrase2 = "secret text"
key2 = 'key to the gates'
iv2 = unhexlify('f4d50b2385a2a996')
plength = Blowfish.block_size - len(passphrase2) % Blowfish.block_size
padding = [plength] * plength
pad_str = passphrase2 + pack('b' * plength, *padding)
cipher = Blowfish.new(key2, Blowfish.MODE_CBC, iv2)
print hexlify("Salted__12341234"+cipher.encrypt(pad_str))
#output:
#secret text
#53616c7465645f5f31323334313233346aa3f2169677cbf282b1330b46da3114
I guess you are not supposed to answer your own question, but i leave it here anyway:)
I think maybe padding was off in the original code, not really sure.
This time i used the nice "Padding" lib https://pypi.python.org/pypi/Padding
from Crypto.Cipher import Blowfish
from binascii import hexlify, unhexlify
import Padding
class BlowFishCipher:
def __init__( self, key,iv,salt ):
self.key = unhexlify(key)
self.iv = unhexlify(iv)
self.salt = unhexlify(salt)
def encrypt( self, raw ):
raw = Padding.appendPadding(raw, BS)
cipher = Blowfish.new( self.key, Blowfish.MODE_CBC, self.iv )
return hexlify("Salted__"+self.salt+cipher.encrypt(raw))
def decrypt( self, enc):
enc = unhexlify(enc)
cipher = Blowfish.new(self.key, Blowfish.MODE_CBC, self.iv )
return Padding.removePadding(cipher.decrypt( enc), BS)
if __name__== "__main__":
BS = Blowfish.block_size
key_perl = "031f2cc96d063cf836ce42c77a8a3d25bdd959659d00a892a02b13930e92f47c82a7054256be4a0f1b3771bd36c07fe3ea4f6900f8ddebe5"
iv_perl = "f4d50b2385a2a996"
salt_perl= "3132333431323334"
passphrase_perl = "53616c7465645f5f31323334313233344c0ad60f0eb9fdffc46b5cc02d76d473"
# remove "Salted__12341234" from passhphrase_perl by [32:]
passphrase = passphrase_perl[32:]
decryptor = BlowFishCipher(key_perl,iv_perl,salt_perl)
plaintext = decryptor.decrypt(passphrase)
print "decrypted {:>70}".format(plaintext)
ciphertext = "secret text"
encodedtext = decryptor.encrypt(ciphertext)
print "encrypted pyhton {:>70}".format(encodedtext)
print "encrypted perl {:>70}".format(passphrase_perl)
decrypted secret text
encrypted pyhton 53616c7465645f5f31323334313233344c0ad60f0eb9fdffc46b5cc02d76d473
encrypted perl 53616c7465645f5f31323334313233344c0ad60f0eb9fdffc46b5cc02d76d473
I am trying to encrypt using node.js as follows (node.js v0.10.33):
var crypto = require('crypto');
var assert = require('assert');
var algorithm = 'aes256'; // or any other algorithm supported by OpenSSL
var key = 'mykey';
var text = 'this-needs-to-be-encrypted';
var cipher = crypto.createCipher(algorithm, key);
var encrypted = cipher.update(text, 'utf8', 'hex') + cipher.final('hex');
console.log('encrypted', encrypted, encrypted.length)
/*
var decipher = crypto.createDecipher(algorithm, key);
try {
var decrypted = decipher.update(encrypted, 'hex', 'utf8') + decipher.final('utf8');
} catch (e) {
console.error('Couldnt decipher encrypted text. Invalid key provided', e)
} finally {
assert.equal(decrypted, text);
}
*/
How can I decrypt the encrypted text using PyCrypto (v2.6.1) on py2.7?
You should be using crypto.createCipheriv as stated in https://nodejs.org/api/crypto.html#crypto_crypto_createcipher_algorithm_password.
The answer below assumes you change your snippet to use crypto.createCipheriv, as following:
var crypto = require('crypto');
var assert = require('assert');
var algorithm = 'aes256'; // or any other algorithm supported by OpenSSL
var key = '00000000000000000000000000000000';
var iv = '0000000000000000';
var text = 'this-needs-to-be-encrypted';
var cipher = crypto.createCipheriv(algorithm, key, iv);
var encrypted = cipher.update(text, 'utf8', 'hex') + cipher.final('hex');
console.log('encrypted', encrypted, encrypted.length)
which generates the encrypted text b88e5f69c7bd5cd67c9c12b9ad73e8c1ca948ab26da01e6dad0e7f95448e79f4.
Python Solution with explicit key and IV:
from Crypto import Random
from Crypto.Cipher import AES
BS = 16
def pad(data):
padding = BS - len(data) % BS
return data + padding * chr(padding)
def unpad(data):
return data[0:-ord(data[-1])]
def decrypt_node(hex_data, key='0'*32, iv='0'*16):
data = ''.join(map(chr, bytearray.fromhex(hex_data)))
aes = AES.new(key, AES.MODE_CBC, iv)
return unpad(aes.decrypt(data))
def encrypt_node(data, key='0'*32, iv='0'*16):
aes = AES.new(key, AES.MODE_CBC, iv)
return aes.encrypt(pad(data)).encode('hex')
print(encrypt_node('this-needs-to-be-encrypted'))
print(decrypt_node('b88e5f69c7bd5cd67c9c12b9ad73e8c1ca948ab26da01e6dad0e7f95448e79f4'))
If you keep using plain crypto.createCipher you will need to derive the key and iv from the password using https://www.openssl.org/docs/man1.0.2/crypto/EVP_BytesToKey.html.
I'm trying to user CryptoJS under node to decrypt messages. I've got working Python code for decrypting the messages, but I need to run this under nodejs and would rather not call out to python for every message.
from Crypto.Cipher import AES
from Crypto import Random
import base64
encrypted='tBIFLLdvl/Bp8XAwXBYatbJSYkNTl9/dXkHZd4OjbZ0I9Jg6xrAx/bxuQHuZrNSzYZOBEKbyMlTTT8nQEDza8wQ22mrRaZlQqT3aWpdZe6aiWAEIvTHoQPglgVbz1HnYOHfZtGmu3a3cwfpFMK+ouczTWM545nWvG/I4zV4uFgna1rW9sznxumN/3RKSbC1USZ2TM9PrG967M5Mu+riQfh9i/yt6ubwj3kln2+C0WsRRr44ELyDKGdS69YExa535z42bfXTORjvaiMvizvkz55c343s0G4ziT6tLfDCGELsrAu/2NViKxJZZRg8Dmm0FnchB9OQ4ujVCBoDUXvfx3iHjzquC+OftbOovQUecoXb7UfuwIxMekgSJnonLC45S'
key = '22<\\09\\8e.==\\4#{{+!%i=]%Y/upi8!Z'
iv = '{+!%i=]%Y/upi8!Z'
cipher = AES.new(key, AES.MODE_CBC, iv)
print cipher.decrypt(base64.b64decode(encrypted))
This prints out my decrypted string from python. I'm sure my CryptoJS version is completely wrong at this point.
var node_cryptojs = require('node-cryptojs-aes');
var CryptoJS = node_cryptojs.CryptoJS;
var key = CryptoJS.enc.Latin1.parse('22<\\09\\8e.==\\4#{{+!%i=]%Y/upi8!Z');
var iv = CryptoJS.enc.Latin1.parse('{+!%i=]%Y/upi8!Z');
var encrypted = 'tBIFLLdvl/Bp8XAwXBYatbJSYkNTl9/dXkHZd4OjbZ0I9Jg6xrAx/bxuQHuZrNSzYZOBEKbyMlTTT8nQEDza8wQ22mrRaZlQqT3aWpdZe6aiWAEIvTHoQPglgVbz1HnYOHfZtGmu3a3cwfpFMK+ouczTWM545nWvG/I4zV4uFgna1rW9sznxumN/3RKSbC1USZ2TM9PrG967M5Mu+riQfh9i/yt6ubwj3kln2+C0WsRRr44ELyDKGdS69YExa535z42bfXTORjvaiMvizvkz55c343s0G4ziT6tLfDCGELsrAu/2NViKxJZZRg8Dmm0FnchB9OQ4ujVCBoDUXvfx3iHjzquC+OftbOovQUecoXb7UfuwIxMekgSJnonLC45S';
var plaintextArray = CryptoJS.AES.decrypt({ ciphertext: encrypted }, key, { iv: iv } );
console.log(CryptoJS.enc.Latin1.stringify(plaintextArray));
All I get out of this version is a bunch of garbled text such as
{)¬L¶u[?®º[ «)þd0³(Á¨ÕßgÙä Þ¨Þêâí99dáb*¦ÿßqf pr£Æ(> þ?C×$ÀM#<o¬_±À¥s=ê,)u<¯XÚîDÊP¢q|f̽^IiaJÂ__NîjbÉâïðp8å.º}ÜucósLÈqÁè&ô£LYLüâÙháë
Turns out I was one encoding away from correct. The Latin1 parses are correct. It was just the decode from base64 on the input that was missing. Must have missed that combination earlier.
var node_cryptojs = require('node-cryptojs-aes');
var CryptoJS = node_cryptojs.CryptoJS;
var key = CryptoJS.enc.Latin1.parse('22<\\09\\8e.==\\4#{{+!%i=]%Y/upi8!Z');
var iv = CryptoJS.enc.Latin1.parse('{+!%i=]%Y/upi8!Z');
var encrypted = 'tBIFLLdvl/Bp8XAwXBYatbJSYkNTl9/dXkHZd4OjbZ0I9Jg6xrAx/bxuQHuZrNSzYZOBEKbyMlTTT8nQEDza8wQ22mrRaZlQqT3aWpdZe6aiWAEIvTHoQPglgVbz1HnYOHfZtGmu3a3cwfpFMK+ouczTWM545nWvG/I4zV4uFgna1rW9sznxumN/3RKSbC1USZ2TM9PrG967M5Mu+riQfh9i/yt6ubwj3kln2+C0WsRRr44ELyDKGdS69YExa535z42bfXTORjvaiMvizvkz55c343s0G4ziT6tLfDCGELsrAu/2NViKxJZZRg8Dmm0FnchB9OQ4ujVCBoDUXvfx3iHjzquC+OftbOovQUecoXb7UfuwIxMekgSJnonLC45S';
var plaintextArray = CryptoJS.AES.decrypt({ ciphertext: CryptoJS.enc.Base64.parse(encrypted) }, key, { iv: iv } );
console.log(CryptoJS.enc.Latin1.stringify(plaintextArray));
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?
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)
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)
Just for any one that is similar to me, who was finding a simple way to do the encryption and decryption for AES in python that is doing the same thing in node.js. The class here supports different bits of AES and both hex and base64 encoding that produces same result in node.js.
Also noted that if you are missing the package Crypto, you can simply install it by
pip install pycrypto
The code for python is as follows:
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])]
The following are examples to use the class:
Encryption Example:
password = 'some_random_password'
content = 'content_to_be_encrypted'
cipher = AESCrypto('aes192', password)
encrypted = cipher.encrypt(content, 'hex')
Decryption Example:
password = 'some_random_password'
content = 'encrypted_content'
cipher = AESCrypto('aes192', password)
decrypted = cipher.decrypt(content, 'hex')
Because I spent way too much time on this with Python 3.10.7 and Node.js v18.6.0.
Here is a working code totally compatible between two languages with examples.
Only the secret is needed for getting same values as expected :)
Note pycryptodome is needed for Python. Code should be tweaked for supporting different algorithms.
const crypto = require('crypto')
function get_crypto(secret, encode) {
// Create hashed key from password/key
let m = crypto.createHash('md5').update(secret)
const key = m.digest('hex')
m = crypto.createHash('md5').update(secret + key)
const iv = m.digest('hex').slice(0, 16) // only in aes-256
return encode
? crypto.createCipheriv('aes-256-cbc', key, iv)
: crypto.createDecipheriv('aes-256-cbc', key, iv)
}
const secret = 'f8abb29f13cb932704badb0de414ab08ca9f6c63' // crypto.randomBytes(20).toString('hex')
const value = 'hello world'
const data = Buffer.from(value, 'utf8').toString('binary')
const cipher = get_crypto(secret, true)
const encrypted = Buffer.concat([cipher.update(data, 'utf8'), cipher.final()]).toString('binary')
const encoded = Buffer.from(encrypted, 'binary').toString('base64')
console.log('encoded:', encoded)
const edata = Buffer.from(encoded, 'base64').toString('binary')
const decipher = get_crypto(secret, false)
const decoded = Buffer.concat([decipher.update(edata, 'binary'), decipher.final()]).toString('utf-8')
console.log('decoded:', decoded)
# This script needs pycryptodome dependency
# pip install pycryptodome
from Crypto.Cipher import AES
from hashlib import md5
import base64
BLOCK_SIZE = AES.block_size
def get_aes(s):
m = md5()
m.update(s.encode('utf-8'))
key = m.hexdigest()
m = md5()
m.update((s + key).encode('utf-8'))
iv = m.hexdigest()
return AES.new(key.encode("utf8"), AES.MODE_CBC, iv.encode("utf8")[:BLOCK_SIZE])
# pkcs5 padding
def pad(byte_array):
pad_len = BLOCK_SIZE - len(byte_array) % BLOCK_SIZE
return byte_array + (bytes([pad_len]) * pad_len)
# pkcs5 - unpadding
def unpad(byte_array):
return byte_array[:-ord(byte_array[-1:])]
def _encrypt(s, data):
data = pad(data.encode("UTF-8"))
aes = get_aes(s)
encrypted = aes.encrypt(data)
return base64.urlsafe_b64encode(encrypted).decode('utf-8')
def _decrypt(s, edata):
edata = base64.urlsafe_b64decode(edata)
aes = get_aes(s)
return unpad(aes.decrypt(edata)).decode('utf-8')
if __name__ == '__main__':
secret = 'f8abb29f13cb932704badb0de414ab08ca9f6c63'
value = 'hello world'
encoded = _encrypt(secret, value)
print('encoded:', encoded)
decoded = _decrypt(secret, encoded)
print('decoded:', decoded)
Help from:
Implementing AES/ECB/PKCS5 padding in Python
Node.js - Set padding in crypto module
Python Encrypting with PyCrypto AES