AES decryption with PyCryptodome does not produce the expected result - javascript

I have some difficulties reproducing AES encryption and decryption in python.
Context: A year ago, I created a small django based application using this javascript library for client side encryption. Basically, some users' inputs are encrypted with a key and sent as hexadecimal strings to be stored.
For the illustration, I'll focus on bd45bcccd0 (a.k.a 'Masha' encrypted with john's key: 3ed8bd71327aafd855aac37921519767)
Encryption and decryption with the current js library
encryption utf-8 -> bytes -> encrypted bytes -> hex
decryption hex -> encrypted bytes -> bytes -> utf-8
id_password is a MD5 hash of the user's password. It is stored in the session storage and is used as a key
function encrypt(t){
var key = aesjs.utils.hex.toBytes(sessionStorage.getItem("id_password"));
var textBytes = aesjs.utils.utf8.toBytes(t);
var aesCtr = new aesjs.ModeOfOperation.ctr(key);
var encryptedBytes = aesCtr.encrypt(textBytes);
var encryptedHex = aesjs.utils.hex.fromBytes(encryptedBytes);
return encryptedHex;
}
function decrypt(t){
var key = aesjs.utils.hex.toBytes(sessionStorage.getItem("id_password"));
var textBytes = aesjs.utils.hex.toBytes(t);
var aesCtr = new aesjs.ModeOfOperation.ctr(key);
var decriptedBytes = aesCtr.decrypt(textBytes);
var decrypted_utf8 = aesjs.utils.utf8.fromBytes(decriptedBytes);
return decrypted_utf8;
}
Once loaded in key, I get a 16 items array (So I guess a AES 128bits CTR is performed):
var key = aesjs.utils.hex.toBytes(sessionStorage.getItem("id_password"));
console.log(key)
Array(16) [ 62, 216, 189, 113, 50, 122, 175, 216, 85, 170, … ]
With the current code, encryption and decryption work
Python implementation
For unit-testing purposes, I wanted to be able to decrypt. I am using this library. To mimic the client side as much as possible, I tried the following:
john_key = "3ed8bd71327aafd855aac37921519767"
cipher = AES.new(codecs.decode(john_key,'hex_codec'), AES.MODE_CTR)
d = cipher.decrypt(codecs.decode('bd45bcccd0', 'hex_codec'))
d.decode('utf-8')
UnicodeDecodeError: 'utf-8' codec can't decode byte 0xac in position 0: invalid start byte
Here is the problem but I am not sure at which stage it occurs. Here is what I checked:
# key's length is correct
k = codecs.decode(john_key,'hex_codec')
k
b'>\xd8\xbdq2z\xaf\xd8U\xaa\xc3y!Q\x97g'
len(k)
16
# decoded message's length is correct
d = cipher.decrypt(codecs.decode('bd45bcccd0', 'hex_codec'))
len(d)
5
Since I can't rely on a library I can't reproduce the results, I wonder whether I misused PyCryptodome or whether the way this javascript library implements AES CTR encryption is reliable. Any insights?

The CTR-mode requires an IV. Since you do not explicitly create the IV, an implicitly created IV is used. However, both codes generate different IVs, so that the decryption fails. In the Python-code, a random IV is generated, in the aes-js-code a fixed IV (1) is used.
So that the decryption is possible with the Python-code, the same IV must be used here as in the aes-js-code (here and here). For this purpose:
cipher = AES.new(codecs.decode(john_key,'hex_codec'), AES.MODE_CTR)
has to be replaced by
counter = Counter.new(128, initial_value = 1)
cipher = AES.new(codecs.decode(john_key,'hex_codec'), AES.MODE_CTR, counter = counter)
which decrypts the ciphertext to Maria (however not Masha).
For security reasons it is mandatory for CTR that key/IV pairs may only be used once, i.e. if the same key is applied, a new IV must be generated for each encryption. The current code has the weakness that key/IV pairs would be repeated when using the same key. A better way would be to generate a random IV for each encryption, send this IV together with the ciphertext to the recipient (the IV isn't secret, so it is usually prepended to the ciphertext), where it can be used for the decryption.

Related

Using AES-256-ctr to encode Id

I'm using Node's crypto native api to encode an id because it's going to be shown in an URL parameter, the problem is, it encodes in a way it can be guessed and I can't comprehend why, here's my code.
const encrypt = (key, value) => {
const iv = crypto.randomBytes(16);
const cipher = crypto.createCipheriv('aes-256-ctr', Buffer.from(key), iv);
let encrypted = cipher.update(value);
encrypted = Buffer.concat([encrypted, cipher.final()]);
return `${iv.toString('hex')}-${encrypted.toString('hex')}`;
}
what happens is that the AES encoded id outputs in a predictable way, for example, a number 5 outputs the code 160f20bea36be22f90b092f876f1abdd-55 and note that if I change the last two numbers to 56, the output became 6, or to 54, the id's discovered because it outputs 7.
What I want it to do is to give me a secure encoded id.
Your problem with predictability of output comes from the fact that you are using CTR mode. Let's see how CTR mode works:
CTR is a streaming mode. As you see, CTR mode works based on encrypting a counter which starts from 1 and XORing result with your plain data. Nonce in this picture is IV. This mechanism has a drawback as you mentioned. If we assume that C(i) is encrypted counter i, we have:
Cipher(i) = Plain(i) xor C(i)
Since C(i) does not depend on input, if you change Plain(i), Cipher(i) will be changed based on your Plain(i) change:
Cipher(i) = Plain(i) xor C(i) => C(i) = Cipher(i) xor Plain(i)
then for block i:
Cipher(x) = Plain(x) xor C(i) => Cipher(x) = Plain(x) xor Cipher(i) xor Plain(i)
XOR is add without carry. That's why you feel that your result is predictable. But this only happens IF your IV is fixed and as a result, shows importance of IV in CTR mode.
I should say that your code in general does not have any main issue since you use:
const iv = crypto.randomBytes(16);
to generate a new IV each time for encrypting and add that IV to your encoded result. Since you change IV every time, C(i) will be different for each encrypted data and no one can guess the new encrypted data. As an example:
input data encrypted output
---------------------------------------------------
5 160f20bea36be22f90b092f876f1abdd-16
6 160f20bea36be22f90b092f876f1abdd-15 // Feels predictable since IV is similar
6 160f20bea36be22f90b092f876f1abde-ee // Sample input, but completely deferent
// output since I changed 1 bit in IV
So in general, your code does not have any major problems cryptography-wise. But since you need to keep IV in your result for decryption, your encrypted data result will be large.

How to encrypt with CryptoJS and decrypt with Rust Magic Crypt

I'm attempting to encrypt a message using Javascript's CryptoJS like so:
const encrypted_payload = CryptoJS.AES.encrypt("Hello, world!", "magickey");
console.log(enrypted_payload.toString());
... and then decrypt using Rust's Magic Crypt Library, like so:
#[macro_use] extern crate magic_crypt;
use magic_crypt::MagicCryptTrait;
fn main() {
let message = r#"U2FsdGVkX1+anrxRX8UNTJ8ur9LIf6n2YcmbmDqPSls="#;
let mc = new_magic_crypt!("magickey", 256);
let result = mc.decrypt_base64_to_string(&message)
.expect("Could not decrypt base64 to string");
println!("{}", result)
}
The Cargo.toml file for Rust includes:
[dependencies]
magic-crypt = "3.1.6"
This fails to compile. The error statement is DecryptError(BlockModeError).
If in CryptoJS the key is passed as a string, then it is interpreted as a password and the key/IV is derived using a special derivation function (EVP_BytesToKey). To process the key directly as a key, it must be passed as a WordArray.
In the Rust code the passwords seem to be simply hashed (e.g. MD5 for AES-128, SHA256 for AES-256), which is surprisingly insecure (though I'm not a Rust expert and may be overlooking something).
Anyway this results in incompatible keys. One possibility is to use the hash of the Rust key as WordArray in CryptoJS:
Example: From the magic_crypt documentation, here:
use magic_crypt::MagicCryptTrait;
let mc = new_magic_crypt!("magickey", 256);
let base64 = mc.encrypt_str_to_base64("http://magiclen.org");
assert_eq!("DS/2U8royDnJDiNY2ps3f6ZoTbpZo8ZtUGYLGEjwLDQ=", base64);
assert_eq!("http://magiclen.org", mc.decrypt_base64_to_string(&base64).unwrap());
magic_crypt internally derives a 32 bytes key as SHA256 hash from the password. Then the encryption with CryptoJS is:
var key = CryptoJS.SHA256("magickey");
var ciphertext = "DS/2U8royDnJDiNY2ps3f6ZoTbpZo8ZtUGYLGEjwLDQ=";
var iv = CryptoJS.enc.Base64.parse("AAAAAAAAAAAAAAAAAAAAAA==")
var decrypted = CryptoJS.AES.decrypt(ciphertext, key, {iv: iv});
console.log(decrypted.toString(CryptoJS.enc.Utf8));
<script src="https://cdnjs.cloudflare.com/ajax/libs/crypto-js/4.0.0/crypto-js.min.js"></script>
Both codes apply CBC mode with a zero IV and PKCS7 padding. Note that a static IV is insecure and should only be used for testings.
The other direction is analogous, e.g. encryption with CryptoJS generates the ciphertext from the Rust example (assuming the same key, IV and plaintext):
var key = CryptoJS.SHA256("magickey");
var plaintext = "http://magiclen.org";
var iv = CryptoJS.enc.Base64.parse("AAAAAAAAAAAAAAAAAAAAAA==")
var ciphertext = CryptoJS.AES.encrypt(plaintext, key, {iv: iv});
console.log(ciphertext.toString());
<script src="https://cdnjs.cloudflare.com/ajax/libs/crypto-js/4.0.0/crypto-js.min.js"></script>

Convert Java AES encryption to Javascript using the crypto library

Im trying to convert the java library - AESCrypt-Java
to javascript.
This is my implementation so far for the decrypt function. Im not able to decrypt the text. Can someone figure out where I'm going wrong?
function decrypt(password, base64text) {
key = generateKey(password);
var decodedCipherText = new Buffer(base64text, 'base64')
var iv = new Buffer(16);
iv.fill(0);
var decipher = crypto.createDecipheriv("aes-256-cbc", key, iv)
let decrypted = decipher.update(decodedCipherText, 'base64', 'utf-8');
decrypted += decipher.final('utf-8')
return decryptedBytes
}
function generateKey(password) {
return crypto.createHash('sha256').update(usr_id).digest();
}
var encryptedText = '1+2yFMDH1C/uIc1huwezbrsQ==';
var password = '8AVrWtyabQ';
decrypt(password, encryptedText)
The expected plaintext output is Wordpress.
You are making a few decisions that will adversely affect the security of your sensitive values:
You are using a static, all-zero IV. The IV must be unique and non-predictable for every message encrypted with a specific key. The IV can then be prepended to the cipher text and transmitted unprotected to the recipient, where it is sliced and used for decryption.
Your key derivation function (KDF) is weak -- SHA-256 can be cracked at 23 billion attempts per second on commodity hardware. Use a key-stretching algorithm like PBKDF2 with a high iteration count, or bcrypt or scrypt for memory hardness.
Your cipher text is not authenticated -- AES/CBC provides confidentiality, but not integrity or authentication. An interceptor can manipulate the cipher text in transmission and attempt to decrypt it. This can result in unauthorized decryption (i.e. injecting malicious plaintext into your application) or a padding oracle attack, and eventually cipher text recovery. Use an authenticated encryption (with associated data) (AE or AEAD) cipher mode to mitigate this, or add a strong HMAC construction using a separate key over the cipher text and verify prior to decryption with a constant-time equals method.
new Buffer(string, encoding) and new Buffer(size) are deprecated and Buffer.from(string, encoding) and Buffer.alloc(size) should be used instead. You create a Buffer containing the provided cipher text which is encoded in Base64. I have a feeling there is an issue occurring with your encoding (you don't provide any example output for us to see). Here is an example of encrypting and decrypting with Buffer objects.
function encrypt(buffer){
var cipher = crypto.createCipher(algorithm,password)
var crypted = Buffer.concat([cipher.update(buffer),cipher.final()]);
return crypted;
}
function decrypt(buffer){
var decipher = crypto.createDecipher(algorithm,password)
var dec = Buffer.concat([decipher.update(buffer) , decipher.final()]);
return dec;
}
var hw = encrypt(new Buffer("hello world", "utf8"))
// outputs hello world
console.log(decrypt(hw).toString('utf8'));
As you can see, cipher.update(buffer) handles the encoding internally so you don't need to.

AES encode in Delphi XE4 decode in Javascript

I would like to AES encode in Delphi XE4 and decode in JavaScript.
My Delphi code:
(I use DCPcrypt Cryptographic Component Library v2 Beta 3)
procedure TForm1.Button5Click(Sender: TObject);
var
Cipher : TDCP_rijndael;
key: Ansistring;
data: Ansistring;
iv: Ansistring;
begin
Key := SHA256('password');
IV := 'cd6f6eea9a2a59f2';
Data := '12345678901234567890';
Cipher := TDCP_rijndael.Create(Self);
if Length(Key) <= 16 then
Cipher.Init(Key[1], 128, #IV[1])
else
if Length(Key) <= 24 then
Cipher.Init(Key[1], 192, #IV[1])
else
Cipher.Init(Key[1], 256, #IV[1]);
Cipher.EncryptCBC(Data[1],Data[1],Length(Data));
memo1.Lines.Add('DATA_ENC:'+DATA);
memo1.Lines.Add('DATA_BASE64_ENC: '+Base64encode(DATA));
end;
My JavaScript code (I use CryptoJS):
encypted = 'Pz8/yw0/ck+4tTY/Pn8zPz/f9D8='; //input base64 text from Delphi routine
var key = CryptoJS.SHA256(CryptoJS.enc.Base64.parse("password"));
var iv = CryptoJS.enc.Base64.parse('cd6f6eea9a2a59f2');
var decrypted = CryptoJS.AES.decrypt(encrypted,key,
keySize: 256,
iv: iv,
mode: CryptoJS.mode.CBC,
padding: CryptoJS.pad.ZeroPadding
});
console.log('DECRYPTED: '+decrypted.toString(CryptoJS.enc.Utf8));
I do not get back the original text, please help me. What is the matter?
I have no idea about Delphi, so I can't help you there, but I can say, that your Delphi code is wrong, because if you parse the Base64 ciphertext and encode it as Hex, you will see this:
3f3f3fcb0d3f724fb8b5363f3e7f333f3fdff43f
A ciphertext of a modern cipher is supposed to be indistinguishable from random noise, but this ciphertext looks rather regular (there are a lot of 0x3f bytes).
Your JavaScript code is rather all over the place. Almost every string that you use, has a wrong encoding.
run.onclick = function(){
var encrypted = CryptoJS.enc.Base64.parse(inVal.value);
var key = CryptoJS.SHA256(CryptoJS.enc.Utf8.parse("password"));
var iv = CryptoJS.enc.Utf8.parse('cd6f6eea9a2a59f2');
var decrypted = CryptoJS.AES.decrypt({
ciphertext: encrypted
}, key, {
iv: iv,
padding: CryptoJS.pad.ZeroPadding
});
outHex.innerHTML = decrypted.toString();
outUtf8.innerHTML = decrypted.toString(CryptoJS.enc.Utf8);
};
<script src="https://cdn.rawgit.com/CryptoStore/crypto-js/3.1.2/build/rollups/aes.js"></script>
<script src="https://cdn.rawgit.com/CryptoStore/crypto-js/3.1.2/build/rollups/sha256.js"></script>
<script src="https://cdn.rawgit.com/CryptoStore/crypto-js/3.1.2/build/components/pad-zeropadding-min.js"></script>
<div>Base64 input: <input id="inVal" value="Pz8/yw0/ck+4tTY/Pn8zPz/f9D8="></div>
<div>Decrypted Hex: <span id="outHex">-</span></div>
<div>Decrypted Utf8: <span id="outUtf8">-</span></div>
<div><button id="run">Decrypt</button></div>
When you have fixed your Delphi code, you can include the Base64 in the above runnable snippet and see that decrypts correctly.
Security considerations:
You need to use a random IV, if you're sending multiple ciphertexts with the same key. If you send the same message again, an attacker can see that only by observing ciphertexts. The IV doesn't have to be secret, so you can send it along with the ciphertext. A common way is to prepend it to the ciphertext and remove it before decryption.
SHA-256 is not sufficient for key derivation from a low-entropy password. You should use an iterated key derivation function (KDF) such as PBKDF2, bcrypt, scrypt or Argon2. See more: How to securely hash passwords?
It is better to authenticate your ciphertexts so that attacks like a padding oracle attack are not possible. This can be done with authenticated modes like GCM or EAX, or with an encrypt-then-MAC scheme.

Using Forge to encrypt/decrypt AES

I want to create two functions encrypt(message, key) and decrypt(ciphertext, key) using the Forge library in javascript, but I dont undestand the example code.
// generate a random key and IV
var key = forge.random.getBytesSync(16);
var iv = forge.random.getBytesSync(16);
// encrypt some bytes using CBC mode
// (other modes include: CFB, OFB, and CTR)
var cipher = forge.aes.createEncryptionCipher(key, 'CBC');
cipher.start(iv);
cipher.update(forge.util.createBuffer(someBytes));
cipher.finish();
var encrypted = cipher.output;
// outputs encrypted hex
console.log(encrypted.toHex());
// decrypt some bytes using CBC mode
// (other modes include: CFB, OFB, and CTR)
var cipher = forge.aes.createDecryptionCipher(key, 'CBC');
cipher.start(iv);
cipher.update(encrypted);
cipher.finish();
// outputs decrypted hex
console.log(cipher.output.toHex());
// generate a password-based 16-byte key
var salt = forge.random.getBytesSync(128);
var derivedKey = forge.pkcs5.pbkdf2('password', salt, numIterations, 16);
Where should I use my own key?
Where can I choose 256 bit mode?
Can you give me an easier example?
Where should I use my own key?
I haven't used that library but it seems pretty straight forward. Take this part at the top:
// generate a random key and IV
var key = forge.random.getBytesSync(16);
And put your key in like this:
// generate a random key and IV
var key = neverGuessMahKeyIs1234;
Do the same for the iv if you want.
Where can I choose 256 bit mode?
Ok, so first of all your dealing with symmetric encryption which has a key length of the desired size. Because it's symmetric, it's used on both the encrypting and decrypting ends, which is what the code that you posted seems to do. I say 'seems' because I'm trusting that the library's native functions are as you posted them.
So, the code as you posted seems to use (as I showed above) 128 bits (16*8=128). If you want a random 256, then just use:
var key = forge.random.getBytesSync(32);
Or just make your own key that 256 bits long.

Categories