Cannot reproduce sha512 hmac from PHP in node.js - node.js

I have some PHP code that produces a hmac as follows:
<?php
$secret = "7pgj8Dm6";
$message = "Test\0Message";
echo base64_encode(hash_hmac('sha512', $message, base64_decode($secret), true))."\n";
echo "69H45OZkKcmR9LOszbajUUPGkGT8IqasGPAWqW/1stGC2Mex2qhIB6aDbuoy7eGfMsaZiU8Y0lO mQxlsWNPrw==\n";
?>
When I try to produce similar code in node.js, I get a different base64 encoded result than I'd like to have, and I can't figure out why.
var hmac = function(msg, secret){
var s = (new Buffer(secret, 'base64')).toString('utf8');
var hmac = require('crypto').createHmac('sha512',s);
hmac.update(msg);
return hmac.digest('base64');
};
var secret = "7pgj8Dm6";
var message = "Test\0Message";
var wanted = "69H45OZkKcmR9LOszbajUUPGkGT8IqasGPAWqW/1stGC2Mex2qhIB6aDbuoy7eGfMsaZiU8Y0lO3mQxlsWNPrw==";
var got = hmac(message, secret);
if(wanted === got){
console.log('All is fine.');
}else{
console.log('Hash is wrong :(');
}
console.log('wanted:\t'+wanted);
console.log('got:\t'+got);
My motivation for this is the anxpro api, with which I'd like to play a bit.

Ok, I figured it out. The problem was that I was calling toString on the Buffer created in the hmac function. When I remove that, everything works fine.
var hmac = function(msg, secret){
var s = new Buffer(secret, 'base64');
var hmac = require('crypto').createHmac('sha512',s);
hmac.update(msg);
return hmac.digest('base64');
};

Related

NodeJS AESCFB + pkcs7 padding decryption

I'm trying to port the following Go functions to nodeJS using crypt or crypt-js but i'm having issues trying to figure out what's wrong:
The Go encryption code is available at https://go.dev/play/p/O88Bslwd-qh ( both encrypt and decrypt work)
The current nodejs implementation is:
var decryptKey= "93D87FF936DAB334C2B3CC771C9DC833B517920683C63971AA36EBC3F2A83C24";
const crypto = require('crypto');
const algorithm = 'aes-256-cfb';
const BLOCK_SIZE = 16;
var message = "8a0f6b165236391ac081f5c614265b280f84df882fb6ee14dd8b0f7020962fdd"
function encryptText(keyStr, text) {
const hash = crypto.createHash('sha256');
//Decode hex key
keyStr = Buffer.from(keyStr, "hex")
hash.update(keyStr);
const keyBytes = hash.digest();
const iv = crypto.randomBytes(BLOCK_SIZE);
const cipher = crypto.createCipheriv(algorithm, keyBytes, iv);
cipher.setAutoPadding(true);
let enc = [iv, cipher.update(text,'latin1')];
enc.push(cipher.final());
return Buffer.concat(enc).toString('hex');
}
function decryptText(keyStr, text) {
const hash = crypto.createHash('sha256');
//Decode hex key
keyStr = Buffer.from(keyStr, "hex")
hash.update(keyStr);
const keyBytes = hash.digest();
const contents = Buffer.from(text, 'hex');
const iv = contents.slice(0, BLOCK_SIZE);
const textBytes = contents.slice(BLOCK_SIZE);
const decipher = crypto.createDecipheriv(algorithm, keyBytes, iv);
decipher.setAutoPadding(true);
let res = decipher.update(textBytes,'latin1');
res += decipher.final('latin1');
return res;
}
console.log(message)
result = decryptText(decryptKey,message);
console.log(result);
message = encryptText(decryptKey,'hola').toString();
console.log(message)
result = decryptText(decryptKey,message);
console.log(result);
Any idea why it is not working as expected?
Note: I know that padding is not required with cfb but i can't modify the encryption code, it just for reference.
I don't know Go or the specifics of aes.NewCipher(key), but from its documentation it doesn't look like it's hashing the key in any way. The Go code you're linking to also doesn't hash it, so I'm not sure why you're hashing it in the Node.js code.
This should be sufficient:
function encryptText(keyStr, text) {
const keyBytes = Buffer.from(keyStr, "hex")
…
}
function decryptText(keyStr, text) {
const keyBytes = Buffer.from(keyStr, 'hex');
…
}
As an aside: it looks like you may be encrypting JSON blocks with these functions. If so, I would suggest not using any encoding (like latin1) during the encryption/decryption process, given that JSON text must be encoded using UTF-8.

Nodejs AES-256-GCM decrypt the encrypted client message by webcrypto api

I've Encrypted my text by a key in Client by AES-256-GCM algorithm and I can decrypt it in Client, But when I send it to the Backend which has a SharedKey(the same as the Client has), it can decrypt the message by AES-256-CTR algorithm(I used this algo because the AES-256-GCM in Nodejs needs authTag that I don't create it in Client and iv is the only thing I have).
When I decrypt the message on the Backend side, it works with no error, but the result is not what I encrypted in the Client
Here is what I wrote:
Client:
async function encrypt(text: string) {
const encodedText = new TextEncoder().encode(text);
const aesKey = await generateAesKey();
const iv = window.crypto.getRandomValues(
new Uint8Array(SERVER_ENCRYPTION_IV_LENGTH)
);
const encrypted = await window.crypto.subtle.encrypt(
{
name: 'AES-GCM',
iv,
},
aesKey,
encodedText
);
const concatenatedData = new Uint8Array(
iv.byteLength + encrypted.byteLength
);
concatenatedData.set(iv);
concatenatedData.set(new Uint8Array(encrypted), iv.byteLength);
return arrayBufferToBase64(concatenatedData),
}
Backend:
export function decrypt(sharedKey: string, message: string) {
const messageBuffer = new Uint8Array(base64ToArrayBuffer(message));
const iv = messageBuffer.subarray(0, 16);
const data = messageBuffer.subarray(16);
const decipher = crypto.createDecipheriv(
'aes-256-ctr',
Buffer.from(sharedKey, 'base64'),
iv
);
const decrypted =
decipher.update(data, 'binary', 'hex') + decipher.final('hex');
return Buffer.from(decrypted, 'hex').toString('base64');
}
Sample usage:
const encrypted = encrypt("Hi Everybody");
// send the encrypted message to the server
// Response is: Ô\tp\x8F\x03$\f\x91m\x8B B\x1CkQPQ=\x85\x97\x8AêsÌG0¸Ê
Since GCM is based on CTR, decryption with CTR is in principle also possible. However, this should generally not be done in practice, since it skips the authentication of the ciphertext, which is the added value of GCM over CTR.
The correct way is to decrypt on the NodeJS side with GCM and properly consider the authentication tag.
The authentication tag is automatically appended to the ciphertext by the WebCrypto API, while the crypto module of NodeJS handles ciphertext and tag separately. Therefore, not only the nonce but also the authentication tag must be separated on the NodeJS side.
The following JavaScript/WebCrypto code demonstrates the encryption:
(async () => {
var nonce = crypto.getRandomValues(new Uint8Array(12));
var plaintext = 'The quick brown fox jumps over the lazy dog';
var plaintextEncoded = new TextEncoder().encode(plaintext);
var aesKey = base64ToArrayBuffer('a068Sk+PXECrysAIN+fEGDzMQ3xlpWgE1bWXHVLb0AQ=');
var aesCryptoKey = await crypto.subtle.importKey('raw', aesKey, 'AES-GCM', true, ['encrypt', 'decrypt']);
var ciphertextTag = await crypto.subtle.encrypt({name: 'AES-GCM', iv: nonce}, aesCryptoKey, plaintextEncoded);
ciphertextTag = new Uint8Array(ciphertextTag);
var nonceCiphertextTag = new Uint8Array(nonce.length + ciphertextTag.length);
nonceCiphertextTag.set(nonce);
nonceCiphertextTag.set(ciphertextTag, nonce.length);
nonceCiphertextTag = arrayBufferToBase64(nonceCiphertextTag.buffer);
document.getElementById("nonceCiphertextTag").innerHTML = nonceCiphertextTag; // ihAdhr6595oyQ3koj52cnZp7VeB1fzWuY1v7vqFdSQGxK0VQxIXUegB1mVG4rC5Aymij7bQ9rmnFWbpo7C2znN4ROnnChB0=
})();
// Helper
// https://stackoverflow.com/a/9458996/9014097
function arrayBufferToBase64(buffer){
var binary = '';
var bytes = new Uint8Array(buffer);
var len = bytes.byteLength;
for (var i = 0; i < len; i++) {
binary += String.fromCharCode(bytes[i]);
}
return window.btoa(binary);
}
// https://stackoverflow.com/a/21797381/9014097
function base64ToArrayBuffer(base64) {
var binary_string = window.atob(base64);
var len = binary_string.length;
var bytes = new Uint8Array(len);
for (var i = 0; i < len; i++) {
bytes[i] = binary_string.charCodeAt(i);
}
return bytes.buffer;
}
<p style="font-family:'Courier New', monospace;" id="nonceCiphertextTag"></p>
This code is basically the same as your code, with some changes needed because of methods you didn't post like generateAesKey() or arrayBufferToBase64().
Example output:
ihAdhr6595oyQ3koj52cnZp7VeB1fzWuY1v7vqFdSQGxK0VQxIXUegB1mVG4rC5Aymij7bQ9rmnFWbpo7C2znN4ROnnChB0=
The following NodeJS/crypto code demonstrates the decryption. Note the tag separation and explicit passing with setAuthTag():
var crypto = require('crypto');
function decrypt(key, nonceCiphertextTag) {
key = Buffer.from(key, 'base64');
nonceCiphertextTag = Buffer.from(nonceCiphertextTag, 'base64');
var nonce = nonceCiphertextTag.slice(0, 12);
var ciphertext = nonceCiphertextTag.slice(12, -16);
var tag = nonceCiphertextTag.slice(-16); // Separate tag!
var decipher = crypto.createDecipheriv('aes-256-gcm', key, nonce);
decipher.setAuthTag(tag); // Set tag!
var decrypted = decipher.update(ciphertext, '', 'utf8') + decipher.final('utf8');
return decrypted;
}
var nonceCiphertextTag = 'ihAdhr6595oyQ3koj52cnZp7VeB1fzWuY1v7vqFdSQGxK0VQxIXUegB1mVG4rC5Aymij7bQ9rmnFWbpo7C2znN4ROnnChB0=';
var key = 'a068Sk+PXECrysAIN+fEGDzMQ3xlpWgE1bWXHVLb0AQ=';
var decrypted = decrypt(key, nonceCiphertextTag);
console.log(decrypted);
Output:
The quick brown fox jumps over the lazy dog
For completeness: Decryption of a GCM ciphertext with CTR is also possible by appending 4 bytes to the 12 bytes nonce (0x00000002). For other nonce sizes the relation is more complex, see e.g. Relationship between AES GCM and AES CTR. However, as already said, this should not be done in practice, since it bypasses the authentication of the ciphertext and is thus insecure.

How to use 'script' in nodejs

I have this kind of api example and I want to use this in nodejs.
/*
https://code.google.com/archive/p/crypto-js/
https://storage.googleapis.com/google-code-archive-downloads/v2/code.google.com/crypto-js/CryptoJS%20v3.1.2.zip
*/
<script type="text/javascript" src="./CryptoJS/rollups/hmac-sha256.js"></script>
<script type="text/javascript" src="./CryptoJS/components/enc-base64.js"></script>
function makeSignature() {
var space = " "; // one space
var newLine = "\n"; // new line
var method = "GET"; // method
var url = "/photos/puppy.jpg?query1=&query2"; // url (include query string)
var timestamp = "{timestamp}"; // current timestamp (epoch)
var accessKey = "{accessKey}"; // access key id (from portal or Sub Account)
var secretKey = "{secretKey}"; // secret key (from portal or Sub Account)
var hmac = CryptoJS.algo.HMAC.create(CryptoJS.algo.SHA256, secretKey);
hmac.update(method);
hmac.update(space);
hmac.update(url);
hmac.update(newLine);
hmac.update(timestamp);
hmac.update(newLine);
hmac.update(accessKey);
var hash = hmac.finalize();
return hash.toString(CryptoJS.enc.Base64);
}
But the problem is when I use this in Nodejs, I don't know how to require those CryptoJS.
For example, I downloaded CryptoJS file by google. and it is reading by require.
Even though it is read, I don't know which should I read correctly.
Could you help how to solve this problem?
const CryptoJS = require('./CryptoJS v3.1.2/components/enc-base64');
In NodeJS (latest version), you don't even need to download an external library or install from NPM.
Nodejs has crypto built-in library.
const crypto = require('crypto');
var space = " ";
var newLine = "\n";
var method = "GET";
var url = "/photos/puppy.jpg?query1=&query2";
var timestamp = "{timestamp}";
var accessKey = "{accessKey}";
var secretKey = "{secretKey}";
const hash = crypto.createHmac('sha256', secretKey)
.update(method)
.update(space)
.update(url)
.update(newLine)
.update(timestamp)
.update(newLine)
.update(accessKey)
.digest('hex');
console.log(hash);
First of all I dont know why you download file from google? There is very useful npm library. find it here and use it. https://www.npmjs.com/package/crypto-js

define hash cod SHA256 NodeJS

How to select the hash code on NodeJS?
I have a system made in another language with passwords on SHA256
The function there is:
#define HASH_CODE = 'WEASDSAEWEWAEAWEAWEWA';
SHA256_PassHash(HASH_CODE, password, 64);
First, param is the hash code, second is the var will be encrypted, third is the base64
I made the encrypt on NodeJS, but I have no control on hash code, so the systems don't make the same hash, how to select the code on the register on NodeJS so the system communicates with others?
const code = 'WEASDSAEWEWAEAWEAWEWA';
const normal = 'anne';
const crypto = require('crypto');
const encrypted = crypto
.createHash('sha256')
.update(normal)
.digest('base64');
console.log(encrypted);
A exemple of compatibly code, this login on PHP
login.php
<?php require_once('../mysql_conn.php'); ?>
<?php
session_start();
$HASH_SENHA = 'WEASDSAEWEWAEAWEAWEWA';
if(isset($_SESSION['Username']))
{
header("location: ../myaccount.php");
exit();
}
if(isset($_POST['usr']) && isset($_POST['psw']) && isset($_POST['botao']))
{
$usuario = mysqli_real_escape_string($MYSQL_CONNECT, $_POST['usr']);
$senha = strtoupper(hash("sha256", $_POST['psw'] . $HASH_SENHA));
$query = mysqli_query($MYSQL_CONNECT, "SELECT * FROM accounts WHERE Username='$usuario' AND Senha='$senha' LIMIT 1");
if(mysqli_num_rows($query) < 1)
{
echo "<script type=\"text/javascript\">
alert('Incorrect Username or Password.');
window.location = '../login.php';
</script>";
exit();
}
else
{
//login efetuado
$dados = mysqli_fetch_assoc($query);
if (isset($_SESSION['loc'])) {
header("location:".$_SESSION['loc']);
}
else header("location:../index.php");
}
}
?>
By looking at the PHP code you've provided.
hash("sha256", $_POST['psw'] . $HASH_SENHA)
It's hashing the string concatenation of $_POST['psw'] and $HASH_SENHA
So, the equivalent code in Node.js should look like below
Node.js
const crypto = require('crypto');
const code = 'WEASDSAEWEWAEAWEAWEWA';
const input = 'password 123';
const encrypted = crypto
.createHash('sha256')
.update(input + code) // concatenation
.digest('hex'); // get hash in hex format
console.log(encrypted);
Output
3b3107f01da624da6bb014abe532aa7416869811ebe321784b26770cd2dd74ff

Encrypting file in Node via Crypto and Stream

I want to read from a stream then encrypt it and finally write it to another file.
This is my code:
var fs = require('fs');
var crypto = require('crypto');
var infile = fs.createReadStream('a.dmg');
var outfile = fs.createWriteStream('b.dmg');
var encrypt = crypto.createCipher('aes192', 'behdad');
var size = fs.statSync('a.dmg').size;
console.log(size);
infile.on('data',function(data) {
var percentage = parseInt(infile.bytesRead) / parseInt(size);
console.log(percentage * 100);
var encrypted = encrypt.read(data);
console.log(encrypted);
if(encrypted){
console.log(encrypted);
outfile.write(encrypted);
}
});
infile.on('close', function() {
encrypt.end();
outfile.close();
});
But it returns an empty file, and encrypted is null. What is the problem? I don't want to use pipe .
You really want to use Cipher#update and Cipher#final instead of Stream#read, because the function signature is read([size]) and data is not a size.
var fs = require('fs');
var crypto = require('crypto');
var infile = fs.createReadStream('a.dmg');
var outfile = fs.createWriteStream('b.dmg');
var encrypt = crypto.createCipher('aes192', 'behdad');
var size = fs.statSync('a.dmg').size;
console.log(size);
infile.on('data',function(data) {
var percentage = parseInt(infile.bytesRead) / parseInt(size);
console.log(percentage * 100);
var encrypted = encrypt.update(data);
console.log(encrypted);
if(encrypted){
console.log(encrypted);
outfile.write(encrypted);
}
});
infile.on('close', function() {
outfile.write(encrypt.final());
outfile.close();
});
Since crypto.createCipher is deprecated now. You should use crypto.createCipheriv where you provide a key and IV. That means that you should stretch the password that you use with PBKDF2 or similar to get a key and generate a random IV to get semantic security. Since the salt for PBKDF2 and the IV are not supposed to be secret, they can be written in front of the ciphertext. Since they have always the same length (salt is usually 8-16 bytes and IV always 16 bytes for AES-CBC), you know how many bytes you have to read in order to get those values back. Keep in mind that the decryption code has to have proper error handling.

Resources