Crypto.sign() function to sign a message with given private key - javascript

I need to sign a message with crypto.sign() function in NodeJS to get a valid JWT.
I have a private key (base 64) like this:
Dm2xriMD6riJagld4WCA6zWqtuWh40UzT/ZKO0pZgtHATOt0pGw90jG8BQHCE3EOjiCkFR2/gaW6JWi+3nZp8A==
And I tried to get a signature:
const getJWT = () => {
const privateKey =
"Dm2xriMD6riJagld4WCA6zWqtuWh40UzT/ZKO0pZgtHATOt0pGw90jG8BQHCE3EOjiCkFR2/gaW6JWi+3nZp8A==";
const payload = {
iss: "test",
aud: "test.com",
iat: 1650101178,
exp: 1650101278,
sub: "12345678-1234-1234-1234-123456789123"
};
const token = encode(payload, privateKey);
return token
};
const encode = (payload, key) => {
const header = {
typ: "JWT",
alg: "EdDSA"
};
const headerBase64URL = base64url(JSON.stringify(header));
const payloadBase64URL = base64url(JSON.stringify(payload));
const msg = Buffer.from(`${headerBase64URL}.${payloadBase64URL}`);
const keyDecoded = Buffer.from(key, "base64");
const signature = crypto.sign("Ed25519", msg, keyDecoded); //Here is the problem
const signatureBase64url = base64url(Buffer.from(signature));
return `${msg}.${signatureBase64url}`;
};
I received this error:
internal/crypto/sig.js:142
return _signOneShot(keyData, keyFormat, keyType, keyPassphrase, data,
^
Error: error:0909006C:PEM routines:get_name:no start line
library: 'PEM routines',
function: 'get_name',
reason: 'no start line',
code: 'ERR_OSSL_PEM_NO_START_LINE'
How can I adapt my private key to a valid format?

The crypto.sign() method requires for Ed25519 a private key in PKCS#8 format. Your key is a raw key consisting of the concatenation of the raw private 32 bytes key and the raw public 32 bytes, base64 encoded. A DER encoded PKCS#8 key can be derived and imported as follows:
Base64 decode your key. Use the first 32 bytes of your raw 64 bytes key (i.e. the raw private key).
Concat the following prefix for a private Ed25519 key (hex): 302e020100300506032b657004220420
Import that DER encoded PKCS#8 key.
Accordingly, the key import in getJWT() must be changed as follows:
const privateKey = toPkcs8der('Dm2xriMD6riJagld4WCA6zWqtuWh40UzT/ZKO0pZgtHATOt0pGw90jG8BQHCE3EOjiCkFR2/gaW6JWi+3nZp8A==');
with
const toPkcs8der = (rawB64) => {
var rawPrivate = Buffer.from(rawB64, 'base64').subarray(0, 32);
var prefixPrivateEd25519 = Buffer.from('302e020100300506032b657004220420','hex');
var der = Buffer.concat([prefixPrivateEd25519, rawPrivate]);
return crypto.createPrivateKey({key: der, format: "der", type: "pkcs8"})
}
Furthermore, in the encode() function:
Remove the line const keyDecoded = Buffer.from(key, "base64")
Create the signature with
const signature = crypto.sign(null, msg, key)
Note that for Ed25519, a null must be passed as first parameter in the sign() call. The algorithm comes from the key.
With these changes, the NodeJS code returns the following JWT:
eyJ0eXAiOiJKV1QiLCJhbGciOiJFZERTQSJ9.eyJpc3MiOiJ0ZXN0IiwiYXVkIjoidGVzdC5jb20iLCJpYXQiOjE2NTAxMDExNzgsImV4cCI6MTY1MDEwMTI3OCwic3ViIjoiMTIzNDU2NzgtMTIzNC0xMjM0LTEyMzQtMTIzNDU2Nzg5MTIzIn0.f7WG_02UKljrMeVVOTNNBAGxtLXJUT_8QAnujNhomV18Pn5cU-0lHRgVlmRttOlqI7Iol_fHut3C4AOXxDGnAQ
that matches the expected JWT.

Related

Node.js: Crypto signature is inconsistent (different with every test)

I'm trying to sign a payload and recreate the expected signature in the documentation of a service documented here: https://paybis.readme.io/reference/partner-api#signing-requests
My implementation of the signature is:
const signRequest = (privateKey: Buffer) => {
const verifiableData = '{"event":"VERIFICATION_STATUS_UPDATED","data":{"partnerUserId":"e18fb964-fd9a-4de7-96c4-1lclszzd","status":"started"},"timestamp":1654073212}'
const signature = crypto.sign(
'sha512',
Buffer.from(verifiableData),
{
key: privateKey,
padding: crypto.constants.RSA_PKCS1_PSS_PADDING,
},
);
return signature.toString("base64");
}
And my test:
const privateKey = fs.readFileSync(path.resolve(__dirname, './private.key'));
const signature = signRequest(privateKey);
const signature2 = signRequest(privateKey);
expect(signature).to.equal(signature2); //FAILS
Where the private key is formatted as such:
-----BEGIN RSA PRIVATE KEY-----
MIIJKQIBAAKCAgEAtJWQWnZJqbbxz1wNr3Dn/9I43z4Ddm/jd4G+PCkNGYXcqVqX
...
ukzH0Cx/iuONcUrYtpirM9ZMotfyyl4xO0Hc9bD/I97xn93GOvKFBkV9l7hW
-----END RSA PRIVATE KEY-----
No matter what I do, the signature is not consistent (aka. varies with every attempt)
Can anyone pinpoint what I might be doing wrong?
I tried using a string as the verifiable body, saving the key as a .pem file, instantiating the private key as a string, and passing it as both Buffer and string to the function.
As stated in one of the comments, the answer is that RSA-PSS is non-deterministic, so the signature will not be the same by design.
However, when trying to verify any of these different correct signatures with crypto.verify, it does verify as expected:
it(`Can create correctly encrypt body`, async function () {
const privateKey = process.env.PAYBIS_RSA_PRIVATE_KEY!;
const publicKey = KEY1;
const wrongPublicKey = KEY2;
const requestBody = { ... };
const signature = signRequest(requestBody, privateKey); // Signature here is always different
const verified = verifySignature(requestBody, signature, publicKey);
const verifiedFalse = verifySignature(requestBody, signature, wrongPublicKey);
expect(verified).to.equal(true, 'Could not verify correct signature'); //Correctly outputs true
expect(verifiedFalse).to.equal(false, 'Verified incorrect signature'); //Correctly outputs false
});

Wrong result with HMAC verification using SubtleCrypto in JavaScript

I am trying to verify a HMAC signature using the SubtleCrypto API. The whole thing is supposed to run in Cloudflare Workers and I am testing it locally using their wrangler tool.
This is my code so far, but it generates the wrong signature.
const message = "(query params from an url)";
const given_signature = "(extracted from the query params)";
const SECRET = "...";
const algorithm = { name: 'HMAC', hash: 'SHA-256' };
const encoder = new TextEncoder();
const key = await crypto.subtle.importKey(
'raw',
encoder.encode(SECRET),
algorithm,
false,
['sign', 'verify']
);
const signature = await crypto.subtle.sign(
algorithm.name,
key,
encoder.encode(message)
);
const digest = btoa(String.fromCharCode(...new Uint8Array(signature)));
// The digest does not match the signature extracted from the query params
// If I, for example, want to verify the signature directly, the result is still false.
const verify = await crypto.subtle.verify(
algorithm.name,
key,
encoder.encode(given_signature),
encoder.encode(message)
);
If I am using the same secret and message in online HMAC testing tools, I am getting the correct results, so I am certain that there must be a bug in my code.
What I find interesting, is that the signature generated by my code is much shorter than the given one (e.g. 3fn0mhrebHTJMhtOyvRP5nZIhogX/M1OKQ5GojniZTM= vs ddf9f49a1ade6c74c9321b4ecaf44fe67648868817fccd4e290e46a239e26533).
Does anyone have an idea where I am going wrong?
Thanks to the helpful comments! The problem in a nutshell was that the provided signature was encoded as a HEX string, while the generated signature was a base64-encoded string.
To keep things clean, here is a working version that uses the crypto.subtle.verify function:
const message = "(query params from an url w/o the hmac signature)";
const given_signature = "(the given hmac signature extracted from the query params)";
const SECRET = "(my secret key)";
const hexToBuffer = (hex: string) => {
const matches = hex.match(/[\da-f]{2}/gi) ?? [];
const typedArray = new Uint8Array(
matches.map(function (h) {
return parseInt(h, 16);
})
);
return typedArray.buffer;
};
const algorithm = { name: "HMAC", hash: "SHA-256" };
const encoder = new TextEncoder();
const key = await crypto.subtle.importKey(
"raw",
encoder.encode(SECRET),
algorithm,
false,
["sign", "verify"]
);
const result: boolean = await crypto.subtle.verify(
algorithm.name,
key,
hexToBuffer(given_signature),
encoder.encode(message)
);

AES-GCM needs the same init_vector from encryption FOR decryption. Why?

I've created a TypeScript example from MDN example as verbatim as I could to illustrate. It encrypts and decrypts just fine. I just noticed that, for Decryption to work, it requires the same init_vector from encryption. Isn't the init_vector supposed to be a nonce?
How is the person receiving the message going to know what the init_vector I've used for encryption if decryption is a separate process done at a different place and time?
const message_plain: string = "Hello World!";
const password_plain: string = "letmein";
// AES-GCM - ENCRYPTION
const pbkdf2_salt: Uint8Array = crypto.getRandomValues(new Uint8Array(16)); // 128 bits
const pbkdf2_iterations: number = 100000;
const init_vector: Uint8Array = crypto.getRandomValues(new Uint8Array(12)); // 96 bits
const utf8_encoder: TextEncoder = new TextEncoder();
const message_bytes: Uint8Array = utf8_encoder.encode(message_plain);
const password_bytes: Uint8Array = utf8_encoder.encode(password_plain);
const crypto_key_material: CryptoKey = await crypto.subtle.importKey(
"raw",
password_bytes,
{ name: "PBKDF2" },
false,
["deriveBits", "deriveKey"],
);
const crypto_key_derived: CryptoKey = await crypto.subtle.deriveKey(
{
"name": "PBKDF2",
salt: pbkdf2_salt,
"iterations": pbkdf2_iterations,
"hash": "SHA-256",
},
crypto_key_material,
{ "name": "AES-GCM", "length": 256 },
true,
["encrypt", "decrypt"],
);
const message_encrypted: ArrayBuffer = await crypto.subtle.encrypt(
{ name: "AES-GCM", iv: init_vector },
crypto_key_derived,
message_bytes,
);
const message_encrypted_bytes: Uint8Array = new Uint8Array(message_encrypted);
console.log(
`[${message_encrypted.byteLength} bytes total] -> ${message_encrypted_bytes}`,
);
// AES-GCM - DECRYPTION
const utf8Decoder: TextDecoder = new TextDecoder();
try {
const message_decrypted: ArrayBuffer = await crypto.subtle.decrypt(
{ name: "AES-GCM", iv: init_vector },
crypto_key_derived,
message_encrypted,
);
const message_decrypted_bytes: Uint8Array = new Uint8Array(message_decrypted);
console.log(utf8Decoder.decode(message_decrypted_bytes));
} catch (e) {
console.log("*** Decryption error ***");
}
You should use asymmetric encryption like RSA which has a public / private key, for example this node-rsa package.
In terms of having the same initial vector, I found this snippet taken from this answer:
In any case, the IV never needs to be kept secret — if it did, it would be a key, not an IV. Indeed, in most cases, keeping the IV secret would not be practical even if you wanted to since the recipient needs to know it in order to decrypt the data (or verify the hash, etc.).

Convert request.body to json

I am trying to assert data that I am sending the API.
When I log body I see that it's off ArrayBuffer format. But when I use Buffer.from I get a message First argument must be a string, Buffer, ArrayBuffer, Array, or array-like object.
cy.wait(‘#myrequest’)
.its('request')
.then((request) => {
console.log(request.body)
const buffer = Buffer.from(request.body)
})
What can I do to retrieve data in form object or json to then assert data that I am sending to an API?
Assuming that this is javascript, you should be able to use JSON.parse
let jsonData = JSON.parse(request.body);
Note that this should be wrapped in a try {} catch {}
If you're using express, you can try https://www.npmjs.com/package/body-parser
There's an example recipe that has multipart handling here stubs multipart form
Test
cy.wait('#submitForm').its('request').then(({ headers, body }) => {
expect(body, 'request body').to.be.a('ArrayBuffer')
const contentType = headers['content-type']
// the browser sets the separator string when sending the form
// something like
// "multipart/form-data; boundary=----WebKitFormBoundaryiJZt6b3aUg8Jybg2"
// we want to extract it and pass to the utility function
// to convert the multipart text into an object of values
expect(contentType, 'boundary').to.match(/^multipart\/form-data; boundary=/)
const boundary = contentType.split('boundary=')[1]
const values = parseMultipartForm({ boundary, buffer: body })
expect(values, 'form values').to.deep.equal({
city: 'Boston',
value: '28', // note this is a string
})
})
Parsing
/*
Utility: parses (very simply) multipart buffer into string values.
the decoded buffer string will be something like
------We bKitFormBoundaryYxsB3tlu9eJsoCeY
Content-Disposition: form-data; name="city"
Boston
------WebKitFormBoundaryYxsB3tlu9eJsoCeY
Content-Disposition: form-data; name="value"
28
------WebKitFormBoundaryYxsB3tlu9eJsoCeY--
there are NPM packages for parsing such text into an object:
- https://www.npmjs.com/package/parse-multipart
- https://www.npmjs.com/package/multiparty
- https://www.npmjs.com/package/busboy
- https://www.npmjs.com/package/formidable
*/
const parseMultipartForm = ({ boundary, buffer }) => {
expect(boundary, 'boundary').to.be.a('string')
const decoder = new TextDecoder()
const decoded = decoder.decode(buffer)
const parts = decoded.split(`--${boundary}`)
.map((s) => s.trim())
.filter((s) => s.startsWith('Content-Disposition: form-data;'))
console.log(decoded)
console.log(parts)
const result = {}
parts.forEach((part) => {
const lines = part.split(/\r?\n/g)
console.log('lines')
console.log(lines)
const key = lines[0].match(/name="(.+)"/)[1]
result[key] = lines[2].trim()
})
return result
}

How can convert this code from golang to reactjs in crypto hmac sha256 hex

Golang code is as below
func GenerateClientToken(secret, user, timestamp, info string) string {
token := hmac.New(sha256.New, []byte(secret))
token.Write([]byte(user))
token.Write([]byte(timestamp))
token.Write([]byte(info))
return hex.EncodeToString(token.Sum(nil))
}
How can I convert from this to reactjs code.
I am trying like this
import CryptoJS from 'crypto-js'
generateClientToken(secret, user, timestamp, info) {
var hmac = CryptoJS.algo.HMAC.create(CryptoJS.algo.SHA256, secret);
hmac.update(user);
hmac.update(timestamp);
hmac.update(info);
var hash = hmac.finalize();
console.log("hmac: ", hash.toString(CryptoJS.enc.Base64))
console.log("hmac: ", hash.toString(CryptoJS.enc.Hex))
}
but result is not same with golang result. What am I wrong? and How will I do?
Go code: https://play.golang.org/p/7pXgn5GPQm
React:
Package used: "crypto-js": "^3.1.9-1"
React v15.4.2
Inside a React Component, a function:
generateClientToken(secret, user, timestamp, info) {
let hmac = CryptoJS.algo.HMAC.create(CryptoJS.algo.SHA256, secret);
hmac.update(user);
hmac.update(timestamp);
hmac.update(info);
let hash = hmac.finalize();
console.log("hmac: ", hash.toString(CryptoJS.enc.Hex))
}
Inside render()
const secret = "test";
const user = "Dennis";
const timestamp = "1";
const info = "qwerty";
this.generateClientToken(secret, user, timestamp, info);

Categories

Resources