Triple DES encryption in JavaScript and decryption in PHP - javascript

I am using JavaScript to encrypt and php to decrypt the string and vice versa but the problem is that on both the platforms the output being generated is different say If I encrypt a string "abc" on both the platforms they will produce different results although I am sure my encryption is correct because the string I am encrypting is decrypted in same language.
I understands that in this case there has to be something different in key or iv but do not know what it
Javascript code to encrypt string
var encrypted = CryptoJS.TripleDES.encrypt("Message", "SecretPassphrase");
console.log(encrypted);console.log(String(encrypted));
var text = "<some plain text goes here>";
var key = "00f74597de203655b1ebf5f410f10eb8";
var useHashing = true;
if (useHashing) {
key = CryptoJS.MD5(key).toString();
key += key.substring(1, 16);
console.log(key);
}
var textWordArray = CryptoJS.enc.Utf16.parse(text);
var keyHex = CryptoJS.enc.Hex.parse(key);
var iv = String.fromCharCode(0) + String.fromCharCode(0) + String.fromCharCode(0) + String.fromCharCode(0) + String.fromCharCode(0) + String.fromCharCode(0) + String.fromCharCode(0) + String.fromCharCode(0);
var ivHex = CryptoJS.enc.Hex.parse(iv);
console.log('hexadecimal key: ' + keyHex + '\n');
console.log('iv: ' + iv + '\n');
console.log('hexadecimal iv: ' + ivHex + '\n');
var options = {
mode: CryptoJS.mode.CBC,
padding: CryptoJS.pad.Pkcs7,
iv: ivHex
};
var encrypted = CryptoJS.TripleDES.encrypt(textWordArray, keyHex, options);
var base64String = encrypted.toString();
console.log('base64: ' + base64String + '\n');
var decrypted = CryptoJS.TripleDES.decrypt({
ciphertext: CryptoJS.enc.Base64.parse(base64String)
}, keyHex, options);
console.log('decrypted: ' + decrypted.toString(CryptoJS.enc.Utf16));
PHP code to encrypt string
//Generate a key from a hash
$key = md5(utf8_encode("00f74597de203655b1ebf5f410f10eb8"), true);
//Take first 8 bytes of $key and append them to the end of $key.
$key .= substr($key, 0, 8);
//Padding for 3DES
$blockSize = mcrypt_get_block_size('tripledes', 'ecb');
$len = strlen($value);
$pad = $blockSize - ($len % $blockSize);
$value .= str_repeat(chr($pad), $pad);
//Generating iv for 3DES
$iv = chr(0) . chr(0) . chr(0) . chr(0) . chr(0) . chr(0) . chr(0) . chr(0);
//Encrypt data
$encData = mcrypt_encrypt(MCRYPT_3DES, $key, $value, MCRYPT_MODE_CBC, $iv);
$value = base64_encode($encData);
Example
If I encrypt the string "admin" from javascript it gives me "U2FsdGVkX1+y/zo1FJEZZ0aqPMQuwilOydbJjzIKpYw="
Where as php give me "AzZFzbnwp2Y="
Note I am using CryptoJSv3 plugin for triple DES*

MD5 produces output of 128 bit, but a Triple DES key should be 192 bit long. That is why your PHP code copies the first 64 bits of the produced hash to the back. PHP and CryptoJS both use the EDE and this key copy leads to the key layout of K1 || K2 || K1.
You can do the same thing in CryptoJS. Since CryptoJS uses a WordArray to manage binary data internally as words of 32 bit, you can directly copy the first two words to the back of the internal array.
var pt = "admin";
document.querySelector("#pt").innerHTML = pt;
var key = "00f74597de203655b1ebf5f410f10eb8";
key = CryptoJS.MD5(key);
// copy 3DES subkey 1 to the last 64 bit to make a full 192-bit key
key.words[4] = key.words[0];
key.words[5] = key.words[1];
// create a 64-bit zero filled
var iv = CryptoJS.lib.WordArray.create(64/8);
var encrypted = CryptoJS.TripleDES.encrypt(pt, key, {iv: iv});
var encryptedBase64 = encrypted.toString();
document.querySelector("#enc").innerHTML = encryptedBase64;
var ct = {
ciphertext: CryptoJS.enc.Base64.parse(encryptedBase64)
};
var decrypted = CryptoJS.TripleDES.decrypt(ct, key, {iv: iv});
document.querySelector("#dec").innerHTML = decrypted.toString(CryptoJS.enc.Utf8);
<script src="https://cdn.rawgit.com/CryptoStore/crypto-js/3.1.2/build/rollups/tripledes.js"></script>
<script src="https://cdn.rawgit.com/CryptoStore/crypto-js/3.1.2/build/rollups/md5.js"></script>
<p>Expected: "AzZFzbnwp2Y="<br/>Got: "<span id="enc"></span>"</p>
<p>Expected: "<span id="pt"></span>"<br/>Got: "<span id="dec"></span>"</p>
This code is not very secure for those reasons:
It uses Triple DES which only provides 112 bit of security, at best. You should use AES.
It uses a static IV. This is not semantically secure, because an attacker might see whether you sent the same message only by observing the ciphertexts. You need to use a random IV for each encryption and send it along with the ciphertext.
It doesn't use authentication. This system might be vulnerable to the padding oracle attack. It is an online attack which enables the attacker to decrypt any ciphertext with multiple tries. You either need to use an authenticated mode like GCM/EAX (not advisable for Triple DES) or run an HMAC-SHA256 over the ciphertext to produce the authentication tag and send it along with the ciphertext to verify it on the receiving end.

Related

PHP / Javascript AES 128 CFB Encryption / Decryption CryptoJS OpenSSL

Working Decryption in PHP
function decrypt($encryptedText){
$key = "1234567890123456";
$decrypted = openssl_decrypt(substr(base64_decode($encryptedText), 16), "AES-128-CFB", $key, OPENSSL_RAW_DATA, substr(base64_decode($encryptedText), 0, 16));
return $decrypted;
}
$encryptedText = "1EwMDT20BELrEixvrSGswDC4GZIn5BWGor6MP6ERi9Ux";
echo decrypt($encryptedText);
Will output "Decrypted Message"
I'm trying to replicate it in Javascript
var key = '1234567890123456';
var base64data = CryptoJS.enc.Base64.parse("1EwMDT20BELrEixvrSGswDC4GZIn5BWGor6MP6ERi9Ux");
var encrypted = new CryptoJS.lib.WordArray.init(base64data.words.slice(4));
var iv = new CryptoJS.lib.WordArray.init(base64data.words.slice(0, 4));
var cipher = CryptoJS.lib.CipherParams.create({ ciphertext: encrypted });
var decrypted = CryptoJS.AES.decrypt(cipher, CryptoJS.enc.Utf8.parse(key), {iv: iv, mode: CryptoJS.mode.CFB});
console.log("Decrypted: "+decrypted.toString(CryptoJS.enc.Utf8));
I get an empty response, any help would be appreciated as to where I've gone wrong.
Thanks
CFB is a stream cipher mode, i.e. does not use padding.
In a mode with padding, the ciphertext always has a length that corresponds to an integer multiple of the block size, i.e. 16 bytes for AES. This means that the ciphertext always consists of complete WordArrays (a WordArray is 4 bytes in size).
In a mode without padding, such as CFB, the ciphertext has the same length as the plaintext, so that the last WordArray is generally not completely part of the ciphertext.
For instance, the message Decrypted Message has a length of 17 bytes, i.e. consists of 5 WordArrays, the last of which has only one significant byte.
Therefore the number of significant bytes: base64data.sigBytes - 16, must be specified for encrypted (the subtraction of 16 is necessary because base64data also contains the IV).
Furthermore, the padding must be explicitly disabled.
With these two changes, the decryption is successful:
var key = '1234567890123456';
var base64data = CryptoJS.enc.Base64.parse("1EwMDT20BELrEixvrSGswDC4GZIn5BWGor6MP6ERi9Ux");
var encrypted = new CryptoJS.lib.WordArray.init(base64data.words.slice(4), base64data.sigBytes - 16); // consider significant bytes
var iv = new CryptoJS.lib.WordArray.init(base64data.words.slice(0, 4));
var cipher = CryptoJS.lib.CipherParams.create({ ciphertext: encrypted });
var decrypted = CryptoJS.AES.decrypt(cipher, CryptoJS.enc.Utf8.parse(key), {iv: iv, mode: CryptoJS.mode.CFB, padding: CryptoJS.pad.NoPadding}); // disable padding
document.getElementById("pt").innerHTML = "Decrypted: " + decrypted.toString(CryptoJS.enc.Utf8);
<script src="https://cdnjs.cloudflare.com/ajax/libs/crypto-js/4.0.0/crypto-js.min.js"></script>
<p style="font-family:'Courier New', monospace;" id="pt"></p>
By the way, there are different variants of CFB. Here, both codes use full block CFB (CFB128).

AES Encryption in PHP and Decryption in Javascript

I have an application in which I am encrypting a json encoded array using AES CBC 128 algorithm and then Decrypting it in javascript(React/Next Js Project). My Encryption in php is as shown in the below code
ENCRYPTION PHP
$plaintext = "message to be encrypted";
$ivlen = openssl_cipher_iv_length($cipher="AES-128-CBC");
$iv = openssl_random_pseudo_bytes($ivlen);
$ciphertext_raw = openssl_encrypt($plaintext, $cipher, $key, $options=OPENSSL_RAW_DATA, $iv);
$hmac = hash_hmac('sha256', $ciphertext_raw, $key, $as_binary=true);
$ciphertext = base64_encode( $iv.$hmac.$ciphertext_raw );
I am facing problems in decryption of this in Javascript
my code so far is as shown below
const baseenc = CryptoJS.enc.Base64.parse(cipher).toString();
var encrypted = CryptoJS.AES.decrypt(cipher, key, { iv: iv }).toString();
var plaintext = CryptoJS.enc.Latin1.stringify(encrypted);
Can any body please show what is the error or help me in getting the correct output
The following steps must be implemented in the CryptoJS code:
Separate IV, HMAC and ciphertext (after Base64 decoding)
Calculate the HMAC for the ciphertext
Check the authenticity of the ciphertext. The ciphertext is authentic if the received and calculated HMAC are identical.
Perform decryption, only if the ciphertext is authentic
The following code is a possible implementation. As key 0123456789012345 was applied and with the PHP code the used ciphertext was generated:
var ciphertext = 'WqfMfCxKg7U7h5S1mbx7mSHOkkkIrUUpg++mX4ZdWt0I26VfKn7bsi60Oo/SIsWQGyC4dF5z081NvjTXwZGjIpguA0k/QqIM/GDEpCojaro=';
var key = '0123456789012345';
// Convert key and ciphertext into WordArrays
var ciphertextWA = CryptoJS.enc.Base64.parse(ciphertext);
var keyWA = CryptoJS.enc.Utf8.parse(key);
// Separate IV, HMAC and ciphertext
var ivWA = CryptoJS.lib.WordArray.create(ciphertextWA.words.slice(0, 4));
var hmacWA = CryptoJS.lib.WordArray.create(ciphertextWA.words.slice(4, 4 + 8));
var actualCiphertextWA = CryptoJS.lib.WordArray.create(ciphertextWA.words.slice(4 + 8));
// Authenticate
var hmacCalculatedWA = CryptoJS.HmacSHA256(actualCiphertextWA, keyWA);
if(CryptoJS.enc.Base64.stringify(hmacCalculatedWA) === CryptoJS.enc.Base64.stringify(hmacWA)) {
// Decrypt if authentication is successfull
var decryptedMessageWA = CryptoJS.AES.decrypt({ciphertext: actualCiphertextWA}, keyWA, {iv: ivWA});
var decryptedMessage = CryptoJS.enc.Utf8.stringify(decryptedMessageWA);
console.log(decryptedMessage);
} else {
console.log('Authentication failed!');
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/crypto-js/4.0.0/crypto-js.min.js"></script>
Please note that it is better to use different keys for encryption and authentication, see here.

AES-256 produces different ciphertexts in iOS and Node.js with same password

So I have my iOS code:
#import <CommonCrypto/CommonCrypto.h>
NSString* password = #"1234567890123456";
NSString* salt = #"gettingsaltyfoo!";
-(NSString *)decrypt:(NSString*)encrypted64{
NSMutableData* hash = [NSMutableData dataWithLength:CC_SHA256_DIGEST_LENGTH];
NSMutableData* key = [NSMutableData dataWithLength:CC_SHA256_DIGEST_LENGTH];
CC_SHA256(salt.UTF8String, (CC_LONG)strlen(salt.UTF8String), hash.mutableBytes);
CCKeyDerivationPBKDF(kCCPBKDF2, password.UTF8String, strlen(password.UTF8String), hash.bytes, hash.length, kCCPRFHmacAlgSHA1, 1000, key.mutableBytes, key.length);
NSLog(#"Hash : %#",[hash base64EncodedStringWithOptions:0]);
NSLog(#"Key : %#",[key base64EncodedStringWithOptions:0]);
NSData* encryptedWithout64 = [[NSData alloc] initWithBase64EncodedString:encrypted64 options:0];
NSMutableData* decrypted = [NSMutableData dataWithLength:encryptedWithout64.length + kCCBlockSizeAES128];
size_t bytesDecrypted = 0;
CCCrypt(kCCDecrypt,
kCCAlgorithmAES128,
kCCOptionPKCS7Padding,
key.bytes,
key.length,
NULL,
encryptedWithout64.bytes, encryptedWithout64.length,
decrypted.mutableBytes, decrypted.length, &bytesDecrypted);
NSData* outputMessage = [NSMutableData dataWithBytes:decrypted.mutableBytes length:bytesDecrypted];
NSString* outputString = [[NSString alloc] initWithData:outputMessage encoding:NSUTF8StringEncoding];
NSLog(#"Decrypted : %#",outputString);
return outputString;
}
-(NSString *)encrypt:(NSString *)toEncrypt{
NSMutableData* hash = [NSMutableData dataWithLength:CC_SHA256_DIGEST_LENGTH];
NSMutableData* key = [NSMutableData dataWithLength:CC_SHA256_DIGEST_LENGTH];
CC_SHA256(salt.UTF8String, (CC_LONG)strlen(salt.UTF8String), hash.mutableBytes);
CCKeyDerivationPBKDF(kCCPBKDF2, password.UTF8String, strlen(password.UTF8String), hash.bytes, hash.length, kCCPRFHmacAlgSHA1, 1000, key.mutableBytes, key.length);
NSData* message = [toEncrypt dataUsingEncoding:NSUTF8StringEncoding];
NSMutableData* encrypted = [NSMutableData dataWithLength:message.length + kCCBlockSizeAES128];
size_t bytesEncrypted = 0;
CCCrypt(kCCEncrypt,
kCCAlgorithmAES128,
kCCOptionPKCS7Padding,
key.bytes,
key.length,
NULL,
message.bytes, message.length,
encrypted.mutableBytes, encrypted.length, &bytesEncrypted);
NSString* encrypted64 = [[NSMutableData dataWithBytes:encrypted.mutableBytes length:bytesEncrypted] base64EncodedStringWithOptions:0];
NSLog(#"Encrypted : %#",encrypted64);
return encrypted64;
}
and I have my node.js code:
var CryptoJS = require("crypto-js");
var crypto = require('crypto');
var password = "1234567890123456";
var salt = "gettingsaltyfoo!";
var hash = CryptoJS.SHA256(salt);
var key = CryptoJS.PBKDF2(password, hash, { keySize: 256/32, iterations: 1000 });
var algorithm = 'aes128';
function encrypt(text){
var cipher = crypto.createCipher(algorithm,key.toString(CryptoJS.enc.Base64));
var crypted = cipher.update(text,'utf8','hex');
crypted += cipher.final('hex');
console.log(crypted);
console.log(hash.toString(CryptoJS.enc.Base64));
console.log(key.toString(CryptoJS.enc.Base64));
return crypted;
}
function decrypt(text){
var decipher = crypto.createDecipher(algorithm,key.toString(CryptoJS.enc.Base64));
var dec = decipher.update(text,'hex','utf8');
dec += decipher.final('utf8');
console.log(dec);
return dec;
}
Question: Unfortunately, though I have the same hash, key, and eventually decrypted value (meaning they can work independently), I get different encrypted values. So in one code, if I take the encrypted value and try to decrypt it in another, I get an error. When I go from iOS to node I get this error:
ERROR:digital envelope routines:EVP_DecryptFinal_ex:wrong final block length CODE: decrypt('vfOzya0yV9G5hLHeSh3R1g==');
Also I get these different encrypted values for the string "Hello World":
IOS: vfOzya0yV9G5hLHeSh3R1g==
NODE: 13b51a6785f47d8601c3a612d41b9a8b
How can I resolve this matter so that I can interop my iOS and Node.js, and in the future Android. I know my hashing algorithm is right for producing the SHA256 and PBDKF2 because I get the same hash and key. This means that somewhere my implementation is wrong for AES128 upon encrypting my password. Most likely my iOS code. Please let me know where my error is.
You don't need to use CryptoJS, because node.js' crypto module provides everything you need for this to work. CryptoJS has a different binary representation than node.js' native Buffer, so there will be problem using both in conjunction.
Problems:
You're using crypto.createCipher() which will derive the key from a password on its own in an OpenSSL compatible format. You want to use crypto.createCipheriv().
You're not passing an IV to in Objective-C which defaults to a zero filled IV. You need to do the same in node.js by initializing a zero-filled Buffer.
You provide the key in Base64 encoded form in node.js, but you have to provide the bytes (Buffer).
Since the key size is 256 bit you're actually using AES-256 and not AES-128. The CommonCrypto code seems to change automatically to 256 bit despite specifying 128 bit, but node.js requires you to specify 256 bit explicitly. Also, "aes128" or "aes256" will default to ECB mode in node.js, but CommonCrypto defaults to CBC mode, so you need to explicitly specify this.
Full working code:
var crypto = require('crypto');
var password = "1234567890123456";
var salt = "gettingsaltyfoo!";
var sha256 = crypto.createHash("sha256");
sha256.update(salt);
var hash = sha256.digest();
var key = crypto.pbkdf2Sync(password, hash, 1000, 32, "sha1");
var iv = new Buffer(16);
iv.fill(0);
var algorithm = 'aes-256-cbc';
function encrypt(text){
var cipher = crypto.createCipheriv(algorithm, key, iv);
var crypted = cipher.update(text,'utf8','base64');
crypted += cipher.final('base64');
return crypted;
}
function decrypt(text){
var decipher = crypto.createDecipheriv(algorithm, key, iv);
var dec = decipher.update(text,'base64','utf8');
dec += decipher.final('utf8');
return dec;
}
console.log(encrypt("Hello World"));
Output:
vfOzya0yV9G5hLHeSh3R1g==
Other considerations:
You need to generate a random IV for every encryption that you do. If you don't do this, then an attacker may see that you encrypted the same message multiple times without actually decrypting it if you use the same key every time. Since you derive a key from a password, then you can do this a bit better by generating a random salt and derive 384 bit (48 byte) from PBKDF2. Use the first 32 byte for the key and the rest for the IV.
You need to authenticate the ciphertexts. If you don't then an attacker might mount a padding oracle attack on your system. You can easily do this by running an HMAC over the ciphertext and send the resulting tag along with it. You can then verify the tag before decryption by running the HMAC again over the received ciphertext in order to check for manipulation.
Or you could use an authenticated mode like GCM.

SJCL not concatenating bit arrays

I am trying to use RNCryptor-JS which uses SJCL but for some reason, SJCL bit array concatenation does not seem to work.
var SALT_SIZE = 64/8;
var plaintext = "Hello, World!";
var password = "myPassword";
function keyForPassword(password, salt){
// Using CryptoJS for pbkdf2, aes, sha256, and random word arrays
var pbkdf2_key = CryptoJS.PBKDF2(
password,
salt,
{
keySize: 256/32,
iterations: 1000,
hasher: CryptoJS.algo.SHA256
}
);
return pbkdf2_key;
}
var encryption_salt = CryptoJS.lib.WordArray.random(SALT_SIZE);
var encryption_key = keyForPassword(password, encryption_salt);
var hmac_salt = CryptoJS.lib.WordArray.random(SALT_SIZE);
var hmac_key = keyForPassword(password, hmac_salt);
var iv = CryptoJS.lib.WordArray.random(128/8);
var version = sjcl.codec.hex.toBits("03");
var options = sjcl.codec.hex.toBits("01");
var message = sjcl.bitArray.concat(version, iv);
message = sjcl.bitArray.concat(message, encryption_salt);
message = sjcl.bitArray.concat(message, hmac_salt);
message = sjcl.bitArray.concat(message, iv);
// Progressive cipher
var aesEncryptor = CryptoJS.algo.AES.createEncryptor(encryption_key, {iv: iv});
var ciphertext = aesEncryptor.process(plaintext);
message = sjcl.bitArray.concat(message, ciphertext);
var hmac = new sjcl.misc.hmac(hmac_key).encrypt(message);
var encrypted_data = sjcl.bitArray.concat(message, hmac);
var output = sjcl.codec.hex.fromBits(encrypted_data);
console.log(output);
When I log the output of message after the first set of sjcl.bitArray.concat is done, all that returns is the first concatenation of version and iv. The final hex output is just that first concatenation and hmac concatenated. This reinforces my suspicion that it might be CryptoJS's fault because the output concatenation works and is between two sjcl variables.
I tried using SJCL random bit arrays but had some trouble. SJCL's generator, prng, did not work when using
new sjcl.prng.randomWords(32/4);
or
new sjcl.prng(32/4);
And sjcl.random.randomWords does not seem to work anymore.
CryptoJS (WordArray) and SJCL (bitArray) have different internal representations of data. You can't simply concatenate them.
The easiest way would be probably to encode it into an intermediate format such as Hex and let the other side decode into its internal format:
message = sjcl.bitArray.concat(version, sjcl.codec.hex.toBits(iv.toString()));
WordArray#toString() automatically uses Hex encoding. You would have to do this for all lines, but this is a little overkill, since you can concatenate Hex strings as strings:
message = sjcl.codec.hex.toBits("03" + iv + encryption_salt + hmac_salt + iv);
This should work as expected, because adding a WordArray such as iv to a string automatically calls its toString() function which in turn produces a big-endian hex-encoded string.
I wonder why you're using iv twice. Perhaps you meant options on one of them.
What needs to change:
function convert(wordArray){
return sjcl.codec.hex.toBits(wordArray.toString());
}
var message = "0301" + encryption_salt + hmac_salt + iv;
var ciphertext = CryptoJS.AES.encrypt(plaintext, encryption_key, {iv: iv}).ciphertext;
message += ciphertext;
message = sjcl.codec.hex.toBits(message);
var hmac = new sjcl.misc.hmac(convert(hmac_key)).encrypt(message);
var encrypted_data = sjcl.bitArray.concat(message, hmac);
var output = sjcl.codec.hex.fromBits(encrypted_data);
console.log(output);

CryptoJS and Pycrypto working together

I'm encrypting a string in a web application using CryptoJS (v 2.3), and I need to decrypt it on the server in Python, so I'm using PyCrypto. I feel like I'm missing something because I can't can it working.
Here's the JS:
Crypto.AES.encrypt('1234567890123456', '1234567890123456',
{mode: new Crypto.mode.CBC(Crypto.pad.ZeroPadding)})
// output: "wRbCMWcWbDTmgXKCjQ3Pd//aRasZ4mQr57DgTfIvRYE="
The python:
from Crypto.Cipher import AES
import base64
decryptor = AES.new('1234567890123456', AES.MODE_CBC)
decryptor.decrypt(base64.b64decode("wRbCMWcWbDTmgXKCjQ3Pd//aRasZ4mQr57DgTfIvRYE="))
# output: '\xd0\xc2\x1ew\xbb\xf1\xf2\x9a\xb9\xb6\xdc\x15l\xe7\xf3\xfa\xed\xe4\xf5j\x826\xde(m\xdf\xdc_\x9e\xd3\xb1'
Here is a version with CryptoJS 3.1.2. Always beware of the following things (use the same in both languages):
Mode of operation (CBC in this case)
Padding (Zero Padding in this case; better use PKCS#7 padding)
Key (the same derivation function or clear key)
Encoding (same encoding for key, plaintext, ciphertext, ...)
IV (generated during encryption, passed for decryption)
If a string is passed as the key argument to the CryptoJS encrypt() function, the string is used to derive the actual key to be used for encryption. If you wish to use a key (valid sizes are 16, 24 and 32 byte), then you need to pass it as a WordArray.
The result of the CryptoJS encryption is an OpenSSL formatted ciphertext string. To get the actual ciphertext from it, you need to access the ciphertext property on it.
The IV must be random for each encryption so that it is semantically secure. That way attackers cannot say whether the same plaintext that was encrypted multiple times is actually the same plaintext when only looking at the ciphertext.
Below is an example that I have made.
JavaScript:
var key = CryptoJS.enc.Utf8.parse('1234567890123456'); // TODO change to something with more entropy
function encrypt(msgString, key) {
// msgString is expected to be Utf8 encoded
var iv = CryptoJS.lib.WordArray.random(16);
var encrypted = CryptoJS.AES.encrypt(msgString, key, {
iv: iv
});
return iv.concat(encrypted.ciphertext).toString(CryptoJS.enc.Base64);
}
function decrypt(ciphertextStr, key) {
var ciphertext = CryptoJS.enc.Base64.parse(ciphertextStr);
// split IV and ciphertext
var iv = ciphertext.clone();
iv.sigBytes = 16;
iv.clamp();
ciphertext.words.splice(0, 4); // delete 4 words = 16 bytes
ciphertext.sigBytes -= 16;
// decryption
var decrypted = CryptoJS.AES.decrypt({ciphertext: ciphertext}, key, {
iv: iv
});
return decrypted.toString(CryptoJS.enc.Utf8);
}
Python 2 code with pycrypto:
BLOCK_SIZE = 16
key = b"1234567890123456" # TODO change to something with more entropy
def pad(data):
length = BLOCK_SIZE - (len(data) % BLOCK_SIZE)
return data + chr(length)*length
def unpad(data):
return data[:-ord(data[-1])]
def encrypt(message, key):
IV = Random.new().read(BLOCK_SIZE)
aes = AES.new(key, AES.MODE_CBC, IV)
return base64.b64encode(IV + aes.encrypt(pad(message)))
def decrypt(encrypted, key):
encrypted = base64.b64decode(encrypted)
IV = encrypted[:BLOCK_SIZE]
aes = AES.new(key, AES.MODE_CBC, IV)
return unpad(aes.decrypt(encrypted[BLOCK_SIZE:]))
Warning: Keep in mind that both python2 and pycrypto are obsolete, so the code has to be adjusted to fit python3 and pycryptodome.
Other considerations:
It seems that you want to use a passphrase as a key. Passphrases are usually human readable, but keys are not. You can derive a key from a passphrase with functions such as PBKDF2, bcrypt or scrypt.
The code above is not fully secure, because it lacks authentication. Unauthenticated ciphertexts may lead to viable attacks and unnoticed data manipulation. Usually the an encrypt-then-MAC scheme is employed with a good MAC function such as HMAC-SHA256.
I had to port a Javascript implementation of AES encryption/decryption which was using crypto-js library, to Python3.
Basically, my approach was to run the debugger on the existing JS code and look at variables getting filled in each step. I was able to figure out the equivalent methods to do the same in python as well.
Here is how I ported it using pycryptodome library which has some useful features.
AES.js
var CryptoJS = require("crypto-js");
var Base64 = require("js-base64");
function decrypt(str, secret) {
try {
var _strkey = Base64.decode(secret);
var reb64 = CryptoJS.enc.Hex.parse(str);
var text = reb64.toString(CryptoJS.enc.Base64);
var Key = CryptoJS.enc.Base64.parse(_strkey.split(",")[1]); //secret key
var IV = CryptoJS.enc.Base64.parse(_strkey.split(",")[0]); //16 digit
var decryptedText = CryptoJS.AES.decrypt(text, Key, { keySize: 128 / 8, iv: IV, mode: CryptoJS.mode.CBC, padding: CryptoJS.pad.Pkcs7 });
return decryptedText.toString(CryptoJS.enc.Utf8); //binascii.unhexlify(decryptedText)
} catch (e) {
console.log("Error", e)
}
}
function encrypt(str, secret) {
str = Math.random().toString(36).substring(2, 10) + str;
var _strkey = Base64.decode(secret);
_strkey.split(",");
var text = CryptoJS.enc.Utf8.parse(str);
var Key = CryptoJS.enc.Base64.parse(_strkey.split(",")[1]); //secret key
var IV = CryptoJS.enc.Base64.parse(_strkey.split(",")[0]); //16 digit
var encryptedText = CryptoJS.AES.encrypt(text, Key, { keySize: 128 / 8, iv: IV, mode: CryptoJS.mode.CBC, padding: CryptoJS.pad.Pkcs7 });
var b64 = encryptedText.toString();
var e64 = CryptoJS.enc.Base64.parse(b64);
var eHex = e64.toLocaleString(CryptoJS.enc.Hex);
return eHex.toUpperCase();
}
const secret = "V1VWTVRFOVhJRk5WUWsxQlVrbE9SUT09LFRrOUNUMFJaSUZkSlRFd2dTMDVQVnc9PQ=="
const data = "THIS IS MY SECRET MESSAGE!"
encData = EncryptText2(data, secret)
decData = DecryptText2(encData, secret)
console.log("encryptedData", encData)
console.log("decryptedData", decData)
AESify.py
import string
import random
import base64
import binascii
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad, unpad
class AESify:
def __init__(self, key=None, iv=None,secret = None, block_len=16, salt_len= 8):
self.key = key
self.iv = iv
self.salt_len = salt_len
self.block_len = block_len
self.mode = AES.MODE_CBC
if(secret):
self.useSecret(secret)
if(self.key is None and self.iv is None):
raise Exception("No key , IV pair or secret provided")
#staticmethod
def makeSecret(key, iv):
if(len(key) % 8 != 0):
raise Exception("Key length must be a mutliple of 8")
if(len(iv) % 8 != 0):
raise Exception("Initial vector must be a multiple of 8")
key64 = base64.b64encode(key.encode()).decode()
iv64 = base64.b64encode(iv.encode()).decode()
secret = iv64 + "," + key64
secret64 = base64.b64encode(secret.encode()).decode()
return secret64
def useSecret(self, secret):
iv64, key64 = base64.b64decode(secret).decode().split(",") # decode and convert to string
self.iv = base64.b64decode(iv64)
self.key = base64.b64decode(key64)
return self
def encrypt(self, text):
text = self.add_salt(text, self.salt_len)
cipher = AES.new(self.key, self.mode, self.iv)
text = cipher.encrypt(pad(text.encode('utf-8'), self.block_len))
return binascii.hexlify(text).decode()
def decrypt(self, data):
text = binascii.unhexlify(data) # UNHEX and convert the encrypted data to text
cipher = AES.new(self.key, self.mode, self.iv)
return unpad(cipher.decrypt(text), self.block_len).decode('utf-8')[self.salt_len:]
def add_salt(self, text, salt_len):
# pre-pad with random salt
salt = ''.join(random.choice(string.ascii_letters + string.digits) for _ in range(salt_len))
text = salt + text
return text
main.py
from AESify import AESify
key , iv = "NOBODY WILL KNOW", "YELLOW SUBMARINE"
# contains IV and key
secret = AESify.makeSecret(key, iv)
aes = AESify(secret= secret, block_len=16, salt_len=4)
msg = "THIS IS MY SECRET MESSAGE"
encrypted = aes.encrypt(msg)
decrypted = aes.decrypt(encrypted)
print(f"{secret=}")
print(f"{encrypted=}")
print(f"{decrypted=}")
Note : salt , iv , padding should be same in js and python
generate salt and iv value and convert it into a byte string uisng CryptoJS.enc.Utf8.parse()
js file
var encrypted = CryptoJS.AES.encrypt(JSON.stringify(json_data), CryptoJS.enc.Utf8.parse(data['salt']) , { iv: CryptoJS.enc.Utf8.parse(data['iv']) , mode: CryptoJS.mode.CBC , padding: CryptoJS.pad.Pkcs7});
en_data = encrypted.ciphertext.toString(CryptoJS.enc.Base64)
send this encrypted data to the python file
python file
from Crypto.Util.Padding import pad, unpad
ct = request.POST['encrypted_data']
data = base64.b64decode(ct)
cipher1 = AES.new(salt, AES.MODE_CBC, iv)
pt = unpad(cipher2.decrypt(data), 16)
data = json.loads(pt.decode('utf-8'))
pad and upad in pycrypto by default uses pkcs#7
salt and iv value should in byte string

Categories