Porting AES decryption from CryptoJS to PyCrypto - javascript

Here is a JavaScript part which decodes a string with AES encryption
var p = 'some large string'
var s = 'Q05WTmhPSjlXM1BmeFd0UEtiOGg='
var y = CryptoJS.AES.decrypt({
ciphertext: CryptoJS.enc.Base64.parse(p)
}, CryptoJS.enc.Base64.parse(s), {
iv CryptoJS.enc.Hex.parse("random")
});
var v = y.toString(CryptoJS.enc.Utf8)
I am trying to code a similar decoding function in python with importing AES.
Could anyone help me with this one. I can't figure out all equivalent code for js to python.
I looked up this page
Python AES Decryption Routine (Code Help)
and
AES - Encryption with Crypto (node-js) / decryption with Pycrypto (python)
Not sure if they have the code similar to the js I have here
"y.toString(CryptoJS.enc.Utf8)"
This in python what it means
I have tried something like this from another source
from base64 import b64decode
from Crypto.Cipher import AES
iv = 'random'
key = 'Q05WTmhPSjlXM1BmeFd0UEtiOGg='
encoded = b64decode('some large string')
dec = AES.new(key=key, mode=AES.MODE_CBC, IV=iv)
value = dec.decrypt(encoded)

There are multiple problems with your CryptoJS code and Python code.
Wrong key size
Your key s contains only 20 bytes (160 bit) which doesn't constitute any of the valid key sizes for AES which are 128 (10), 192 (12) and 256 bit (14 rounds). CryptoJS will silently run the key schedule for a 160 bit key with 11 rounds which PyCrypto doesn't support (see AES.c).
You can reduce the key to 128 bit like this in CryptoJS:
var key = CryptoJS.enc.Base64.parse('Q05WTmhPSjlXM1BmeFd0UEtiOGg=');
key.sigBytes = 16;
key.clamp();
or in Python:
key = b64decode('Q05WTmhPSjlXM1BmeFd0UEtiOGg=')[:16]
Wrong character encoding
You forgot to decode the key from a Base64 string in Python and you forgot to decode the IV from hex. The character '0' and the byte 0x00 are entirely different. There's an easier way to define an all zero IV:
iv = "\0"*16
No unpadding
CryptoJS uses PKCS#7 padding by default, but PyCrypto doesn't implement any padding and only handles data as a multiple of the block size. After you decrypt something, you need to remove the padding yourself in Python:
value = value[:value[-1]]
(the last byte determines how many bytes are padding bytes). More on that here.
Other considerations:
You really shouldn't be setting the IV to a static value. The IV should be randomly generated for every encryption using the same key. Otherwise, you will lose semantic security. Since the IV doesn't have to be secret, you can put it in front of the ciphertext and slice it off before decryption.

Related

Ruby OpenSSL to node.js Crypt conversion

I have been struggling with this for a couple of days and was wondering if anyone would have the experience to know these two encryption libraries well enough to help.
I am currently creating a SSO payload according to instructions given to me by a vendor. The steps to have this created are highlighted as follows:
Create an AES 256 CBC cypher of the payload
i. The key will be a SHA256 digest of the site token.
2. Base64 encode the initialization vector (IV) and encrypted payload from above
3. CGI-escape the output from step 2.
4. Your final payload would look something like ikUbqiutwMhi%2Bjg6WwUHyeZB76g6LdLGcrKrEV4YpvQ%3D%0A.
SHA256 will always generate a 32-byte hash, but it can’t be displayed nicely in Base64. When it’s displayed as Hex, it is 32 pairs of Hex values (a total of 64 characters on the screen) but representing only 32 bytes.
I was able to get it to work on Ruby with Open SSL, the code is:
require 'digest'
require 'openssl'
require "base64"
require 'cgi'
require 'json'
cipher = OpenSSL::Cipher.new('aes-256-cbc')
cipher.encrypt
cipher.key = Digest::SHA256.digest(siteToken)
iv = cipher.random_iv
data= unencryptedPayload
encrypted = cipher.update(JSON.generate(data)) + cipher.final
encoded = CGI::escape(Base64.encode64(iv + encrypted))
puts encoded
However, I have not yet had luck with Node.js's Crypto library. This is what I have so far:
const crypto = require('crypto');
// Defining algorithm
const algorithm = 'aes-256-cbc';
// Defining key
//'key' variable is defined and equal to siteToken in the OpenSSL version
//const key = siteToken;
// Defining iv
const iv = crypto.randomBytes(16);
// An encrypt function
function encrypt(text) {
// Creating Cipheriv with its parameter
let cipher = crypto.createCipheriv(
'aes-256-cbc', Buffer.from(key), iv);
// Updating text
let encrypted = cipher.update(text);
// Using concatenation
encrypted = Buffer.concat([encrypted, cipher.final()]);
// Returning iv and encrypted data
return { iv: iv.toString('hex'),
encryptedData: encrypted.toString('hex') };
}
// Displays output
var output = encrypt(unencryptedPayload);
I think my code has so far covered almost all of these except for the SHA256 digest of the site token. Does anyone know how I might achieve this in Node.js terms?
Thanks!

AES ECB Mode giving different results when using different languages

I am trying to pass an AES encrypted string from a python script into a nodejs script, using ECB mode. The code used is:
To start, I use pycryptodome to encrypt a string into AES
from Crypto.Cipher import AES
key = b'ipu9TUv54yv]isFMh5#;t.5w34E2Ry#{'
cipher = AES.new(key, AES.MODE_ECB)
print(cipher.encrypt(b"foobar "))
This gives me the string \xb0\x07\x93\xf3\x02\xd0\x87\xa4\xaek\x1bS\xccg\xa4H.
However, when i try to reverse the effect with Javascript:
var crypto = require('crypto')
let key = Buffer.from('ipu9TUv54yv]isFMh5#;t.5w34E2Ry#{');
let decipher = crypto.createDecipheriv("aes-256-ecb", key, '');
let result = decipher.update(Buffer.from('\xb0\x07\x93\xf3\x02\xd0\x87\xa4\xaek\x1bS\xccg\xa4H'));
console.log(result.toString())
It gives me a completely different result from the original text: �k��gR�O
Is there something that I am missing that is changing the way that it decrypts?
There are two core issues:
On the node side, you're treating the output of Python as if it's a UTF-8 string. Node will treat it as a UTF-8 string, and the resulting bytes that make up the Buffer are going to be wrong. Dump it out, you'll see it's a 25 byte buffer, not what you intended.
Once you fix that, you'll find the second issue. The crypto library expects padding bytes, even if the only block is exactly the block size. To fix this, always add padding to the plaintext.
So, the encrypt changes to this:
from Crypto.Cipher import AES
key = b'ipu9TUv54yv]isFMh5#;t.5w34E2Ry#{'
cipher = AES.new(key, AES.MODE_ECB)
# Don't need to ensure the plain text is exactly block-size anymore
data = b'foobar'
# Pad it, regardless of it's size
length = 16 - (len(data) % 16)
data += bytes([length]) * length
# And encode the encrypted text. Using hex here, it's easy, though
# often base64 is used
print(cipher.encrypt(data).hex())
And decoding in Node:
var crypto = require('crypto')
let key = Buffer.from('ipu9TUv54yv]isFMh5#;t.5w34E2Ry#{');
let decipher = crypto.createDecipheriv("aes-256-ecb", key, '');
// Using the hex encoding, let Buffer decode it
let result = decipher.update(Buffer.from('bf8242c6046ad5cb47e733dca4d487f1', 'hex'));
// Make sure to give decipher a chance to operate on the final block
result += decipher.final();
console.log(result.toString())
This outputs foobar as expected.

Encrypt with CryptoJS and decrypt from Objective-C

In communication between two applications, I'd like to encrypt a piece of information in JavaScript and decrypt the message from an Objective-C client using a fixed key (just for basic security).
Encryption works well:
var command = "mjallo";
var crypto_key = CryptoJS.enc.Base64.parse('280f8bb8c43d532f389ef0e2a5321220');
var crypto_iv = CryptoJS.enc.Base64.parse("CC0A69779E15780A");
// Encrypt and encode
var encrypted = CryptoJS.AES.encrypt(command, crypto_key, {iv: crypto_iv}).toString();
var encrypted_and_encoded = btoa(encrypted);
// encrypted_and_encoded => 'dFBQVDZZS3dGSktoa0J3Y1NQOElpZz09'
// Confirms that decrypt works with CryptoJS:
// Decode and decrypt
var decrypted = CryptoJS.AES.decrypt(atob(encrypted_and_encoded), crypto_key, {iv: crypto_iv});
// decrypted => 'mjallo'
How would you go about decoding and decrypting the message in Objective-c after it was encrypted by CryptoJS?
I've attempted to decrypt using CocoaSecurity, but with no luck. Following is RubyMotion syntax:
begin
res = CocoaSecurity.aesDecryptWithBase64('dFBQVDZZS3dGSktoa0J3Y1NQOElpZz09', hexKey: '280f8bb8c43d532f389ef0e2a5321220', hexIv: 'CC0A69779E15780A')
rescue NSException => e
p e.reason # => "Length of iv is wrong. Length of iv should be 16(128bits)"
end
AES supports a block size of 128 bit and key sizes of 128, 192 and 256 bit. The IV for CBC mode (which is the default) should be 128 bit.
Your encoded key consists of 32 characters. In CryptoJS you're parsing it as Base64 which results in a 192 bit key, but in CocoaSecurity you're assuming that it is Hex encoded. Since it only contains digits and the letters a to f, it's likely Hex encoded and not Base64 encoded. If one would assume that it is Hex encoded, then one would get a valid AES key size of 128 bit:
var crypto_key = CryptoJS.enc.Hex.parse('280f8bb8c43d532f389ef0e2a5321220');
Your IV on the other hand doesn't have a valid size under the same assumption. An IV should be 16 bytes long for AES in CBC mode. Additionally, an IV should never be fixed at a static value. You would need to generate a random IV for every encryption. Since the IV doesn't have to be secret, you can send it along with the ciphertext.
var crypto_iv = CryptoJS.lib.WordArray.random(128/8);
console.log("IV: " + crypto_iv.toString()); // hex encoded
The result of CryptoJS.<Cipher>.encrypt() is a special formattable object. If you call toString() on that object, you will get a Base64 encoded ciphertext (optionally with a salt when password-based encryption was used). But then you're encoding it again with Base64 by calling btoa(). You don't need to encode it twice.
var encrypted = CryptoJS.AES.encrypt(command, crypto_key, {iv: crypto_iv}).toString();
console.log("Ciphertext (Base64): " + encrypted.toString());
console.log("Ciphertext (Hex): " + encrypted.ciphertext.toString());
As far I can judge, your RubyMotion code looks fine.
If you can only change the CocoaSecurity code, then you will need to
re-encode the key by decoding it as Base64 and encoding it as Hex,
append 16 "0" characters to the IV hex string, because CryptoJS fills the IV up to the next valid IV with 0x00 bytes,
decode the ciphertext once from Base64.
You should always authenticate the ciphertexts. This can either be done with an authenticated mode like GCM or with an HMAC over the ciphertext.

Google CryptoJS AES result too long by 1 block

Hello I'm using the following passphrase: "test".
This generates the AES key: "9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08"
IV is 0.
As a test I tried to encrypt this key. Since I will always use this program with 256 bits I cut the encrypted cipher after 64 hex characters (256 bits).
However when I decrypt this cut ciphertext I'm missing the last 16 characters (128bits) of my key.
This is the size of one AES block, but my key is 256 bits so it seems weird to me.
My question is: Why is my AES result too long? And: Can I do this correctly so its 64 chars only?
(CryptoJS is the google crypto library for javascript.)
function go(){
var shakey = CryptoJS.SHA256(document.getElementById("t3").value); //Textbox key here.
var hash =CryptoJS.enc.Hex.parse(CryptoJS.enc.Hex.stringify(shakey));
var key = CryptoJS.enc.Hex.stringify(hash);
var iv = CryptoJS.enc.Hex.parse('00000000000000000000000000000000');
var encHex = CryptoJS.enc.Hex.parse(document.getElementById("t1").value);
var encrypted = CryptoJS.AES.encrypt(encHex, hash, { iv: iv }); //Textbox input here.
var encObj = {ciphertext:CryptoJS.enc.Hex.parse(document.getElementById("t2").value)}; //Textbox decrypt here
var decrypted = CryptoJS.AES.decrypt(encObj, hash, { iv: iv});
var encResult = (encrypted.ciphertext+"").length > 63 ? (encrypted.ciphertext+"").substring(0,64) : (encrypted.ciphertext+"");
document.getElementById("p1").innerHTML=encResult;
document.getElementById("p2").innerHTML=decrypted;
}
Disable padding using the NoPadding option. The default is PKCS#7 padding, which will always apply padding to the plaintext before encryption. In the case of a full block, it will pad out another full block, which makes the result one block longer than you would expect.

How to decrypt AES with SJCL

I want to decrypt AES by given cipher and key with the Stanford Javascript Crypto Library (SJCL), but i can't pass the key:
var key = 'key';
var cipher = 'abjslö';
var aes = new sjcl.cipher.aes(key);
var plaintext = aes.decrypt(cipher);
alert(plaintext);
This dosen't work. Referred to the documentation, the key has to be "an array of 4, 6 or 8 words".
How could this be done?
The key has to be an AES key, which is 128, 192 or 256 bits. The SJCL library however operates on 32 bit machine "words". Check out the source of the open source library or one of the tests to find out what to pass. Note that a password is not a key, you need a password based key derivation function such as PBKDF2 to convert a password into a key.
Encryption
Create an array of random words which will
serve as our IV(initialisation vector).
Then you need to create a bit array using a random key(size depends upon level
of encryption and the type)
Then you create a cypher using the key array.
And finally encode your data using the cypher and the IV. (you can
also add meta data to check the authenticity)
Now just concat the IV and the encrypted bit array and finally
convert it to a base64 string and pass.
Please note that you cannot decrypt a AES-GCM without IV.
Decryption
While decrypting slice the IV from the encryted string.
Now using the key create a cypher and use that to decrypt the string.
You can find the complete code here.

Categories

Resources