CryptoJS equivalent of PyCrypto - python

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

Related

Unable to decrypt aes-128-cbc in Node when encrypted in Python

I made the following MRE to illustrate my problem. My Python code is taking a string, encrypting it with an ad-hoc created key and posting it to a Node server that is supposed to use the provided key for decryption.
Using the console output I can see that the Python output (key and encrypted message) are the same on both systems (after base64 conversion for transfer).
Also from another post on stackoverflow, I understand that the key generated by Pyhon is already base64 encoded and the decoded value needs to be split to extract the iv and key respectively.
However, I get an error saying that the iv length is invalid. I am not sure what I am doing wrong. Thanks for your hints.
Python Code fernet.py (Python 3.10.5 on Windows 11)
import requests
import base64
from cryptography.fernet import Fernet
def get_key():
key = Fernet.generate_key()
return key
def enc(input, key):
f = Fernet(key)
encrypted = f.encrypt(input.encode())
return encrypted
msg = "hello world"
key = get_key()
cypher = enc(msg, key)
print("msg: " + msg)
print("key: " + key.decode())
print("cyp: " + cypher.decode())
url='http://.../test'
headers = {'Content-type': 'application/json'}
r = requests.post(url, data={"key": key, "msg": base64.b64encode(cypher)})
Output:
msg: hello world
key: bOe3HCoveTPhHi04FvvG697O2rEkwF9vuXeZcGgbu9k=
cyp: gAAAAABi0WYLxwlMG7RGKqISZABsPlmenUmN8Ii81rJBIQdSPGzi1-IHss4atZuGp4eZIdaiR7mpBah6cQmlXxw2DzY3Ct9d7A==
Here is my node server (12.22.09 running on a Ubuntu 22.04 VM)
const express = require('express');
const url = require('url');
const crypto = require('crypto');
const app = express();
const port = 8088;
app.use(express.urlencoded({extended: true, limit:'100mb', parameterLimit:1000000}));
app.disable('x-powered-by');
function decrypt(message, fernet) {
let iv = Buffer.from(fernet, 'base64').toString('binary', 0, 16)
let key = Buffer.from(fernet, 'base64').toString('binary', 16,32);
let decoded_msg = Buffer.from(message, 'base64').toString('utf8');
let decipher = crypto.createDecipheriv('aes-128-cbc', key, iv);
let decrypted = decipher.update(decoded_msg, 'binary', 'utf8') + decipher.final('utf8');
console.log(decrypted);
return decrypted
}
app.post("/test", function(req, res) {
let fernet = req.body.key;
let message = req.body.msg;
console.log("key: " + fernet);
console.log("msg: " + Buffer.from(message, 'base64').toString('utf8'));
let dec = decrypt(message, fernet);
console.log("dec: " + dec);
res.writeHead(200, {"Content-Type": "application/json"});
res.end(JSON.stringify({"status": "OK"}));
});
app.listen(port, () => {
console.log("Listening on localhost:" + port);
});
Output:
key: bOe3HCoveTPhHi04FvvG697O2rEkwF9vuXeZcGgbu9k=
msg: gAAAAABi0WYLxwlMG7RGKqISZABsPlmenUmN8Ii81rJBIQdSPGzi1-IHss4atZuGp4eZIdaiR7mpBah6cQmlXxw2DzY3Ct9d7A==
Error: Invalid IV length
at Decipheriv.createCipherBase (internal/crypto/cipher.js:103:19)
at Decipheriv.createCipherWithIV (internal/crypto/cipher.js:121:20)
at new Decipheriv (internal/crypto/cipher.js:262:22)
at Object.createDecipheriv (crypto.js:131:10)
at decrypt (/.../app.js:55:24)

Decrypting data with Python3 that was already encrypted

I have some data that was encrypted with AES in Java. I would now like to decrypt in Python.
For reference here is the decrypt Java code:
public static String decryptAES(String input, String key) throws EncryptionException {
String clearText = null;
byte[] keyBytes = key.getBytes();
SecretKeySpec keySpec = new SecretKeySpec(keyBytes, "AES");
try {
Cipher cipher = Cipher.getInstance("AES/ECB/NoPadding");
cipher.init(1, keySpec);
// We need to revert our plus sign replacements from above
input = input.replaceAll(Pattern.quote("_"), "+");
byte[] decodedInput = Base64.decodeBase64(input.getBytes());
byte[] clearTextBytes = cipher.doFinal(decodedInput);
clearText = new String(clearTextBytes);
clearText = StringUtils.strip(clearText, "{");
} catch (Exception ex) {
throw new EncryptionException(ex);
}
return clearText;
}
Here is what I have
from Crypto.Cipher import AES
encryptionKey = "]zOW=Rf*4*5F^R+?frd)G3#J%tH#qt_#"
encryptedData = "Hx8mA8afdgsngdfCgfdg1PHZsdfhIshfgdesd4rfgdk="
cipher = AES.new(encryptionKey.encode(), AES.MODE_ECB)
plain = cipher.decrypt(encryptedData.encode())
print(plain)
But I am getting a "ValueError: Data must be aligned to block boundary in ECB mode"
I did google and did find some suggestions like ValueError: Data must be aligned to block boundary in ECB mode but I couldn't really get it to work. No idea what the block size should be
Decoding with Base64 as suggested by #kelalaka solves the problem of Value error, but the output seems to be just random bytes:
import base64
from Crypto.Cipher import AES
encryptionKey = "]zOW=Rf*4*5F^R+?frd)G3#J%tH#qt_#"
encryptedData = "Hx8mA8afdgsngdfCgfdg1PHZsdfhIshfgdesd4rfgdk="
data = base64.b64decode(encryptedData)
cipher = AES.new(encryptionKey.encode(), AES.MODE_ECB)
plain = cipher.decrypt(data)
print(plain)
Output:
b'\xcfh(\xb5\xec%(*^\xd4\xd3:\xde\xfb\xd9R<B\x8a\xb2+=\xbf\xc2%\xb0\x14h\x10\x14\xd3\xbb'

Trouble with AES Cryptography (specifically encrypting with CryptoSwift and decrypting with PyCryptodome

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()```

encrypt using node.js crypto aes256 and decrypt using python2.7 PyCrypto

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.

Decrypting AES and HMAC with PyCrypto

Having a bit of trouble getting a AES cipher text to decrypt.
In this particular scenario, I am encrypting data on the client side with Crypto-JS and decrypting it back on a python server with PyCrypto.
encrypt.js:
var password = 'BJhtfRjKnTDTtPXUBnErKDxfkiMCOLyP';
var data = 'mytext';
var masterKey = CryptoJS.SHA256(password).toString();
// Derive keys for AES and HMAC
var length = masterKey.toString().length / 2
var encryptionKey = masterKey.substr(0, length);
var hmacKey = masterKey.substr(length);
var iv = CryptoJS.lib.WordArray.random(64/8);
var encrypted = CryptoJS.AES.encrypt(
data,
encryptionKey,
{
iv: iv,
mode: CryptoJS.mode.CFB
}
);
var concat = iv + encrypted;
// Calculate HMAC using iv and cipher text
var hash = CryptoJS.HmacSHA256(concat, hmacKey);
// Put it all together
var registrationKey = iv + encrypted + hash;
// Encode in Base64
var basemessage = btoa(registrationKey);
decrypt.py:
class AESCipher:
def __init__(self, key):
key_hash = SHA256.new(key).hexdigest()
# Derive keys
encryption_key = key_hash[:len(key_hash)/2]
self.key = encryption_key
self.hmac_key = key_hash[len(key_hash)/2:]
def verify_hmac(self, input_cipher, hmac_key):
# Calculate hash using inputted key
new_hash = HMAC.new(hmac_key, digestmod=SHA256)
new_hash.update(input_cipher)
digest = new_hash.hexdigest()
# Calculate hash using derived key from local password
local_hash = HMAC.new(self.hmac_key, digestmod=SHA256)
local_hash.update(input_cipher)
local_digest = local_hash.hexdigest()
return True if digest == local_digest else False
def decrypt(self, enc):
enc = base64.b64decode(enc)
iv = enc[:16]
hmac = enc[60:]
cipher_text = enc[16:60]
# Verify HMAC using concatenation of iv + cipher like in js
verified_hmac = self.verify_hmac((iv+cipher_text), self.hmac_key)
if verified_hmac:
cipher = AES.new(self.key, AES.MODE_CFB, iv)
return cipher.decrypt(cipher_text)
password = 'BJhtfRjKnTDTtPXUBnErKDxfkiMCOLyP'
input = 'long base64 registrationKey...'
cipher = AESCipher(password)
decrypted = cipher.decrypt(input)
I'm successful in re-calculating the HMAC but when I try and then decrypt the cipher I get something that seems encrypted with �'s in the result.
I was getting errors about input length of cipher text but when I switched to CFB mode that fixed it so I don't think it's a padding issue.
There are many problems with your code.
Client (JavaScript):
AES has a block size of 128 bit and CFB mode expects a full block for the IV. Use
var iv = CryptoJS.lib.WordArray.random(128/8);
The iv and hash variables are WordArray objects, but encrypted is not. When you force them to be converted to strings by concatenating them (+), iv and hash are Hex-encoded, but encrypted is formatted in an OpenSSL compatible format and Base64-encoded. You need to access the ciphertext property to get the encrypted WordArray:
var concat = iv + encrypted.ciphertext;
and
var registrationKey = iv + encrypted.ciphertext + hash;
registrationKey is hex-encoded. There is no need to encode it again with Base64 and bloat it even more:
var basemessage = registrationKey;
If you want to convert the hex encoded registrationKey to base64 encoding, use:
var basemessage = CryptoJS.enc.Hex.parse(registrationKey).toString(CryptoJS.enc.Base64);
concat is a hex-encoded string of the IV and ciphertext, because you forced the stringification by "adding" (+) iv and encrypted. The HmacSHA256() function takes either a WordArray object or a string. When you pass a string in, as you do, it will assume that the data is UTF-8 encoded and try to decode it as UTF-8. You need to parse the data yourself into a WordArray:
var hash = CryptoJS.HmacSHA256(CryptoJS.enc.Hex.parse(concat), hmacKey);
The CryptoJS.AES.encrypt() and CryptoJS.HmacSHA256() expect the key either as a WordArray object or as a string. As before, if the key is supplied as a string, a UTF-8 encoding is assumed which is not the case here. You better parse the strings into WordArrays yourself:
var encryptionKey = CryptoJS.enc.Hex.parse(masterKey.substr(0, length));
var hmacKey = CryptoJS.enc.Hex.parse(masterKey.substr(length));
Server (Python):
You're not verifying anything in verify_hmac(). You hash the same data with the same key twice. What you need to do is hash the IV+ciphertext and compare the result with the hash (called tag or HMAC-tag) that you slice off the full ciphertext.
def verify_hmac(self, input_cipher, mac):
# Calculate hash using derived key from local password
local_hash = HMAC.new(self.hmac_key, digestmod=SHA256)
local_hash.update(input_cipher)
local_digest = local_hash.digest()
return mac == local_digest
And later in decrypt():
verified_hmac = self.verify_hmac((iv+cipher_text), hmac)
You need to correctly slice off the MAC. The 60 that is hardcoded is a bad idea. Since you're using SHA-256 the MAC is 32 bytes long, so you do this
hmac = enc[-32:]
cipher_text = enc[16:-32]
The CFB mode is actually a set of similar modes. The actual mode is determined by the segment size. CryptoJS only supports segments of 128 bit. So you need tell pycrypto to use the same mode as in CryptoJS:
cipher = AES.new(self.key, AES.MODE_CFB, iv, segment_size=128)
If you want to use CFB mode with a segment size of 8 bit (default of pycrypto), you can use a modified version of CFB in CryptoJS from my project: Extension for CryptoJS
Full client code:
var password = 'BJhtfRjKnTDTtPXUBnErKDxfkiMCOLyP';
var data = 'mytext';
var masterKey = CryptoJS.SHA256(password).toString();
var length = masterKey.length / 2
var encryptionKey = CryptoJS.enc.Hex.parse(masterKey.substr(0, length));
var hmacKey = CryptoJS.enc.Hex.parse(masterKey.substr(length));
var iv = CryptoJS.lib.WordArray.random(128/8);
var encrypted = CryptoJS.AES.encrypt(
data,
encryptionKey,
{
iv: iv,
mode: CryptoJS.mode.CFB
}
);
var concat = iv + encrypted.ciphertext;
var hash = CryptoJS.HmacSHA256(CryptoJS.enc.Hex.parse(concat), hmacKey);
var registrationKey = iv + encrypted.ciphertext + hash;
console.log(CryptoJS.enc.Hex.parse(registrationKey).toString(CryptoJS.enc.Base64));
Full server code:
from Crypto.Cipher import AES
from Crypto.Hash import HMAC, SHA256
import base64
import binascii
class AESCipher:
def __init__(self, key):
key_hash = SHA256.new(key).hexdigest()
self.hmac_key = binascii.unhexlify(key_hash[len(key_hash)/2:])
self.key = binascii.unhexlify(key_hash[:len(key_hash)/2])
def verify_hmac(self, input_cipher, mac):
local_hash = HMAC.new(self.hmac_key, digestmod=SHA256)
local_hash.update(input_cipher)
local_digest = local_hash.digest()
return SHA256.new(mac).digest() == SHA256.new(local_digest).digest() # more or less constant-time comparison
def decrypt(self, enc):
enc = base64.b64decode(enc)
iv = enc[:16]
hmac = enc[-32:]
cipher_text = enc[16:-32]
verified_hmac = self.verify_hmac((iv+cipher_text), hmac)
if verified_hmac:
cipher = AES.new(self.key, AES.MODE_CFB, iv, segment_size=128)
return cipher.decrypt(cipher_text)
else:
return 'Bad Verify'
password = 'BJhtfRjKnTDTtPXUBnErKDxfkiMCOLyP'
input = "btu0CCFbvdYV4B/j7hezAra6Q6u6KB8n5QcyA32JFLU8QRd+jLGW0GxMQsTqxaNaNkcU2I9r1ls4QUPUpaLPQg=="
obj = AESCipher(password)
decryption = obj.decrypt(input)
print 'Decrypted message:', decryption

Categories