Using Forge to encrypt/decrypt AES - javascript

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.

Related

How can I write a client side cryption same as node js server side? [duplicate]

How to convert the following Node's built-in crypto module encryption to CryptoJS?
const crypto = require('crypto');
const pass = 'some,password:)with>spec(chars*'
const cipher1 = crypto.createCipher('aes-256-cbc', pass)
const c1 = cipher1.update(input, 'utf8', 'hex') + cipher1.final('hex')
I tried something like this, but the results are not the same:
const CryptoJS = require('crypto-js');
const pass = 'some,password:)with>spec(chars*'
const cipher2 = CryptoJS.AES.encrypt(input, pass, {
mode: CryptoJS.mode.CBC,
});
const c2 = cipher2.ciphertext.toString(CryptoJS.enc.Hex);
I need this to use as a Postman prerequest script as it does not support Node's crypto, but crypto-js.
Both codes use the OpenSSL proprietary key derivation function EVP_BytesToKey() with an iteration count of 1 and MD5 as digest.
NodeJS does not use a salt, while CryptoJS applies a random salt. For this reason, the NodeJS result is unchanged for each encryption, while the CryptoJS result always changes (assuming the same plaintext and passphrase).
Thus, to get the result of the NodeJS code with the CryptoJS code, you must not use a salt. However, by default, a salt is always applied. This can only be circumvented by explicitly determining key and IV with the key derivation function EvpKDF and then using both in the encryption:
var input = "The quick brown fox jumps over the lazy dog";
var pass = 'some,password:)with>spec(chars*'
var keySize = 32/4;
var ivSize = 16/4;
var kdf = CryptoJS.algo.EvpKDF.create({ keySize: keySize + ivSize, hasher: CryptoJS.algo.MD5 }).compute(pass, ''); // no salt!
var key = CryptoJS.lib.WordArray.create(kdf.words.slice(0, keySize), keySize * 4);
var iv = CryptoJS.lib.WordArray.create(kdf.words.slice(keySize), ivSize * 4);
var ciphertextCP = CryptoJS.AES.encrypt(input, key, {iv: iv}); // default: CBC, PKCS#7 padding
var ciphertext = ciphertextCP.ciphertext.toString(CryptoJS.enc.Hex);
document.getElementById("ct").innerHTML = ciphertext; // d98cf2d285bf0c1d796226190bf54d9c5540300ee1c6f35618f8bb3564b5053920ec958d31b41bbe4e4880e23543d709
<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="ct"></p>
This CryptoJS code produces the same ciphertext for the same plaintext and passphrase as the NodeJS code.
Note that the key derivation with EVP_BytesToKey() and the chosen parameters is deprecated and considered insecure today. This is true for NodeJS to a greater extent than for CryptoJS, due to the lack of a salt.
It is more secure to avoid the built-in key derivation function and specify the key directly.
To do this, use createCipheriv() in NodeJS and pass the key as WordArray in the CryptoJS code. This way, a random IV must be explicitly generated for each encryption.
Optionally, a reliable key derivation function like PBKDF2 can be used, which is supported by both libraries.

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.

AES decryption with PyCryptodome does not produce the expected result

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.

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.

encrypt message with AES-CBC with PKCS#7 padding using forge.js

I need to apply PKCS#7 padding to data which is being digested with AES-CBC algorithm. I use forge lib. Here's my currently working code (without the padding):
// transform json data to bytes
var data = {foo: 'bar'};
var stringData = JSON.stringify(data);
var bytes = forge.util.createBuffer(stringData);
// generate 16-byte vector iv
var iv = forge.random.getBytesSync(16);
// ciphering process
var cipher = forge.cipher.createCipher('AES-CBC', key);
cipher.start({iv: iv});
cipher.update(bytes);
cipher.finish();
var encrypted = cipher.output;
So from the theoretical point of view I know that I should append 1-16 bytes to my bytes variable before passing it to aes algorithm. Is this kind of operation built into the forge lib? If not, could someone help me with implementing application of padding to the message?
Althought the documentation does not mention it, seems the default padding for AES-CBC is PKCS#7. See this thread and the comments of cipherModes.js
modes.cbc.prototype.pad = function(input, options) {
// add PKCS#7 padding to block (each pad byte is the
// value of the number of pad bytes)

Categories