const crypto = require('crypto');
hm = crypto.createHmac("sha256","Some String");
console.log(hm.digest("base64"));
Running this gives me:
Nd6Q8epsIBG+c/jN6TdnfRNbFWCcB7bI0DYkfyDqf+8=
(repl)
But calculating the sha256 at https://approsto.com/sha-generator/ gives me:
fw/WRlO6C7Glec7Str83XpFsxgZiEJ7gwLJPCnUMOmw
Why is there a difference?
Use Hash instead of Hmac.
const crypto = require('crypto');
hash = crypto.createHash("sha256");
hash.update("Some String");
console.log(hash.digest("base64"));
Result:
fw/WRlO6C7Glec7Str83XpFsxgZiEJ7gwLJPCnUMOmw=
See also:
What is the difference between a HMAC and a hash of data?
Related
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.
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.
I am getting an error when decrypting a response using crypto that i don't understand Error: error:0606506D:digital envelope routines:EVP_DecryptFinal_ex:wrong final block length
I'm decrypting a response that looks like this 'p6\u001e�s�p>l?a%ޟ�=~m�\u0002D�K(�[<\u0007O�6\u001c�a�[sP�=\u00112\u001d�)n�Ⴓ?, i've shortened it for brevity. The end result is that it should be a JSON object
My code is as follows
const crypto = require('crypto');
const secret = "mysecret";
const algorithm = 'aes-128-cbc';
function decryptAES(message) {
const bytes = Buffer.from(message);
const salt = bytes.slice(bytes.length - 8);
const key = crypto.pbkdf2Sync(secret, salt, 10000, 16, 'sha1');
const iv = bytes.slice(bytes.length - 24, bytes.length - 8);
const data = bytes.slice(0, bytes.length - 24);
const decipher = crypto.createDecipheriv(algorithm, key, iv);
let decrpyted = decipher.update(data, 'hex', 'utf8');
decrpyted = Buffer.concat([decrpyted, decipher.final('utf8')])
console.log(decrpyted.toString());
}
What could I be doing wrong and what does the error message mean?
Update
From looking at how the data is encrypted the other side I can see that they are using PKCS7Padding. In my decryption code I am not specifying this. Can this be done with crypto?
When using Node.js crypto module
const crypto = require('crypto');
HMACseed = crypto.createHmac('sha512', 'a55e3e55ff89d1cfeab1c85ac4dc7517d8d3228bb41a7d86de9cdf5587126de7').update('02de498327ba9544ba3b5c3d855a56a6761737a399d099b46b2a1d69491ca64ae400000001').digest('hex');
console.log(HMACseed)
result
08b87c15c5cc62ebcdb8cf5bf6a61cd168387fcc59db119e19ecd8deb67380dda98dd5faf7409face6ebcb187929176636f593dadbe7d7aa44a1ed59bbe0dff6
But using https://caligatio.github.io/jsSHA/
result
6b1312b3706844b11dd50012dd31be8d77f2f7cd9ec0624f730ee24bc4246084cbcaf10f63610cca1b4cc86e8b32a29b6c495a3b8bd28de4d3fd0b98df483530
key = 'a55e3e55ff89d1cfeab1c85ac4dc7517d8d3228bb41a7d86de9cdf5587126de7'
data = '02de498327ba9544ba3b5c3d855a56a6761737a399d099b46b2a1d69491ca64ae400000001'
I wonder why the jsSHA will result different value of HMAC-256.
You need to using hex as input or it will regard it as text input.
HMACseed = crypto.createHmac('sha512', Buffer.from('a55e3e55ff89d1cfeab1c85ac4dc7517d8d3228bb41a7d86de9cdf5587126de7', 'hex')).update(Buffer.from('02de498327ba9544ba3b5c3d855a56a6761737a399d099b46b2a1d69491ca64ae400000001','hex')).digest('hex');
console.log(HMACseed)
I'm trying to decode the following base64-encoded ciphertext in Node.js with the built-in crypto library
2tGiKhSjSQEjoDNukf5BpfvwmdjBtA9kS1EaNPupESqheZ1TCr5ckEdWUvd+e51XWLUzdhBFNOBRrUB5jR64Pjf1VKvQ4dhcDk3Fdu4hyUoBSWfY053Rfd3fqpgZVggoKk4wvmNiCuEMEHxV3rGNKeFzOvP/P3O5gOF7HZYa2dgezizXSgnnD6mCp37OJXqHuAngr0pps/i9819O6FyKgu6t2AzwbWZkP2sXvH3OGRU6oj5DFTgiKGv1GbrM8mIrC7rlRdNgiJ9dyHrOAwqO+SVwzhhTWj1K//PoyyzDKUuqqUQ6AvJl7d1o5sHNzeNgJxhywMT9F10+gnliBxIg8gGSmzBqrgwUNZxltT4uEKz67u9eJi59a0HBBi/2+umzwOCHNA4jl1x0mv0MhYiX/A==
It seems to work with PHP's mcrypt functions using the string typeconfig.sys^_- as the key, as shown by inputting the value into http://www.tools4noobs.com/online_tools/decrypt/ and selecting Blowfish, ECB, Base64 decode.
However, when I run the following code in Node.js:
var crypto = require('crypto');
var data = "2tGiKhSjSQEjoDNukf5BpfvwmdjBtA9kS1EaNPupESqheZ1TCr5ckEdWUvd+e51XWLUzdhBFNOBRrUB5jR64Pjf1VKvQ4dhcDk3Fdu4hyUoBSWfY053Rfd3fqpgZVggoKk4wvmNiCuEMEHxV3rGNKeFzOvP/P3O5gOF7HZYa2dgezizXSgnnD6mCp37OJXqHuAngr0pps/i9819O6FyKgu6t2AzwbWZkP2sXvH3OGRU6oj5DFTgiKGv1GbrM8mIrC7rlRdNgiJ9dyHrOAwqO+SVwzhhTWj1K//PoyyzDKUuqqUQ6AvJl7d1o5sHNzeNgJxhywMT9F10+gnliBxIg8gGSmzBqrgwUNZxltT4uEKz67u9eJi59a0HBBi/2+umzwOCHNA4jl1x0mv0MhYiX/A==";
var decipher = crypto.createDecipher('bf-ecb', 'typeconfig.sys^_-');
data = decipher.update(data, "base64", "utf8");
data += decipher.final("utf8");
console.log(data);
I get garbage output:
y
�:����d�(����Q�i��z1��4�� �k�(� ��a5����u��73c/��(ֻ��)��������fȠ���
�ec�-<z�8����(�-L���ԛ�I��1L*��u�4�j-�Чh쭊#\P)?�.�^���q㊬�U���W&�x��85�T-ג9,dE<g}�`*�
��|#����k"�!�D'u���,x��7����
��9q=q�q��ա>�w�T����H3͜�i)R��zy��C��
��o�
I've also tried a test of the library itself, in that it seems to be able to handle stuff it encodes itself fine:
var crypto = require('crypto')
var cipher = crypto.createCipher("bf-ecb", "key");
var data = cipher.update("foobar", "utf8", "base64");
data += cipher.final("base64");
console.log(data);
var decipher = crypto.createDecipher("bf-ecb", "key");
data = decipher.update(data, "base64", "utf8");
data += decipher.final("utf8");
console.log(data);
produces:
y0rq5pYkiU0=
foobar
but copy-and-pasting that base64 string and inputting it into http://www.tools4noobs.com/online_tools/decrypt/ alongside the key "key" produces garbage output also.
Shouldn't these two libraries produce the same output, or is there something I've done wrong?
Node.js computes the MD5 hash of the password before using it as the key. As far as I can tell, mcrypt uses the key as-is.
Compute the MD5 hash of the password, and use that as the mcrypt key.
https://github.com/tugrul/node-mcrypt
var mcrypt = require('mcrypt');
var bfEcb = new mcrypt.MCrypt('blowfish', 'ecb');
bfEcb.open('typeconfig.sys^_-');
var cipherText = new Buffer('2tGiKhSjSQEjoDNukf5BpfvwmdjBtA9kS1EaNPupESqheZ1TCr5ckEdWUvd+e51XWLUzdhBFNOBRrUB5jR64Pjf1VKvQ4dhcDk3Fdu4hyUoBSWfY053Rfd3fqpgZVggoKk4wvmNiCuEMEHxV3rGNKeFzOvP/P3O5gOF7HZYa2dgezizXSgnnD6mCp37OJXqHuAngr0pps/i9819O6FyKgu6t2AzwbWZkP2sXvH3OGRU6oj5DFTgiKGv1GbrM8mIrC7rlRdNgiJ9dyHrOAwqO+SVwzhhTWj1K//PoyyzDKUuqqUQ6AvJl7d1o5sHNzeNgJxhywMT9F10+gnliBxIg8gGSmzBqrgwUNZxltT4uEKz67u9eJi59a0HBBi/2+umzwOCHNA4jl1x0mv0MhYiX/A==', 'base64');
console.log(bfEcb.decrypt(cipherText).toString());
bfEcb.close();