Decrypt pgp message using only public key - node.js

In the following use case, It shows that using the private key when encrypting a message is optional but when decrypting the message, they make the private key mandatory!
How can I encrypted and decrypt a message using only one key, the public key?
const openpgp = require('openpgp'); // use as CommonJS, AMD, ES6 module or via window.openpgp
(async () => {
// put keys in backtick (``) to avoid errors caused by spaces or tabs
const publicKeyArmored = `-----BEGIN PGP PUBLIC KEY BLOCK-----
...
-----END PGP PUBLIC KEY BLOCK-----`;
const privateKeyArmored = `-----BEGIN PGP PRIVATE KEY BLOCK-----
...
-----END PGP PRIVATE KEY BLOCK-----`; // encrypted private key
const passphrase = `yourPassphrase`; // what the private key is encrypted with
const publicKey = await openpgp.readKey({ armoredKey: publicKeyArmored });
const privateKey = await openpgp.decryptKey({
privateKey: await openpgp.readPrivateKey({ armoredKey: privateKeyArmored }),
passphrase
});
const encrypted = await openpgp.encrypt({
message: await openpgp.createMessage({ text: 'Hello, World!' }), // input as Message object
encryptionKeys: publicKey,
signingKeys: privateKey // optional
});
console.log(encrypted); // '-----BEGIN PGP MESSAGE ... END PGP MESSAGE-----'
const message = await openpgp.readMessage({
armoredMessage: encrypted // parse armored message
});
const { data: decrypted, signatures } = await openpgp.decrypt({
message,
verificationKeys: publicKey, // optional
decryptionKeys: privateKey
});
console.log(decrypted); // 'Hello, World!'
// check signature validity (signed messages only)
try {
await signatures[0].verified; // throws on invalid signature
console.log('Signature is valid');
} catch (e) {
throw new Error('Signature could not be verified: ' + e.message);
}
})();

Related

How to use the private key and pass phrase in pgp encryption file in node js?

I need to decrypt an xml file with this pgp encryption method in node js, when i use my key it says UnhandledPromiseRejectionWarning: TypeError: Cannot read property 'readArmored' of undefined.
My code is :
const openpgp = require("openpgp");
const fs = require("fs");
const privateKeyArmored = `-----BEGIN PGP PRIVATE KEY BLOCK-----
Version: OpenPGP v2.0.8
Comment: https://sela.io/pgp/
shgfdjksagfajkfgsjksgjksgfjks
-----END PGP PRIVATE KEY BLOCK-----`;
const passphrase = `Verticurl#EsteeLauder`;
openpgp.config.allow_unauthenticated_stream = true;
decrypt();
async function decrypt() {
const privateKey = (await openpgp.key.readArmored([privateKeyArmored])).keys[0];
await privateKey.decrypt(passphrase);
const decrypted = await openpgp.decrypt({
message: await openpgp.message.readArmored(fs.createReadStream("ConsumerBestRecordList_MDM_20210303_01_21_21_278.xml")),
privateKeys: [privateKey],
});
let readStream = decrypted.data;
let writeStream = fs.createWriteStream("decrypted-dataset.xml", { flags: "a" });
readStream.pipe(writeStream);
readStream.on("end", () => console.log("done!"));
}
How to rectify this error and give correct form of key and pass phrase.

Generate Signature using Private Key and "SHA256 with RSA" algorithm in Node.js

Our system will be calling an API with Authentication server. This server is built in java and requires a lot of key encryption. One requirement is to generate a Signature with client's(it's us) private key using "SHA256 with RSA" algorithm. I have done this in Java but not sure if it's right. Rur server is written in Nodejs. How can I translate below Java code to Node.js?
public static String signSHA256RSA(String input, String strPk) throws Exception {
Security.addProvider(new BouncyCastleProvider());
// Remove markers and new line characters in private key
String realPK = strPk.replaceAll("-----END PRIVATE KEY-----", "")
.replaceAll("-----BEGIN PRIVATE KEY-----", "")
.replaceAll("\n", "");
byte[] b1 = Base64.getDecoder().decode(realPK);
PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(b1);
KeyFactory kf = KeyFactory.getInstance("RSA");
Signature privateSignature = Signature.getInstance("SHA256withRSA");
privateSignature.initSign(kf.generatePrivate(spec));
privateSignature.update(input.getBytes("UTF-8"));
byte[] s = privateSignature.sign();
return Base64.getEncoder().encodeToString(s);
}
NodeJS has a built in utilities in the crypto package that could help you with that
More on that here https://nodejs.org/api/crypto.html#crypto_class_sign
here's an example from the docs
const { privateKey, publicKey } = crypto.generateKeyPairSync('ec', {
namedCurve: 'sect239k1'
});
const sign = crypto.createSign('SHA256');
sign.write('some data to sign');
sign.end();
const signature = sign.sign(privateKey, 'hex');
const verify = crypto.createVerify('SHA256');
verify.write('some data to sign');
verify.end();
console.log(verify.verify(publicKey, signature, 'hex'));
// Prints: true
Here is the complete Flow
Generate Public and Private keys
const crypto = require('crypto');
const fs = require('fs');
const { privateKey, publicKey } = crypto.generateKeyPairSync('rsa', {
modulusLength: 2048,
publicKeyEncoding: {
type: 'pkcs1',
format: 'pem'
},
privateKeyEncoding: {
type: 'pkcs1',
format: 'pem'
}
});
// Writing keys to files.
fs.writeFileSync("./private.key", privateKey);
fs.writeFileSync("./public.key", publicKey);
Sign and Verify using Public and Private keys
const crypto = require('crypto');
const fs = require('fs');
// Reading keys from files.
const privateKey = fs.readFileSync('./private.key');
const publicKey = fs.readFileSync('./public.key');
const data = Buffer.from("My Name is MHamzaRajput");
const signature = crypto.sign('RSA-SHA256', data, privateKey).toString("base64");
console.log("Signing done", signature);
const verify = crypto.verify('RSA-SHA256', data, publicKey, Buffer.from(signature, "base64"));
console.log("verfy done", verify);

Getting invalidCiphertextException: null when decrypting data which was encrypted with kms public key

I am building a POC based on asymmetric encryption where the public key from KMS will be downloaded and used on the client side to encrypt sensitive data and once that data is received at the server end it needs to be decrypted using KMS decrypt function.
Encryption and Decryption using KMS works fine but when I encrypt with the downloaded public key and then decrypt with KMS I get invalidCiphertextException: null
CMK Cryptographic Configuration is
https://i.stack.imgur.com/0muAb.png
The code I use for encryption is
var encrypt_with_public_key = function (data) {
let fs = require('fs'),
path = require('path'),
absolutePath = path.join(__dirname, 'Publickey.pem');
let publicKey = fs.readFileSync(absolutePath, "utf8");
let encrypted = crypto.publicEncrypt({
key: Buffer.from(publicKey),
oaepHash: "sha256",
},Buffer.from(data)).toString("base64");
return encrypted;
}
Code used for Decryption is
var decrypt_data = function (data) {
try {
let params = {
KeyId: kmsConfig["KeyId"],
EncryptionAlgorithm: kmsConfig["EncryptionAlgorithm"] /* RSAES_OAEP_SHA_256*/
}
params.CiphertextBlob = Buffer.from(data)
return kms.decrypt(params).promise().then(data => data.Plaintext);
}
catch (ex) {
throw ex
}
}

Firebase Cloud Functions encrypt and decrypt using SHA256 RSA crashing

I'm currently trying to encrypt and decrypt a message inside a function from Firebase Cloud Functions using SHA256 RSA. It works perfectly locally, but when I deploy the function it doesn't work on Firebase server. I'm facing the following error at the server
Error: error:06065064:digital envelope routines:EVP_DecryptFinal_ex:bad decrypt
at Error (native)
at Object.privateDecrypt (crypto.js:375:12)
at decrypt (/user_code/lib/index.js:58:30)
at exports.generateTransactionToken.functions.https.onRequest (/user_code/lib/index.js:75:30)
at cloudFunction (/user_code/node_modules/firebase-functions/lib/providers/https.js:57:9)
at /var/tmp/worker/worker.js:783:7
at /var/tmp/worker/worker.js:766:11
at _combinedTickCallback (internal/process/next_tick.js:73:7)
at process._tickDomainCallback (internal/process/next_tick.js:128:9)
My functions are structured as follow
import * as functions from 'firebase-functions';
import * as crypto from 'crypto';
function encrypt(message: string, publicKey: string) {
const buffer = Buffer.from(message, 'utf8')
const encrypted = crypto.publicEncrypt(publicKey, buffer)
return encrypted.toString('base64')
}
function decrypt(encryptedMessage: string, privateKey: string) {
const buffer = Buffer.from(encryptedMessage, 'base64')
const decrypted = crypto.privateDecrypt(
{
key: privateKey,
passphrase: '',
},
buffer,
)
return decrypted.toString('utf8')
}
export const generateTransactionToken = functions.https.onRequest((request, response) => {
const publicKey:string = functions.config().test.publickey
const privateKey:string = functions.config().test.privatekey
const fixedPublicKey:string = publicKey.replace(/\\n/g, '\n')
const fixedPrivateKey:string = privateKey.replace(/\\n/g, '\n')
const message = "hello people from the world";
const encryptedMessage = encrypt(message, fixedPublicKey);
const decryptedMessage = decrypt(encryptedMessage, fixedPrivateKey);
let obj = {
'public': publicKey,
'private': privateKey,
'message': message,
'encrypted': encryptedMessage,
'decrypted': decryptedMessage
}
response.status(200).send(obj);
});
And I'm setting my environment variables as follow
Private key
firebase functions:config:set test.privatekey="-----BEGIN RSA PRIVATE KEY-----\nProc-Type: 4,ENCRYPTED\nDEK-Info: AES-256-CBC,73B2FEB41FE8BD5AA9ECCD5F0AFBE37A\n\nE5lYh9m3kmOKYkXx213W3oK+EV7HCypSpgGSRrCTngutaT+1jNTGT8Je4Z0OLqAA\nJc3BucEDnpfMn7mh70XP13wInviOoGLRBp5Kqc8LnCBerF+1mdP/kEBXtQYN36Ql\nX9QFpPQKmnThAxKtFJ0HAzn7xtKnlRLics7gD01b1rw+qLlR32VnH00jFT7rIayj\nqQLM7ZTElbsr4yNdHZ/4kzStVQPM+SLqYXjpbdeK10UJ7K6+HyUdY/s53Y1fFsH9\nFufzP5bQ0qSkZxuA85Df8w6rZEP0Kbg1ljKYrjKt94Gs/+hD5QCvLQ5xHLa4t0Nv\ntjLubR7MqjY4CidFwsSqWQv/32rmTI3JC6l/gmcHmSsL1SozMI5PrrLAuD+JmRXR\n5pb4Ih7GXu7qGeatRUdqcLV6t3O2jrNG9O/miUBKPqblMuI4....\n-----END RSA PRIVATE KEY-----"
Public key
firebase functions:config:set test.publickey="-----BEGIN RSA PUBLIC KEY-----\nMIICCgKCAgEAv9YlZ2msmHzpJ9y2qFVRUtjPKAsy5/Ig21DrKofCvq6/W3AxfWrQ\nlGmrJF/OJGr6NRo8dIlGaAyZ4DRbuJctCP4Ij2ibLH....==\n-----END RSA PUBLIC KEY-----"

NodeJS Crypto Fails to Verify Signature Created by Web Crypto API

I'm having troubles verifying signatures created by the Web Crypto API.
Here is the code I'm using to generate RSA keys in the browser:
let keys;
const generateKeys = async () => {
const options = {
name: 'RSASSA-PKCS1-v1_5',
modulusLength: 2048,
publicExponent: new Uint8Array([0x01, 0x00, 0x01]),
hash: { name: 'SHA-256' },
};
keys = await window.crypto.subtle.generateKey(
options,
false, // non-exportable (public key still exportable)
['sign', 'verify'],
);
};
And to export the public key:
const exportPublicKey = async () => {
const publicKey = await window.crypto.subtle.exportKey('spki', keys.publicKey);
let body = window.btoa(String.fromCharCode(...new Uint8Array(publicKey)));
body = body.match(/.{1,64}/g).join('\n');
return `-----BEGIN PUBLIC KEY-----\n${body}\n-----END PUBLIC KEY-----`;
// Output:
//
// -----BEGIN PUBLIC KEY-----
// MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAx7J3SUG4sq/HSGIaGZWY
// 8b26cfEpVFYHoDUDUORIJzA/fLE9aj+uOKpGUTSfW69rMm7DAOLDz05KaEJJSI5+
// YbDPr2S82A2ByHHQt+Vu168sGz4noXTTSX2HIdVutaR/IJ0a5pNOa1vRR4MUW/ZO
// YaRir3yC5YXgcFLwwQaifNZ3lZ7WndbYEjTGOcieQQ81IUP2221PZCJI52S95nYm
// VfslsLiPhOFH7XhGSqelGYDi0cKyl0p6dKvYxFswfKKLTuWnu2BEFLjVq4S5Y9Ob
// SGm0KL/8g7pAqjac2sMzzhHtxZ+7k8tynzAf4slJJhHMm5U4DcSelTe5zOkprCJg
// muyv0H1Acb3tfXsBwfURjiE0cvSMhfum5I5epF+f139tsr1zNF24F2WgvEZZbXcG
// g1LveGCJ/0BY0pzE71DU2SYiUhl+HGDv2u32vJO80jCDf2lu7izEt544a+XE+2X0
// zVpwjNQGa2Nd4ApGosa1fbcS5MsEdbyrjMf80SAmOeb9g3y5Zt2MY7M0Njxbvmmd
// mF20PkklpH0L01lhg2AGma4o4ojolYHzDoM5a531xTw1fZIdgbSTowz0SlAHAKD3
// c2KCCsKlBbFcqy4q7yNX63SqmI3sNA3kTH9CQJdBloRvV103Le9C0iY8CAWQmow5
// N/sDJUabgOMqe9yopSjb7LUCAwEAAQ==
// -----END PUBLIC KEY-----
};
To sign a message:
const generateHash = async (message) => {
const encoder = new TextEncoder();
const buffer = encoder.encode(message);
const digest = await window.crypto.subtle.digest('SHA-256', buffer);
return digest;
};
const signMessage = async (message) => {
const { privateKey } = keys;
const digest = await generateHash(message);
const signature = await window.crypto.subtle.sign('RSASSA-PKCS1-v1_5', privateKey, digest);
return signature;
};
To verify the message in browser:
const verifyMessage = async (signature, message) => {
const { publicKey } = keys;
const digest = await generateHash(message);
const result = await window.crypto.subtle.verify('RSASSA-PKCS1-v1_5', publicKey, signature, digest);
return result;
};
When the keys are created, the public key is exported and sent to the server. Later:
const message = 'test';
const signature = await signMessage(message);
await verifyMessage(signature, message); // true
sendToServer(message, bufferToHex(signature));
Since the signature is an ArrayBuffer, I convert it to hex with the following code:
const bufferToHex = input => [...new Uint8Array(input)]
.map(v => v.toString(16).padStart(2, '0')).join('');
On the server (NodeJS 8.11.0):
const publicKey = getPublicKey(userId);
const verifier = crypto.createVerify('RSA-SHA256');
verifier.update(message, 'utf-8');
const sigBuf = Buffer.from(signature, 'hex');
verifier.verify(publicKey, sigBuf); // false
I've been chasing down this issue for days and just cannot seem to figure it out. I've tried both RSA-SHA256 and sha256WithRSAEncryption for verification to no avail. Furthermore, no errors are being thrown. Any help would be enormously appreciated!
So I don't fully understand why this is the case, but to solve the issue I needed to convert the SHA hash from an ArrayBuffer into a hex string, then read back into an array buffer using TextEncoder.
const generateHash = async (message) => {
const encoder = new TextEncoder();
const buffer = encoder.encode(message);
const digest = await window.crypto.subtle.digest('SHA-256', buffer);
// Convert to hex string
return [...new Uint8Array(digest)]
.map(v => v.toString(16).padStart(2, '0')).join('');;
};
Then when signing:
const signMessage = async (message) => {
const encoder = new TextEncoder();
const { privateKey } = keys;
const digest = await generateHash(message);
const signature = await window.crypto.subtle.sign('RSASSA-PKCS1-v1_5', privateKey, encoder.encode(digest));
return signature;
};
The signature no longer verifies on the client but it verifies in node. 🤷‍♂️
The issue is that you are signing the hash of hash of your input when you should actually be signing hash of your input. SubtleCrypto internally hashes the input. There is no need for you to provide hashed input. Since you provided hashed input as an argument, SubtleCrypto hashed it again and then signed it which led to mismatch of signatures.
It may be useful to note that both crypto and crypto.subtle hash the message before signing, so hashing separately is not necessary.
Assuming you were able to create and load your keys properly, your code should look like this.
Browser
const message = 'hello'
const encoder = new TextEncoder()
const algorithmParameters = { name: 'RSASSA-PKCS1-v1_5' }
const signatureBytes = await window.crypto.subtle.sign(
algorithmParameters,
privateKey,
encoder.encode(message)
)
const base64Signature = window.btoa(
String.fromCharCode.apply(null, new Uint8Array(signatureBytes))
)
console.log(base64Signature)
// TiJZTTihhUYAIlOm2PpnvJa/+15WOX2U0iKJ2LXsLecvohhRIWnwFfdHy4ci10mcv/UQgf2+bFf9lfFZUlPPdzckBNfXIqAjafM8XquJiw/t1v+pEGtJpaGASlzuWuL37gp3k8ux3l6zBKKbBVPPASkHVhz37uY1AXeMblfRbFE=
Node
This implementation is using crypto, but you could use the crypto.subtle to be more similar to the browser javascript syntax
const crypto = require('crypto')
const message = 'hello'
const base64Signature = 'TiJZTTihhUYAIlOm2PpnvJa/+15WOX2U0iKJ2LXsLecvohhRIWnwFfdHy4ci10mcv/UQgf2+bFf9lfFZUlPPdzckBNfXIqAjafM8XquJiw/t1v+pEGtJpaGASlzuWuL37gp3k8ux3l6zBKKbBVPPASkHVhz37uY1AXeMblfRbFE='
const hashingAlgorithm = 'rsa-sha256'
const doesVerify = crypto.verify(
hashingAlgorithm,
Buffer.from(message),
{ key: publicKey },
Buffer.from(base64Signature, 'base64')
);
console.log(doesVerify)
// true
Not a direct answer but it might just be easier to use this: https://www.npmjs.com/package/#peculiar/webcrypto so your code on client and server is consistent while addressing this problem at the same time.

Resources