jsSha HMAC not match with crypto - OTP algorithm - javascript

This 2 codes doesn't return the same. Sorry I am no expert of both library.
const jsSHA = require("jssha");
const time = "00000000030f7141"
const key = "101010"
var shaObj = new jsSHA("SHA-1", "HEX");
shaObj.setHMACKey(key, "HEX");
shaObj.update(time);
const hmac = shaObj.getHMAC("HEX");
console.log(hmac)
// returns '536d6eed86796085f8ec2ead742c52fd73995f27'
---------------
const crypto = require('crypto')
const time = "00000000030f7141"
const key = "101010"
crypto.createHmac('sha1', new Buffer(key,
'HEX')).update(time).digest('HEX')
// returns '8a3df92d2a68b32b2b571a1b71bfea03556e0df4'
My point is to avoid to use an external lib for using OTP with Google Authenticator.
Best,

your nodejs update() is no different. you need to use hex there also.
Attached a sample code
const jsSHA = require("jssha");
const time = "00000000030f7141"
const key = "101010"
var shaObj = new jsSHA("SHA-1", "HEX");
shaObj.setHMACKey(key, "HEX");
shaObj.update(time);
const hmac = shaObj.getHMAC("HEX");
console.log(hmac)
// returns '536d6eed86796085f8ec2ead742c52fd73995f27'
const crypto = require('crypto')
let out = crypto.createHmac('sha1', new Buffer(key, 'hex')).update(new Buffer(time,'hex')).digest('hex')
// returns '536d6eed86796085f8ec2ead742c52fd73995f27'
console.log(out)

Related

Creating a code verifier and challenge for PKCE auth on Spotify API in ReactJS

I'm trying to add Spotify auth to my single page react application following the doc from their api.
So far this is how I generate the codes based on solutions I found online:
const generateVerifier = () => {
return crypto.randomBytes(64).toString('hex');
}
const getChallenge = verifier => {
return crypto.createHash('sha256')
.update(verifier)
.digest('base64')
.replace(/\+/g, '-')
.replace(/\//g, '_')
.replace(/=/g, '')
}
An example of a pair of codes I created using that technique:
verifier: e8c3745e93a9c25ce5c2653ee36f5b4fa010b4f4df8dfbad7055f4d88551dd960fb5b7602cdfa61088951eac36429862946e86d20b15250a8f0159f1ad001605
challenge: CxF5ZvoXa6Cz6IcX3VyRHxMPRXYbv4PADxko3dwPF-I
An example of an old pair of codes I created:
verifier: 1jp6ku6-16xxjfi-1uteidc-9gjfso-1mcc0wn-tju0lh-tr2d8k-1auq4zk
challenge: SRvuz5GW2HhXzHs6b3O_wzJq4sWN0W2ma96QBx_Z77s
I then get a response from the API saying "code_verifier was incorrect." What am I doing wrong here?
Try following this guide for generating code for generating code challenge and verifier
Here are the important parts:
Generate Code Verifier
// GENERATING CODE VERIFIER
function dec2hex(dec) {
return ("0" + dec.toString(16)).substr(-2);
}
function generateCodeVerifier() {
var array = new Uint32Array(56 / 2);
window.crypto.getRandomValues(array);
return Array.from(array, dec2hex).join("");
}
Generate code challenge from code verifier
function sha256(plain) {
// returns promise ArrayBuffer
const encoder = new TextEncoder();
const data = encoder.encode(plain);
return window.crypto.subtle.digest("SHA-256", data);
}
function base64urlencode(a) {
var str = "";
var bytes = new Uint8Array(a);
var len = bytes.byteLength;
for (var i = 0; i < len; i++) {
str += String.fromCharCode(bytes[i]);
}
return btoa(str)
.replace(/\+/g, "-")
.replace(/\//g, "_")
.replace(/=+$/, "");
}
async function generateCodeChallengeFromVerifier(v) {
var hashed = await sha256(v);
var base64encoded = base64urlencode(hashed);
return base64encoded;
}
Here's a working example
You can also check the validity of the codes here
I took this snippet from the passport oauth2 library to generate code verifier and code challenge.
const code_verifier = base64url(crypto.pseudoRandomBytes(32));
const code_challenge = crypto
.createHash("sha256")
.update(code_verifier)
.digest();
Fully working and verified example:
const {randomBytes, createHash} = require("node:crypto");
// OR: import {randomBytes, createHash} from "crypto";
function generatePKCEPair() {
const NUM_OF_BYTES = 22; // Total of 44 characters (1 Bytes = 2 char) (standard states that: 43 chars <= verifier <= 128 chars)
const HASH_ALG = "sha256";
const randomVerifier = randomBytes(NUM_OF_BYTES).toString('hex')
const hash = createHash(HASH_ALG).update(randomVerifier).digest('base64');
const challenge = hash.replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, ''); // Clean base64 to make it URL safe
return {verifier: randomVerifier, challenge}
}
Run example:
generatePKCEPair();
// Result:
{
verifier: '3e2727957a1bd9f47b11ff347fca362b6060941decb4',
challange: '1SF5UEwYplIjmAwHUwcitzp9qz8zv98uYflt-tBmwLc'
}

Score Didnt show up (Spark AR)

i try write some code to view the score on Spark AR
but it not show up on spark editor, and there`s no error on code
whats wrong?
can you help me?
const Scene = require('Scene');
const P = require('Patches');
const R = require('Reactive');
var SKAWNum = Scene.root.findFirst('test');
var scoreNum = P.outputs.getScalar('score');
SKAWNum.text = scoreNum.toString();
From what I can see you are accessing/getting the patch scalar value incorrectly and the findFirst is used incorrectly.
Your code:
const Scene = require('Scene');
const P = require('Patches');
const R = require('Reactive');
var SKAWNum = Scene.root.findFirst('test');
var scoreNum = P.outputs.getScalar('score');
SKAWNum.text = scoreNum.toString();
Here's how you should do it:
const Scene = require('Scene');
const P = require('Patches');
const R = require('Reactive');
var SKAWNum = Scene.root.find('test');
var scoreNum = P.getScalarValue('score');
SKAWNum.text = scoreNum.toString();
if the Scene.root.find doesn't work then visit this page which has an example of how you use the findFirst which the find from the code above could also be replaced with.

AES GCM encrypt in nodejs and decrypt in browser?

I am trying to encrypt a piece of string in nodejs and need to decrypt that in front end javascript. In nodejs I was using the crypto library and in front end using web crypto.
Facing some error while decrypting in the front end.
NodeJS
const crypto = require('crypto');
const iv = crypto.randomBytes(12);
const algorithm = 'aes-256-gcm';
let password = 'passwordpasswordpasswordpassword';
let text = 'Hello World!';
let cipher = crypto.createCipheriv(algorithm, password, iv);
let encrypted = cipher.update(text, 'utf8', 'hex');
encrypted += cipher.final('hex');
var tag = cipher.getAuthTag();
let cipherObj = {
content: encrypted,
tag: tag,
iv: iv
}
Front End
let cipherObj; //GET FROM BE
let aesKey = await crypto.subtle.importKey(
"raw",
Buffer.from('passwordpasswordpasswordpassword'), //password
"AES-GCM",
true,
["decrypt"]
);
let decrypted = await window.crypto.subtle.decrypt(
{
name: "AES-GCM",
iv: Buffer.from(cipherObj.iv),
tagLength: 128
},
aesKey,
Buffer.concat([Buffer.from(cipherObj.content), Buffer.from(cipherObj.tag)])
);
Decrypt function in the front-end is throwing an error.
ERROR Error: Uncaught (in promise): OperationError
at resolvePromise (zone.js:814)
at zone.js:724
at rejected (main.js:231)
at ZoneDelegate.push../node_modules/zone.js/dist/zone.js.ZoneDelegate.invoke (zone.js:388)
at Object.onInvoke (core.js:3820)
at ZoneDelegate.push../node_modules/zone.js/dist/zone.js.ZoneDelegate.invoke (zone.js:387)
at Zone.push../node_modules/zone.js/dist/zone.js.Zone.run (zone.js:138)
at zone.js:872
at ZoneDelegate.push../node_modules/zone.js/dist/zone.js.ZoneDelegate.invokeTask (zone.js:421)
at Object.onInvokeTask (core.js:3811)
at ZoneDelegate.push../node_modules/zone.js/dist/zone.js.ZoneDelegate.invokeTask (zone.js:420)
at Zone.push../node_modules/zone.js/dist/zone.js.Zone.runTask (zone.js:188)
at drainMicroTaskQueue (zone.js:595)
PS: I am using Angular 7 in front end
I was able to get this working with some changes:
I use SHA-256 to hash the password, so it can be any length. (The OP requires a 32-byte string.)
I added additional helper functions from another answer for converting buffers to/from hex.
I print the output cipherObj in JSON. This is your encrypted message payload.
Helpers - NodeJS and Browser
// look up tables
var to_hex_array = [];
var to_byte_map = {};
for (var ord=0; ord<=0xff; ord++) {
var s = ord.toString(16);
if (s.length < 2) {
s = "0" + s;
}
to_hex_array.push(s);
to_byte_map[s] = ord;
}
// converter using lookups
function bufferToHex2(buffer) {
var hex_array = [];
//(new Uint8Array(buffer)).forEach((v) => { hex_array.push(to_hex_array[v]) });
for (var i=0; i<buffer.length; i++) {
hex_array.push(to_hex_array[buffer[i]]);
}
return hex_array.join('')
}
// reverse conversion using lookups
function hexToBuffer(s) {
var length2 = s.length;
if ((length2 % 2) != 0) {
throw "hex string must have length a multiple of 2";
}
var length = length2 / 2;
var result = new Uint8Array(length);
for (var i=0; i<length; i++) {
var i2 = i * 2;
var b = s.substring(i2, i2 + 2);
result[i] = to_byte_map[b];
}
return result;
}
The backend uses hex2buffer and the frontend uses buffer2hex, but you can just include that code in both.
So the backend code is the above helpers plus:
NodeJS
const crypto = require('crypto');
const iv = crypto.randomBytes(12);
const algorithm = 'aes-256-gcm';
let password = 'This is my password';
let key = crypto.createHash("sha256").update(password).digest();
let text = 'This is my test string, with 🎉 emoji in it!';
let cipher = crypto.createCipheriv(algorithm, key, iv);
let encrypted = cipher.update(text, 'utf8', 'hex');
encrypted += cipher.final('hex');
var tag = cipher.getAuthTag();
let cipherObj = {
content: encrypted,
tag: bufferToHex2(tag),
iv: bufferToHex2(iv)
}
console.log(JSON.stringify(cipherObj));
The output changes every run due to random IV, but for example:
{"content":"22da4796365ac1466f40022dd4510266fa3e24900b816f365e308cf06c95237783d1043c7deeb45d00381f8ff9ed","tag":"b7007905163b2d4890c9452c8edc1821","iv":"eb4758787164f95ac22ee50d"}
So then the example frontend code is the above helper functions, plus:
Browser
let cipherObj; //GET FROM BACKEND
// For example:
cipherObj = {"content":"22da4796365ac1466f40022dd4510266fa3e24900b816f365e308cf06c95237783d1043c7deeb45d00381f8ff9ed","tag":"b7007905163b2d4890c9452c8edc1821","iv":"eb4758787164f95ac22ee50d"}
let password = 'This is my password';
let enc = new TextEncoder();
let key = await window.crypto.subtle.digest({ name:"SHA-256" }, enc.encode(password));
let aesKey = await crypto.subtle.importKey(
"raw",
key,
"AES-GCM",
true,
["decrypt"]
);
let decrypted = await window.crypto.subtle.decrypt(
{
name: "AES-GCM",
iv: hexToBuffer(cipherObj.iv),
tagLength: 128
},
aesKey,
hexToBuffer(cipherObj.content + cipherObj.tag)
);
let dec = new TextDecoder();
console.log(dec.decode(decrypted));
// This is my test string, with 🎉 emoji in it!
Some crypto reference examples.

Problems with cross-language HMAC / SHA256 / Base64

I'm using a node.js script to create a signature for azure documentDB - the simplified version is (result at the bottom):-
var crypto = require("crypto");
var masterKey = "ABCDE"
var key = new Buffer(masterKey, "base64");
var signature = crypto.createHmac("sha256", key).update("FGHIJ").digest("base64");
console.log("\n\n"+signature)
// RNkID54/1h1H9p3NWPeRA0mOW2L0c0HUJGTTY2GPbDo=
This works, and does what I need it to. I'm trying to do the same thing in Swift with CommonCrypto
let keyString = "ABCDE"
let body = "FGHIJ"
let utf8data = keyString.dataUsingEncoding(NSUTF8StringEncoding)
let key = utf8data!.base64EncodedDataWithOptions(NSDataBase64EncodingOptions(rawValue: 0))
let str = body.cStringUsingEncoding(NSUTF8StringEncoding)
let strLen = body.lengthOfBytesUsingEncoding(NSUTF8StringEncoding)
let digestLen = Int(CC_SHA256_DIGEST_LENGTH)
let result = UnsafeMutablePointer<CUnsignedChar>.alloc(digestLen)
CCHmac(CCHmacAlgorithm(kCCHmacAlgSHA256), key.bytes, key.length, str!, strLen, result);
var hmacData = NSData(bytes: result, length: digestLen)
var hmacBase64 = hmacData.base64EncodedStringWithOptions(NSDataBase64EncodingOptions(rawValue: 0))
result.dealloc(digestLen)
let signature = String(hmacBase64)
let test = "RNkID54/1h1H9p3NWPeRA0mOW2L0c0HUJGTTY2GPbDo="
XCTAssert(test == signature, "Pass")
But it returns a completely different result. If I pass the masterKey directly into the javascript hmac, and pass it in as a string into the CCHmac method in Swift, it all works; so it seems to be something to do with finding the equivalent to this:-
var key = new Buffer(masterKey, "base64");
Thoughts?
More information - this:-
let keyString = "ABCDE"
let body = "FGHIJ"
let keyData = keyString.dataUsingEncoding(NSUTF8StringEncoding)! // .base64EncodedDataWithOptions(NSDataBase64EncodingOptions(rawValue: 0))
let bodyData = body.dataUsingEncoding(NSUTF8StringEncoding)!
let digestLen = Int(CC_SHA256_DIGEST_LENGTH)
let result = UnsafeMutablePointer<CUnsignedChar>.alloc(digestLen)
CCHmac(CCHmacAlgorithm(kCCHmacAlgSHA256), keyData.bytes, keyData.length, bodyData.bytes, bodyData.length, result);
var hmacData = NSData(bytes: result, length: digestLen)
var hmacBase64 = hmacData.base64EncodedStringWithOptions(NSDataBase64EncodingOptions(rawValue: 0))
result.dealloc(digestLen)
let signature = String(hmacBase64)
let test = "FA372zbobgpTLI5cQWh5YFiFwkNhMI8womX4Cvw68YE=" // "RNkID54/1h1H9p3NWPeRA0mOW2L0c0HUJGTTY2GPbDo="
XCTAssert(test == signature, "Pass")
Produces the same result as this:-
var crypto = require("crypto");
var masterKey = "ABCDE"
var signature = crypto.createHmac("sha256", masterKey).update("FGHIJ").digest("base64");
console.log("\n\n"+signature)
// FA372zbobgpTLI5cQWh5YFiFwkNhMI8womX4Cvw68YE=

SHA1 libraries - different hash on same binary file

All is ok, when I call the tool with the text file. But results always are different, when I pass the binary one.
I have to use some js library, like jssha, because my code should be run under several platforms and etc. Binary files processing is the mandatory requirement.
Where is the mistake ?
Node
var crypto = require('crypto')
, shasum = crypto.createHash('sha1');
var fs = require('fs');
var args = process.argv.slice(2);
console.time('readFile');
var data = fs.readFileSync(args[0],'utf8').toString();
console.timeEnd('readFile');
console.time('sha1');
shasum.update(data);
var hash = shasum.digest('base64');
console.timeEnd('sha1');
console.log(hash);
jssha
var jsSHA = require('jssha')
var fs = require('fs');
var args = process.argv.slice(2);
console.time('readFile');
var data = fs.readFileSync(args[0],'utf8').toString();
console.timeEnd('readFile');
console.time('sha1');
var shaObj = new jsSHA(data, "TEXT");
var hash = shaObj.getHash("SHA-1", "B64");
console.timeEnd('sha1');
console.log(hash);
Update
The solution for Node and jsSha is my answer below. Looks like all JS-implementations of SHA-1 work a bit different with binary data.
I've found the solution for Node and jsSHA.
Node
var crypto = require('crypto')
, shasum = crypto.createHash('sha1');
var fs = require('fs');
var args = process.argv.slice(2);
console.time('readFile');
var data = fs.readFileSync(args[0]);
console.timeEnd('readFile');
console.time('sha1');
shasum.update(data);
var hash = shasum.digest('base64');
console.timeEnd('sha1');
console.log(hash);
jsSHA
var jsSHA = require('jssha')
var fs = require('fs');
var args = process.argv.slice(2);
console.time('readFile');
var data = fs.readFileSync(args[0]).toString('hex');
console.timeEnd('readFile');
console.time('sha1');
var shaObj = new jsSHA(data, "HEX");
var hash = shaObj.getHash("SHA-1", "B64");
console.timeEnd('sha1');
console.log(hash);

Categories