Implementing JWE encryption for a JWS signed token in Node.JS with Jose 4.11 - node.js

I have difficulty manipulating the Jose Node.JS documentation to chain the creation of a JWS and JWE. I cannot find the proper constructor for encryption. It looks like I can only encrypt a basic payload not a signed JWS.
Here is the code sample I try to fix to get something that would look like
const jws = await createJWS("myUserId");
const jwe = await encryptAsJWE(jws);
with the following methods
export const createJWS = async (userId) => {
const payload = {
}
payload['urn:userId'] = userId
// importing key from base64 encrypted secret key for signing...
const secretPkcs8Base64 = process.env.SMART_PRIVATE_KEY
const key = new NodeRSA()
key.importKey(Buffer.from(secretPkcs8Base64, 'base64'), 'pkcs8-private-der')
const privateKey = key.exportKey('pkcs8')
const ecPrivateKey = await jose.importPKCS8(privateKey, 'ES256')
const assertion = await new jose.SignJWT(payload)
.setProtectedHeader({ alg: 'RS256' })
.setIssuer('demolive')
.setExpirationTime('5m')
.sign(ecPrivateKey)
return assertion
}
export const encryptAsJWE = async (jws) => {
// importing key similar to createJWS key import
const idzPublicKey = process.env.IDZ_PUBLIC_KEY //my public key for encryption
...
const pkcs8PublicKey = await jose.importSPKI(..., 'ES256')
// how to pass a signed JWS as parameter?
const jwe = await new jose.CompactEncrypt(jws)
.encrypt(pkcs8PublicKey)
return jwe
}

The input to the CompactEncrypt constructor needs to be a Uint8Array, so just wrapping the jws like so (new TextEncoder().encode(jws)) will allow you to move forward.
Moving forward then:
You are also missing the JWE protected header, given you likely use an EC key (based on the rest of your code) you should a) choose an appropriate EC-based JWE Key Management Algorithm (e.g. ECDH-ES) and put that as the public key import algorithm, then proceed to call .setProtectedHeader({ alg: 'ECDH-ES', enc: 'A128CBC-HS256' }) on the constructed object before calling encrypt.
Here's a full working example https://github.com/panva/jose/issues/112#issue-746919790 using a different combination of algorithms but it out to help you get the gist of it.

Related

Unable to Generate correct TOTP code from Twilio Authy App Node JS

Here is the scenrio, Id like to utilize https://npm.io/package/otplib to generate a TOTP code and verify it with the user input. The issue is that I am unable to generate a code using multiple authy apps that matches the one the totp.generate() generates. I think the issue might be either due to me passing an invalid secretKey format/type into totp.generate(). Or the issue might me due to the configuration of the totp component(maybe using the wrong encryption type(i.e sha2)) when compared to the authy app.
Here is my code sample following the guide from: https://npm.io/package/otplib
const generateSecretKey = (size=16) => {
const val = crypto.randomBytes(size).toString('hex').slice(0, size).toUpperCase()
return val;
}
const generateTotp = (secret) => {
const token = totp.generate(secret)
return token;
}
const authChallenge = (token, secret) =>{
const isValid = totp.check(token, secret);
return isValid
}
let secret = generateSecretKey()
console.log("secret => " + secret)
let token = generateTotp(secret)
console.log(`generateTotp => token ${token}`)
let authChallengeResponse = authChallenge(token, secret)
The returned value is
It seems the package is able to generate the code, the issue is it is not the same code as the ones in the authy app. Could this be due to me providing an invalid key type?

Why does decrypting modified AES-CBC ciphertext fail decryption?

I am trying to get familiar with encryption/decryption. I am using deno as it supports the web crypto API.
I can encrypt and decrypt to get back the original plaintext using AES-CBC.
What I am now doing now is to encrypt, then manually modify the ciphertext and then decrypt. My expectation is that this would still work since I understand that AES-CBC does not provide integrity and authenticity check. (AES-GCM is the one that is AEAD)
But when I modify the cipher text and try to decrypt, it fails with the following error:
error: Uncaught (in promise) OperationError: Decryption failed
let deCryptedPlaintext = await window.crypto.subtle.decrypt(param, key, asByteArray);
^
at async SubtleCrypto.decrypt (deno:ext/crypto/00_crypto.js:598:29)
at async file:///Users/me/delete/run.js:33:26
Does AES-CBC also have integrity checks? Or why is the decryption failing?
In Deno I had a similar issue while encrypting a jwt around server and client and could not rely on the TextDecoder class for the same reason:
error: OperationError: Decryption failed
After some hours I played around and found a solution, a bit tricky, but is doing the job right:
(async ()=> {
const textEncoder = new TextEncoder();
const textDecoder = new TextDecoder();
const rawKey = crypto.getRandomValues(new Uint8Array(16));
// we import the key that we have previously generated
const cryptoKey = await crypto.subtle.importKey(
"raw",
rawKey,
"AES-CBC",
true,
["encrypt", "decrypt"],
);
// we generate the IV
const iv = crypto.getRandomValues(new Uint8Array(16));
// here is the string we want to encrypt
const stringToEncrypt = "foobar"
// we encrypt
const encryptedString = await crypto.subtle.encrypt(
{ name: "AES-CBC", iv: iv },
cryptoKey,
textEncoder.encode(stringToEncrypt),
);
// we transform the encrypted string to an UInt8Array
const uint8ArrayEncryptedString = new Uint8Array(encryptedString);
// we transform the Array to a String so we have a representation we can carry around
const stringifiedEncryption =
String.fromCharCode(...uint8ArrayEncryptedString);
/* now is time to decrypt again the message, so we transform the string into
a char array and for every iteration we transform
the char into a byte, so in the end we have a byte array
*/
const stringByteArray =
[...stringifiedEncryption].map((v) => v.charCodeAt(0))
// we transform the byte array into a Uint8Array buffer
const stringBuffer = new Uint8Array(stringByteArray.length);
// we load the buffer
stringByteArray.forEach((v, i) => stringBuffer[i] = v)
// we decrypt again
const againDecrString = await crypto.subtle.decrypt(
{ name: "AES-CBC", iv: iv },
cryptoKey,
stringBuffer,
);
console.log(textDecoder.decode(againDecrString))
})()
For some of you relying on the class for the same purpose, I suggest you to use this solution. The underlying implementation pheraps loses some information while converting back and forth the string (I needed it as string after the encryption) and so the decryption fails.

PDFNet Digital Signature in Node JS using Google KMS

I've seen example of signing https://www.pdftron.com/documentation/nodejs/guides/features/signature/sign-pdf
signOnNextSave uses PKCS #12 certificate, but I use Google KMS for asymmetric signing to keep private keys safe.
Here is example of signing and verifying by Google Cloud KMS
I tried to implement custom SignatureHandler but Node.JS API is different from Java or .NET
https://www.pdftron.com/api/pdfnet-node/PDFNet.SignatureHandler.html
How can I implement custom signing and verifying logic?
const data = Buffer.from('pdf data')
// We have 2048 Bit RSA - PSS Padding - SHA256 Digest key in Google Cloud KMS
const signAsymmetric = async () => {
const hash = crypto.createHash('sha256')
hash.update(data)
const digest = hash.digest()
const digestCrc32c = crc32c.calculate(digest)
// Sign the data with Cloud KMS
const [signResponse] = await client.asymmetricSign({
name: locationName,
digest: {
sha256: digest
},
digestCrc32c: {
value: digestCrc32c
}
})
if (signResponse.name !== locationName) {
throw new Error('AsymmetricSign: request corrupted in-transit')
}
if (!signResponse.verifiedDigestCrc32c) {
throw new Error('AsymmetricSign: request corrupted in-transit')
}
if (
crc32c.calculate(signResponse.signature) !==
Number(signResponse.signatureCrc32c.value)
) {
throw new Error('AsymmetricSign: response corrupted in-transit')
}
// Returns signature which is buffer
const encoded = signResponse.signature.toString('base64')
console.log(`Signature: ${encoded}`)
return signResponse.signature
}
// Verify data with public key
const verifyAsymmetricSignatureRsa = async () => {
const signatureBuffer = await signAsymmetric()
const publicKeyPem = await getPublicKey()
const verify = crypto.createVerify('sha256')
verify.update(data)
verify.end()
const key = {
key: publicKeyPem,
padding: crypto.constants.RSA_PKCS1_PSS_PADDING
}
// Verify the signature using the public key
const verified = verify.verify(key, signatureBuffer)
return verified
}
At this time, the PDFTron SDK only supports custom handlers on C++, Java, and C# (there are more plans to include additional languages in the future).
On a different platform like C++, you would extend the custom handler functions by putting hash.update(data) into SignatureHandler::AppendData, and the rest of signAsymmetric would go into SignatureHandler::CreateSignature. A name would be given to the custom handler for interoperability like Adobe.PPKLite (we do not yet support custom handler SubFilter entries, only Filter -- see PDF standard for the difference -- but this won't matter so long as you use a verification tool that supports Filter Adobe.PPKLite). Please see the following link for a concrete example:
https://www.pdftron.com/documentation/samples/cpp/DigitalSignaturesTest
As for verification, our code can already do this for you if your signatures fulfill the following conditions:
they use a standard digest algorithm
they use RSA to sign
they use the correct data formats according to the PDF standard (i.e. detached CMS, digital signature dictionary)
If you have more questions or require more details, please feel free to reach out to PDFTron support at support#pdftron.com

How to encrypt a String with a Firebase Cloud Function

I'm trying to encrypt a string inside my Firebase Cloud Function. I would love to use SHA-256 or AES-256 for this. However I didn't find the right approach yet.
exports.myfunction = functions.https.onCall((data, context) => {
const someString = "Hello World!"
const encryptedString = // How could I do this here?
return encryptedString
})
Therefore any help is appreciated! Thanks.
A good choice for this is probably the crypto module. It provides cryptographic functionality that includes a set of wrappers for OpenSSL's hash, HMAC, cipher, decipher, sign, and verify functions.
You can use crypto.createHash(algorithm\[, options\]) to encrypt a string. Check the documentation on this function.
This is the final solution:
// Use `require('crypto')` to access this module.
const crypto = require('crypto');
exports.myfunction = functions.https.onCall((data, context) => {
const secret = 'Hello World!';
const hash = crypto.createHash('sha256')
.update(pwd)
.digest('base64'); // you can also use 'hex'
return hash
})
Also take a look at the official documentation.
Install crypto-js with this command.
npm install crypto-js
Then import it and encode your string.
const AES = require("crypto-js/aes");
const SHA256 = require("crypto-js/sha256");
exports.myfunction = functions.https.onCall((data, context) => {
const someString = "Hello World!";
const encryptedString = SHA256(someString);
// or
const encryptedString = AES(someString);
return encryptedString;
})
You may have a look at the Google Tink Library
A multi-language, cross-platform library that provides cryptographic APIs that are secure, easy to use correctly, and hard(er) to misuse.

How to decrypt a value in frontend which is encrypted in the backend (nodejs)?

Backend developers have encrypted a value in nodejs using crypto module. The code is shown below:
const _encrypt = async function(text){
var cipher = crypto.createCipher('aes-256-cbc','123|a123123123123123#&12')
var crypted = cipher.update(text,'utf8','hex')
crypted += cipher.final('hex');
console.log("in generic function....encrpted val", crypted)
return crypted;
}
I need to decrypt this value in the front end (Angular). So I tried decrypting like below:
let bytes = CryptoJS.AES.decrypt("e0912c26238f29604f5998fa1fbc78f6",'123|a123123123123123#&12');
if(bytes.toString()){
let m = JSON.parse(bytes.toString(CryptoJS.enc.Utf8));
console.log("data ",m);
}
using hardcoded value. But Im getting Error: Malformed UTF-8 data error. Can anybody please tell me how to decrypt this in angular side?
This is a tricky enough one.. the crypto.createCipher function creates a key and IV from the password you provide (See the createCipher documentation for details).
This is implemented using the OpenSSL function EVP_BytesToKey.
A JavaScript implementation is available here: openssl-file.. we'll use this to get a key and IV from the password.
So there are two steps here:
Get a key and IV from your password.
Use these with Crypto.js to decode your encoded string.
Step 1: Get key and IV (Run in Node.js )
const EVP_BytesToKey = require('openssl-file').EVP_BytesToKey;
const result = EVP_BytesToKey(
'123|a123123123123123#&12',
null,
32,
'MD5',
16
);
console.log('key:', result.key.toString('hex'));
console.log('iv:', result.iv.toString('hex'));
Step 2: Decrypt string:
const encryptedValues = ['e0912c26238f29604f5998fa1fbc78f6', '0888e0558c3bce328cd7cda17e045769'];
// The results of putting the password '123|a123123123123123#&12' through EVP_BytesToKey
const key = '18bcd0b950de300fb873788958fde988fec9b478a936a3061575b16f79977d5b';
const IV = '2e11075e7b38fa20e192bc7089ccf32b';
for(let encrypted of encryptedValues) {
const decrypted = CryptoJS.AES.decrypt({ ciphertext: CryptoJS.enc.Hex.parse(encrypted) }, CryptoJS.enc.Hex.parse(key), {
iv: CryptoJS.enc.Hex.parse(IV),
mode: CryptoJS.mode.CBC
});
console.log('Ciphertext:', encrypted);
console.log('Plain text:', decrypted.toString(CryptoJS.enc.Utf8));
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/crypto-js/3.1.9-1/crypto-js.min.js"></script>
Note that if you change the password you need to generate a new key and iv using EVP_BytesToKey.
I should note that createCipher is now deprecated, so use with caution. The same applies to EVP_BytesToKey.

Resources