Nodejs, Crypto: Encrypting multiple strings using same cipher - node.js

I am trying to encrypt multiple strings using same cipher with the code
var iv = _crypto.randomBytes(16).slice(0, 12);
var salt = _crypto.randomBytes(64);
var key = _crypto.pbkdf2Sync(symmetricKey, salt, 2145, 32, 'sha512');
var cipher = _crypto.createCipheriv('aes-256-gcm', key,iv);
var a = Buffer.concat([cipher.update("data1", 'utf8'), cipher.final()]);
var b = Buffer.concat([salt, iv, a]).toString('base64');
var c = Buffer.concat([cipher.update("data2", 'utf8'), cipher.final()]);
The execution failed in the last line without showing any error.
"TypeError: error:00000000:lib(0):func(0):reason(0)"
On investigating further, I came to know that we can't use the cipher once we have done cipher.final(). But if I won't do it earlier(during encryption of data1), during decryption of "encrypted format of data1", it would fail since cipher.final returns any remaining enciphered contents which gets appended with the original encrypted string.
What's the best way to encrypt multiple strings then, or should I create separate ciphers for all the strings?

Related

PBEWithHmacSHA256AndAES_128 encryption in nodejs

I'm trying to encrypt a string using PBEWithHmacSHA256AndAES_128 in nodejs, however I'm having a bit of trouble determining the correct way to do it.
Lots of documentation state I can use the crypto library, which when I try crypto.getCiphers() I see 'aes-128-cbc-hmac-sha256' is supported.
I've tried various tutorials, https://www.geeksforgeeks.org/node-js-crypto-createcipheriv-method/ and such but I'm mainly hitting "Invalid key length" or "Invalid initialization vector" when I try to change the cipher type.
Could anyone point me to some documentation or code samples that may assist in achieving this?
PBEWithHmacSHA256AndAES_128 and aes-128-cbc-hmac-sha256 refer to different things.
Both encrypt with AES-128 in CBC mode, but the former uses a key derivation, the latter a MAC (more precisely an HMAC) for authentication.
Regarding NodeJS, the latter has apparently never worked reliably. In some versions exceptions are generated, in others no authentication is performed (i.e. the processing is functionally identical to AES-128-CBC), see here. This is not surprising since OpenSSL only intends this to be used in the context of TLS, see here, which of course also applies to NodeJS as this is just an OpenSSL wrapper.
But since you are concerned with PBEWithHmacSHA256AndAES_128, the aes-128-cbc-hmac-sha256 issues are in the end not relevant. PBEWithHmacSHA256AndAES_128 uses PBKDF2 (HMAC/SHA256) as key derivation, which is supported by NodeJS. A possible implementation that is functionally identical to PBEWithHmacSHA256AndAES_128 is:
var crypto = require("crypto");
// Key derivation
var password = 'my passphrase';
var salt = crypto.randomBytes(16); // some random salt
var digest = 'sha256';
var length = 16;
var iterations = 10000;
var key = crypto.pbkdf2Sync(password, salt, iterations, length, digest);
// Encryption
var iv = crypto.randomBytes(16); // some random iv
var cipher = crypto.createCipheriv('AES-128-CBC', key, iv);
var encrypted = Buffer.concat([cipher.update('The quick brown fox jumps over the lazy dog', 'utf8'), cipher.final()]);
// Output
console.log(salt.toString('base64')); // d/Gg4rn0Gp3vG6kOhzbAgw==
console.log(iv.toString('base64')); // x7wfJAveb6hLdO4xqgWGKw==
console.log(encrypted.toString('base64')); // RbN0MsUxCOWgBYatSbh+OIWJi8Q4BuvaYi6zMxqERvTzGtkmD2O4cmc0uMsuq9Tf
The encryption with PBEWithHmacSHA256AndAES_128 gives the same ciphertext when applying the same parameters. This can be checked e.g. with Java and the SunJCE provider which supports PBEWithHmacSHA256AndAES_128 (here).
Edit:
From the linked Java code for decryption all important parameters can be extracted directly:
var crypto = require("crypto");
// Input parameter (from the Java code for decryption)
var password = 'azerty34';
var salt = '12345678';
var digest = 'sha256';
var length = 16;
var iterations = 20;
var iv = password.padEnd(16, '\0').substr(0, 16);
var plaintext = '"My53cr3t"';
// Key derivation
var key = crypto.pbkdf2Sync(password, salt, iterations, length, digest);
// Encryption
var cipher = crypto.createCipheriv('AES-128-CBC', key, iv);
var encrypted = Buffer.concat([cipher.update(plaintext, 'utf8'), cipher.final()]);
// Output
console.log(encrypted.toString('base64')); // bEimOZ7qSoAd1NvoTNypIA==
Note that the IV is equal to the password, but truncated if it is larger than 16 bytes or padded with 0x00 values at the end if it is shorter than 16 bytes (as is the case here).
The NodeJS code now returns the required ciphertext for the given input parameters.
Keep in mind that the static salt is a serious security risk.

Why createCipheriv and createDecipheriv are not working in separate functions - crypto

I am starting to use crypto module for my NodeJS project.
My code is simple as below:
const { createCipheriv, randomBytes, createDecipheriv } = require('crypto');
const key = randomBytes(32);
const iv = randomBytes(16);
const cipher = createCipheriv('aes256', key, iv);
const decipher = createDecipheriv('aes256', key, iv);
const cipherTest = (message) => {
const encryptedMessage = cipher.update(message, 'utf8', 'hex') + cipher.final('hex');
const decryptedMessage = decipher.update(encryptedMessage, 'hex', 'utf-8') + decipher.final('utf8');
return decryptedMessage.toString('utf-8')
}
The cipherTest function returns the same result as input. Correct!
However, If I create the cipher and decipher functions separately as below , It can not decipher the encryptedMessage.
const encryptMessage = (message) => {
const encryptedMessage = cipher.update(message, 'utf8', 'hex') + cipher.final('hex');
return encryptedMessage;
}
const decryptMessage = (encryptedMessage) => {
const decryptedMessage = decipher.update(encryptedMessage, 'hex', 'utf-8') + decipher.final('utf8');
return decryptedMessage;
}
Could someone have a look? I am so thankful for that.
Ciphers are algorithms, but they contain state. Furthermore, they usually require an IV as input, which needs to be randomized for CBC which is used in this case. That IV needs to change for each ciphertext, assuming that the key remains the same.
As such, you should generally keep to the following structure in pseudo-code:
MessageEncryptor {
Key key
constructor(Key key) {
if (key = bad format) {
throw error // fail fast
this.key = key
}
String encryptMessage(String message) {
Cipher cipher = Cipher.Create(key)
Bytes iv = Rng.createBytes(cipher.blockSize)
cipher.setIV(iv)
Bytes encodedMessage = UTF8.encode(message)
// lower level runtimes may require padding for CBC mode
Bytes ciphertext = cipher.encrypt(encodedMessage)
ciphertext = Bytes.concat(ciphertext, cipher.final())
Bytes ciphertextMessage = Bytes.concat(iv, ciphertext)
// use HMAC here to append an authentication tag
String encodedCiphertextMessage = Base64.encode(ciphertextMessage)
return encodedCiphertextMessage
}
String decryptMessage(String encodedCiphertextMessage) {
Cipher cipher = Cipher.Create(key)
Bytes ciphertextMessage = Base64.decode(encodedCiphertextMessage)
// use HMAC here to verify an authentication tag
Bytes iv = Bytes.sub(0, cipher.blockSizeBytes)
cipher.setIV(iv)
Bytes encodedMessage = cipher.decrypt(ciphertextMessage, start = iv.Size)
encodedMessage = Bytes.concat(encodedMessage, cipher.final())
// lower level runtimes may require unpadding here
String message = UTF8.decode(encodedMessage)
return message
}
}
As you can see it is imperative that you:
only use the Cipher construct within the method bodies; these object carry state and are generally not thread-safe as they are mutable;
rely on the key as a field so that the message encryptor always uses the same key;
create a new MessageEncryptor instance in case you want to use another key;
create a new IV per message during encryption and extract it from the ciphertext message during decryption.
Notes:
if your scheme can handle bytes then there is no need to encode the ciphertextMessage;
the IV may have a different size or may not require random values for other modes such as GCM;
using an authenticated mode such as GCM is much safer, but hashing the IV + ciphertext using HMAC is also an option (use a separate key if you are unsure about possible issues for reusing the encryption key);
UTF8.encode and UTF8.decode can simply be replaced by any other codec in case your message is not a simple string (UTF8 is highly recommended for strings though);
a lot of libraries will have shortcuts for encoding and such, so your code may be shorter;
there are also pre-made container formats such as CMS or NaCL which are probably more secure and more flexible than any scheme you can come up with;
CBC, as used in this example, is vulnerable to plaintext oracle attacks including padding oracle attacks
for larger messages it makes more sense to use streaming for all encoding, decoding, encryption and decryption operations (the methods shown here have a high and unnecessary memory requirement);
create these kind of classes only for specific types of messages, and create a protocol + protocol description if you do so; most crypto API's don't need wrapper classes, it was created with this kind of flexibility on purpose;
The MessageEncryptor still is very lightweight - actually it has only one key as state (sometimes having a an additional key and an algorithm or two as well). If you are going to expand with fields that can change value or state such as a buffer then you want to create a new instance of the class each time you use it from a different thread. Otherwise you want to clear the state after each successful message encryption / decryption.
You may want to destroy the key securely once you've finished using it.

nodejs recover createCipher data with createCipheriv

I have some encrypted data in my database
I did it few years ago using crypto.createCipher
const cipher = crypto.createCipher('aes192', password);
As createCipher and createDecipher is deprecated, I would like to change to createCipheriv and createDecipheriv. The problem is that the data I have in my database are encoded without iv.
Is it possible to decode with createDecipheriv data encoded with createDecipher and to generate the same secret with createCipher and createCipheriv.
I tried setting the iv to null but not working
Thanks, because the database migration is an heavy work !
I tried setting the iv to null but not working
This is because this method didn’t allow for passing an initialization vector (IV), and instead derived the IV from the key using the OpenSSL EVP_BytesToKey derivation function, using a null salt meaning that the IV would be deterministic for a given key which is an issue for ciphers with counter mode like CTR, GCM and CCM.
Looking at your code:
const cipher = crypto.createCipher('aes192', password);
If you want to make this code backwards compatible, you need to call OpenSSL’s EVP_BytesToKey function yourself, typically through evp_bytestokey module which makes it available in JS userland.
Is it possible to decode with createDecipheriv data encoded with createDecipher and to generate the same secret with createCipher and createCipheriv.
Yes, you can. check out my example code here:
const crypto = require('crypto');
const EVP_BytesToKey = require('evp_bytestokey')
const ALGO = 'aes192';
const password = 'Your_Password_Here';
const KEY_SIZE = 24;
function decrypt_legacy_using_IV(text) {
const result = EVP_BytesToKey(
password,
null,
KEY_SIZE * 8, // byte to bit size
16
)
let decipher = crypto.createDecipheriv(ALGO, result.key, result.iv);
let decrypted = decipher.update(text, 'hex','utf8') + decipher.final('utf8');
return decrypted.toString();
}
function encrypt_legacy_using_IV(text) {
const result = EVP_BytesToKey(
password,
null,
KEY_SIZE * 8, // byte to bit size
16
)
var cipher = crypto.createCipheriv(ALGO, result.key, result.iv);
var encrypted = cipher.update(text, 'utf8', 'hex') + cipher.final('hex');
return encrypted.toString();
}
For complete running example, clone node-snippets and run node apogee-legacy-crypto-cipheriv.js.
However the reason this function is deprecated in the first place is because you shouldn’t use it, and instead use a random unpredictable IV, which requires you to change your code to something like this:
const iv = crypto.randomBytes(16)
const cipher = crypto.createCipheriv('aes192', password, iv)
Here, for AES-192 in CBC mode (aes192 being aliased to AES-192-CBC by OpenSSL), the IV size is expected to be the same as the block size, which is always 16 bytes.
In order to decrypt the message, you will need the IV as well. Typically you’d store the IV together with the message, as the important part is for the IV to not be predictable ahead of time.

Trying to decrypt an encrypted key generated using AES 256(AES/ECB/PKCS7Padding) algorithm in nodejs using crypto

I tried using aes256,aes-cross, and crypto.
But I was not able to decrypt the key which is encrypted using AES 256 (aes-256-ecb) with PKCS7 padding.
I ended up with the following mentioned errors.
Error: Invalid key length
at Decipheriv.createCipherBase
(or)
Error: Invalid IV length
at Decipheriv.createCipherBase
I was not able to find an npm package that helps me.
Here is the sample code:
const crypto = require("crypto");
//Length of my key is 32
const key = Buffer.from("aaaaaabbbbbbccccccddddddssssssaa", "base64");
//_sek is the encrypted key
const _sek = "NEPkEuWaXZUawBHJZIcMjHJeKuPkaQezuRc3bjWEezlbHzmqCSyh2hazB+WeAJaU"
const cipher = crypto.createDecipheriv(
"aes-256-ecb",
Buffer.from(key, "base64"),
Buffer.from([])
);
return cipher.update(_sek, "base64", "utf8") + cipher.final("utf8");
If anyone could help me with a code-based example in nodejs. It will help me to understand clearly.
Update:
function decrypt(encryted_key, access_key) {
var key = Buffer.from(access_key, "base64");
const decipher = crypto.createDecipheriv("aes-256-ecb", key, "");
decipher.setAutoPadding(false);
var decryptedSecret = decipher.update(encryted_key, "utf8", "base64");
decryptedSecret += decipher.final("base64");
return decryptedSecret;
}
decrypt(
"w2lI56OJ+RqQ04PZb5Ii6cLTxW2bemiMBTXpIlkau5xbmhwP4Qk3oyIydKV1ttWa",
"DvpMLxqKlsdhKe9Pce+dqTdNUnrofuOQPsgmSHhpxF8="
)
Required output: "cdgLxoHvpeMoMd3eXISoMcgQFRxZeMSez5x3F2YVGT4="
But got this : "G7z/eXQefnaeB7mYBq7KDrH+R4LtauNi6AU1v0/yObqoOidSOkIeW085DiMxdCDDjaI+hJiS2JRHDL1fdLrveg="
Thanks in advance.
The Invalid key length error is caused when the length of the key doesn't match the specified algorithm. E.g. in the posted code aes-256-ecb is specified, which defines AES-256, i.e. AES with a key length of 32 bytes. However, the key used has a length of only 24 bytes, since it's Base64 decoded when it's read into the buffer. This means that either a 32 bytes key must be used (e.g. if UTF-8 is used as encoding instead of Base64 when reading into the buffer), or AES-192 (specified as aes-192-ecb).
The Invalid IV length error is caused when an IV is specified in ECB mode (which doesn't use an IV at all), or when in a mode which uses an IV, its length doesn't match the blocksize of the algorithm (e.g. 16 bytes for AES). Since the ECB mode is used here, simply pass null for the IV (Buffer.from([]) works too).
Example using AES-192 and a 24 bytes key:
const crypto = require("crypto");
const key = "aaaaaabbbbbbccccccddddddssssssaa";
const secret = "01234567890123456789012345678901";
// Encryption
const cipher = crypto.createCipheriv("aes-192-ecb", Buffer.from(key, "base64"), null);
const encryptedSecret = cipher.update(secret, "utf8", "base64") + cipher.final("base64");
console.log(encryptedSecret);
// Decryption
const decipher = crypto.createDecipheriv("aes-192-ecb", Buffer.from(key, "base64"), null);
const decryptedSecret = decipher.update(encryptedSecret, "base64", "utf8") + decipher.final("utf8");
console.log(decryptedSecret);
During decryption, UTF-8 is used as output encoding which is of course only possible if the plaintext is compatible with this, otherwise a suitable encoding such as Base64 has to be applied.

NodeJS | Crypto Encryption is not producing correct results

I have a tricky problem to resolve. Not sure how to explain it correctly but will try my best. So here is what I am trying to do: I am trying to use a 3rd Party API, which wants me to encrypt a value and submits it. I successfully achieved it through C# code using the following block:
public string Encrypt(byte[] dataToEncrypt, byte[] keyBytes)
{
AesManaged tdes = new AesManaged();
tdes.KeySize = 256;
tdes.BlockSize = 128;
tdes.Key = keyBytes;
tdes.Mode = CipherMode.ECB;
tdes.Padding = PaddingMode.PKCS7;
ICryptoTransform crypt = tdes.CreateEncryptor();
byte[] cipher = crypt.TransformFinalBlock(dataToEncrypt, 0, dataToEncrypt.Length);
tdes.Clear();
return Convert.ToBase64String(cipher, 0, cipher.Length);
}
Now, I am trying to achieve the same in Node. I wrote the following function.
encrypt(buffer){
var buffbytes = new Buffer('my app key goes here to be used as password','utf8'); //converts the app key into buffer stream
return this.encrypt_key(new Buffer(buffer,'utf8'), buffbytes);
},
encrypt_key(buffer, keybytes){
var cipher = crypto.createCipher('aes-128-ecb',keybytes);
var crypted = cipher.update(buffer,'utf8','base64');
crypted = crypted+ cipher.final('base64');
return crypted;
},
This encryption code works fine. It encrypts it fine, but it doesn't encrypt it similar to what c# code does. When I take the encrypted text from C# code, and inject the encrypted result into the API call, it passes through fine, but when I use my encrypted result into the API call, it fails mentioning that the format of my key is incorrect.
I would like to know if these code blocks are same or not. I assume it is same, because both code using 128 bit AES, ECB Cipher and default padding for Crypto Node module is PKCS5 which is same as PKCS7 for 128 bit encryption. Please Help!
Edit: 9/19/2017
Fixed as per #smarx solution:
encrypt(buffer){
var buffbytes = new Buffer(helper.Constants.AppKey,'utf8'); //converts the app key into buffer stream
return this.encrypt_key(new Buffer(buffer,'utf8'), helper.Constants.AppKey);
},
encrypt_key(buffer, key){
var cipher = crypto.createCipheriv('aes-256-ecb',key,new Buffer(0));
var crypted = cipher.update(buffer,'utf8','base64');
crypted = crypted+ cipher.final('base64');
console.log('printed: ', crypted);
return crypted;
},
In your Node.js code, you're using the wrong cipher algorithm. Use aes-256-ecb, since you're using a 256-bit key. Also, be sure to use createCipheriv, since createCipher expects a password from which it derives an encryption key.
One-line fix:
const cipher = crypto.createCipheriv('aes-256-ecb', key, new Buffer(0));
The below two programs produce identical output (Q9VZ73VKhW8ZvdcBzm05mw==).
C#:
var key = System.Text.Encoding.UTF8.GetBytes("abcdefghijklmnopqrstuvwxyz123456");
var data = System.Text.Encoding.UTF8.GetBytes("Hello, World!");
var aes = new AesManaged {
Key = key,
Mode = CipherMode.ECB,
};
Console.WriteLine(Convert.ToBase64String(
aes.CreateEncryptor().TransformFinalBlock(data, 0, data.Length)));
Node.js:
const crypto = require('crypto');
const key = 'abcdefghijklmnopqrstuvwxyz123456';
const data = 'Hello, World!';
const cipher = crypto.createCipheriv('aes-256-ecb', key, new Buffer(0));
console.log(cipher.update(data, 'utf-8', 'base64') + cipher.final('base64'));

Resources