I am trying to translate a given Code using crypto to a version using crypto-js without luck so far.
I am not even able to get the same hash values. The final goal is to generate TOTP tokens and I need to do it with crypto-js, because I want to generate them in a webbrowser.
The critical part is "Step 1: Generate an HMAC-SHA-1 value", I am not able to get a identical result as in const hmacResult = hmac.digest(); in crypto-js...
const crypto = require('crypto');
const cryptojs = require('crypto-js');
const base32 = require('hi-base32');
function generateHOTP(secret, counter) {
const decodedSecret = base32.decode.asBytes(secret);
const buffer = Buffer.alloc(8);
for (let i = 0; i < 8; i++) {
buffer[7 - i] = counter & 0xff;
counter = counter >> 8;
}
// Step 1: Generate an HMAC-SHA-1 value
let hmacjs = cryptojs.algo.HMAC.create(cryptojs.algo.SHA1, cryptojs.enc.Utf8.stringify(decodedSecret) );
hmacjs.update(buffer);
console.log(hmacjs.finalize().toString());
const hmac = crypto.createHmac('sha1', Buffer.from(decodedSecret));
hmac.update(buffer);
const hmacResult = hmac.digest();
console.log(hmacResult.toString('hex'));
// Step 2: Generate a 4-byte string (Dynamic Truncation)
const code = dynamicTruncationFn(hmacResult);
// Step 3: Compute an HOTP value
return code % 10 ** 6;
}
function dynamicTruncationFn(hmacValue) {
const offset = hmacValue[hmacValue.length - 1] & 0xf;
return (
((hmacValue[offset] & 0x7f) << 24) |
((hmacValue[offset + 1] & 0xff) << 16) |
((hmacValue[offset + 2] & 0xff) << 8) |
(hmacValue[offset + 3] & 0xff)
);
}
function generateTOTP(secret, window = 0) {
const counter = Math.floor(Date.now() / 30000);
return generateHOTP(secret, counter + window);
}
console.log(generateTOTP('GEZDGNBVGY3TQOJQGEZDG', 0));
I tried diverse variation and consulted the documentations, but couldn't figure out a solution.
CryptoJS uses the WordArray
data type internally, i.e. you have to convert decodedSecret
(JavaScript array) and buffer
(NodeJS buffer) into a WordArray
and the resulting HMAC as WordArray
into a NodeJS buffer.
There are several approaches to this. One possibility is the conversion via latin1:
// Step 1: Generate an HMAC-SHA-1 value
let decodedSecretWA = cryptojs.enc.Latin1.parse(Buffer.from(decodedSecret).toString('latin1')); // JS array -> WordArray
let bufferWA = cryptojs.enc.Latin1.parse(buffer.toString('latin1')); // NodeJS Buffer -> WordArray
let hmacjsWA = cryptojs.algo.HMAC.create(cryptojs.algo.SHA1, decodedSecretWA);
hmacjsWA.update(bufferWA);
let hmacResultjsWA = hmacjsWA.finalize();
const hmacResultjs = Buffer.from(hmacResultjsWA.toString(cryptojs.enc.Latin1), 'latin1'); // WordArray -> NodeJS Buffer
console.log(hmacResultjs);
Another approach is to convert directly to a WordArray
with cryptojs.lib.WordArray.create()
, which can handle JavaScript arrays (via typed arrays) and NodeJS buffers. There is no CryptoJS support for the reverse direction, but e.g. CryptJsWordArrayToUint8Array()
from here can be applied:
// Step 1: Generate an HMAC-SHA-1 value
let decodedSecretWA = cryptojs.lib.WordArray.create(new Uint8Array(decodedSecret)); // JS array -> WordArray
let bufferWA = cryptojs.lib.WordArray.create(buffer); // NodeJS Buffer -> WordArray
let hmacjsWA = cryptojs.algo.HMAC.create(cryptojs.algo.SHA1, decodedSecretWA);
hmacjsWA.update(bufferWA);
let hmacResultjsWA = hmacjsWA.finalize();
const hmacResultjs = Buffer.from(CryptJsWordArrayToUint8Array(hmacResultjsWA)); // WordArray -> NodeJS Buffer
console.log(hmacResultjs);