nodejs openpgp hashing algorithm to SHA256 - node.js

I managed to sign message with OpenPGP sign() function,but how can I change the hashing algorithm to SHA256?
async function pgpSignMessage(privateKey,messageToSign,callback) {
const unsignedMessage = await openpgp.createCleartextMessage({ text: messageToSign });
const cleartextMessage = await openpgp.sign({
message: unsignedMessage,
signingKeys: privateKey,
});
console.log("pgpSignMessage cleartextMessage ",cleartextMessage );
return callback(cleartextMessage );
);
ouput :
pgpSignMessage cleartextMessage
-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA512
This is a testing message
-----BEGIN PGP SIGNATURE-----
wsBzBAEBCgAGBQJjTOMPACEJEPjzUwweaxTJFiEEcJyNNiL/3PuC/Rze+PNT
DB5rFMnA2wf+OQV8+S1o12hrHgIOy362xHk7jk+bhUf2DzosyMIb5/FaP58a
Y2VxZUOMkYPFJeiJ9dO7z+25QjbUgbZz5ikExdPIIwmfY1BPX8jgWEQAniOD
ZZVg+pG2hALuZwzBchw+onvoPyyZLSNajkY4rA/wXexuAljWqWTpypoN51iQ
O/E4TN2FbAIQOq89tzS5MXhEAZe4bYHH4cUwsQiaIjA+2OjrspPJt+3/nosP
1f6k6wgZKuPDbyK6KKSqtb0tzFmIVkF+4+VF+vQFi7kx5utWn/nXOWO78tUV
s6dn23+GdJ5sLaFUT5vPDKux3dddErPOKEam/hJUe5VoO2LzeNO5Ww==
=KsDz
-----END PGP SIGNATURE-----

Having access to the openpgp.js sources (in this case it is latest master commit), and going down through the sign() function code, you'll occasionally get to this:
export async function getPreferredHashAlgo(key, keyPacket, date = new Date(), userID = {}, config) {
let hashAlgo = config.preferredHashAlgorithm;
let prefAlgo = hashAlgo;
if (key) {
const primaryUser = await key.getPrimaryUser(date, userID, config);
if (primaryUser.selfCertification.preferredHashAlgorithms) {
[prefAlgo] = primaryUser.selfCertification.preferredHashAlgorithms;
hashAlgo = crypto.hash.getHashByteLength(hashAlgo) <= crypto.hash.getHashByteLength(prefAlgo) ?
prefAlgo : hashAlgo;
}
}
...
So, it would first pick value from the config.preferredHashAlgorithm. Then, if primary user's id certification contains preferred hash algorithms subpacket, and first value's hash length is longer then config's one, it will pick it up.
You may get more information about your key via gpg --list-packets command.

Related

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

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.

NOdeJs Rest API response encoded with private key

I have been supplied with a public and private key to call a restAPI in nodejs.
Both keys are in clear ASCII format.
I use the following code to encript my message:
(async () => {
// put keys in backtick (``) to avoid errors caused by spaces or tabs
// ENCRYPT
const publicKeyArmored = fs.readFileSync(publicKeyFile, {
encoding: 'utf8',
flag: 'r'
});
const publicKey = await openpgp.readKey({ armoredKey: publicKeyArmored });
const encrypted = await openpgp.encrypt({
message: await openpgp.createMessage({ text: 'Hello, World!' })
, encryptionKeys: publicKey
// , signingKeys: privateKey // optional
});
console.log("Encrypted:", encrypted); // '-----BEGIN PGP MESSAGE ... END PGP MESSAGE-----'
}
However when I try to decrypt the response, all the code examples I have found seem to require a passphrase to use the private key supplied, but this is not encoded in any way, it's again plain ascii, begining with :
-----BEGIN PGP PRIVATE KEY BLOCK-----
Version: GnuPG v2.0.22 (GNU/Linux)
lQO+BGHApmABCAC70QG0T3bh1MVRGKmY9cOM2NFEie2KXCLGXUPa+2B5JOnDypGX
msoLau8FtKIqvAVAYSsONlE4P4RcltyrOTHLMvWhu73ZTJIBu6GGkgM6bKOtu2Rp
/VbPylPIXrkA3A4s0089VGgmFqJul04lit2svLwxD31ZEIY3Ke3kd0dV0nM4npRO
EZUPR5Qr6KCwBsL+ZHbDuG2YrC7oKcnJTXcdszrF7+FLAwI8viZhJOXyagJRioXd
/H/IpauXyvejN22/eRjch9IRMSz+qh0avj9tcuuJ1k4sBQQukeoIoPwFe9Rb9TY2 .....
the following code suggests I need a passphrase, but this key does not appear to require one:
async function decrypt() {
const privateKey = (await openpgp.key.readArmored([privateKeyArmored])).keys[0];
await privateKey.decrypt(passphrase);
const encryptedData = fs.readFileSync("encrypted-secrets.txt");
const decrypted = await openpgp.decrypt({
message: await openpgp.message.readArmored(encryptedData),
privateKeys: [privateKey],
});
console.log(decrypted.data);
}
SO how do I use it without a passphrase?
Thank you in advance for your xmas spirit and any help!
Your private key is ASCII armored, so it is possible to transfer it in text representation. After calling gpg --dearmor on it you'll get the binary data. Also private key may be stored using the password encryption, or not encrypted at all (in the second case you will not need to call privateKey.decrypt()). To check this you may use the command gpg --list-packets keyfile.asc.

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.

Node openpgp error when encrypting: Could not find valid key packet for encryption in key

I have a error when trying to encrypt a string with a pgp public key:
'Could not find valid key packet for encryption in key 9ae788ff8eec0b31'
This error doesn't appear with every key but with most of them. Here one of the keys that errors:
-----BEGIN PGP PUBLIC KEY BLOCK-----
Version: BCPG v1.43
mI0EXQJU0gEEAKZKsTmlR71mPgzQR6hyGJXR4tuoH/RgJPnZGCKPlJqCj8GCvlTa
Jqy5gUZQJItwS4ssFU56+fI1a47oe08covHWgLAsGRWCxsD/oneFhddPhhZkHOui
s1CW3CNDQ8hhl/DykhUoegKCmvNDzRVsD4y7ueLkzAisu3MH3ShQWDB/ABEBAAG0
CUxJTlhPIFNBU4icBBABAgAGBQJdAlTSAAoJEPa8OTh7Vcnz4kID/0KL7RBA5Z83
WuirfaVXF+Kqi4QXQO4EBUUknSbO+igRSJ/MLV4NROuhn2AZ3YWXK9B8rLsaZy9Q
49/rr1lPn648Wq2lAoN7uLwtycspFQscjLT76hDMnoOvJGzjrpi+xC7n0W7ggLRN
TkCUB8b+OBvwPhptny8kS6DASwew0Fp7
=2Sis
-----END PGP PUBLIC KEY BLOCK-----
Here is the function im using to encrypt
const openpgp = require('openpgp')
async function pgpEncrypt(pubkey, message) {
await openpgp.initWorker({ path: 'openpgp.worker.js' })
const options = {
message: await openpgp.message.fromText(message), // input as Message object
publicKeys: (await openpgp.key.readArmored(pubkey)).keys, // for encryption
armor: false
}
const cipherText = await openpgp.encrypt(options)
return cipherText.data
}
What's wrong with this? how can i ensure the key works with this library?
It is a version issue. Try the 4.10.8 version of openpgp. Use the following lines for the dependencies.
"dependencies": {
"openpgp": "^4.10.8"
}

How to verify an ES256 JWT token using Web Crypto when public key is distributed in PEM?

I need to verify ES256 JWT tokens in a Cloudflare Worker. To my understanding they do not run Node.js there and I will have to make everything work with the Web Crypto API (available in browsers, too, as window.crypto.subtle). However, I am expecting to receive the public keys as PEM files and I think I am having problems importing them.
I have been trying to modify an existing open source JWT implementation that only supports HS256 to support ES256.
In addition to trying to use the actual tokens and keys, and keys generated in browsers and OpenSSL, I have tried to use a working example from the JWT.io website (after converting it to JWK format using node-jose), since it should validate correctly. But I am not getting any errors, my code running in browser just tells me the token is not valid.
Here is my Node REPL session I used for converting the key from JWT.io to JWK:
> const jose = require('node-jose')
> const publicKey = `-----BEGIN PUBLIC KEY-----
... MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEEVs/o5+uQbTjL3chynL4wXgUg2R9
... q9UU8I5mEovUf86QZ7kOBIjJwqnzD1omageEHWwHdBO6B+dFabmdT9POxg==
... -----END PUBLIC KEY-----`
> const keyStore = jose.JWK.createKeyStore()
> keyStore.add(publicKey, 'pem')
> keyStore.toJSON()
{
keys: [
{
kty: 'EC',
kid: '19J8y7Zprt2-QKLjF2I5pVk0OELX6cY2AfaAv1LC_w8',
crv: 'P-256',
x: 'EVs_o5-uQbTjL3chynL4wXgUg2R9q9UU8I5mEovUf84',
y: 'kGe5DgSIycKp8w9aJmoHhB1sB3QTugfnRWm5nU_TzsY'
}
]
}
>
Here is the failing validation, based on code from webcrypto-jwt package:
function utf8ToUint8Array(str) {
// Adapted from https://chromium.googlesource.com/chromium/blink/+/master/LayoutTests/crypto/subtle/hmac/sign-verify.html
var Base64URL = {
stringify: function (a) {
var base64string = btoa(String.fromCharCode.apply(0, a));
return base64string.replace(/=/g, '').replace(/\+/g, '-').replace(/\//g, '_');
},
parse: function (s) {
s = s.replace(/-/g, '+').replace(/_/g, '/').replace(/\s/g, '');
return new Uint8Array(Array.prototype.map.call(atob(s), function (c) { return c.charCodeAt(0); }));
}
};
str = btoa(unescape(encodeURIComponent(str)));
return Base64URL.parse(str);
}
var cryptoSubtle = (crypto && crypto.subtle) ||
(crypto && crypto.webkitSubtle) ||
(window.msCrypto && window.msCrypto.Subtle);
// Token from JWT.io
var tokenParts = [
'eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9',
'eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImlhdCI6MTUxNjIzOTAyMn0',
'tyh-VfuzIxCyGYDlkBA7DfyjrqmSHu6pQ2hoZuFqUSLPNY2N0mpHb3nk5K17HWP_3cYHBw7AhHale5wky6-sVA'
];
// Public key from JWT.io converted in Node using node-jose
var publicKey = {
kty: 'EC',
kid: '19J8y7Zprt2-QKLjF2I5pVk0OELX6cY2AfaAv1LC_w8',
crv: 'P-256',
x: 'EVs_o5-uQbTjL3chynL4wXgUg2R9q9UU8I5mEovUf84',
y: 'kGe5DgSIycKp8w9aJmoHhB1sB3QTugfnRWm5nU_TzsY'
};
var importAlgorithm = {
name: 'ECDSA',
namedCurve: 'P-256',
hash: 'SHA-256',
};
cryptoSubtle.importKey(
"jwk",
publicKey,
importAlgorithm,
false,
["verify"]
).then(function (key) {
var partialToken = tokenParts.slice(0,2).join('.');
var signaturePart = tokenParts[2];
cryptoSubtle.verify(
importAlgorithm,
key,
utf8ToUint8Array(signaturePart),
utf8ToUint8Array(partialToken)
).then(function (ok) {
if (ok) {
console.log("I think it's valid");
} else {
console.log("I think it isn't valid");
}
}).catch(function (err) {
console.log("error verifying", err);
});
}).catch(function(err) {
console.log("error importing", err);
});
Since I copied a valid key and a valid token from JWT.io, I am expecting the code to log "I think it's valid" without errors. It does not show any errors, indeed, but it ends up in "I think it isn't valid" branch.
Answered here How to verify a signed JWT with SubtleCrypto of the Web Crypto API?
So, if I understood correctly, the problem was that base64 encoding included in the open source upstream just does not work correctly in one of the directions, since it uses the browser's btoa. Adapting https://github.com/swansontec/rfc4648.js instead works.

Resources