I'm using openpgp.js version 2.2.1.
So I've managed to encrypt a message just fine
const options = {
data: voteObject.option, // input as Message object
publicKeys: (pgp.key.readArmored(pubkey)).keys, // for encryption
};
pgp.encrypt(options).then(function(ciphertext) {
console.log(ciphertext.data);
});
This logs the encrypted message. The problem I'm now having is that I can't decrypt it. I'm at a complete loss at this point and to be honest I've tried everything to the point I don't know what I'm doing anymore. I know this isn't much to work with but I don't really have anything else to give.
Any suggestions at all would be a huge help!
I think you are mixing up the passphrase for a key and the password for "simply" encrypting a string.
Usually, in PGP a sender is encrypting a message with the receiver's public key. The receiver of the message can then decrypt his private key with his secret passphrase and with the resulting decrpyted private key he can decrypt the message.
I added a working example below:
Encryption
const receiverPublicKey = ...;
let publicKeys = (await openpgp.key.readArmored(receiverPublicKey)).keys;
let options = {
data: 'Hello, World!',
publicKeys: publicKeys
};
return openpgp.encrypt(options)
.then((encryptedMessageObject) => {
return encryptedMessageObject.data; // -----BEGIN PGP MESSAGE----- ... wcBMA0rHUQJA4dCdAQg...
});
Decryption
const receiverPrivateKey = ...;
const receiverPassphrase = 'secret';
const encryptedMessage = '-----BEGIN PGP MESSAGE----- ... wcBMA0rHUQJA4dCdAQg...';
let privKeyObj = (await openpgp.key.readArmored(receiverPrivateKey)).keys[0];
await privKeyObj.decrypt(receiverPassphrase);
let options = {
message: await openpgp.message.readArmored(encryptedMessage),
privateKey: privKeyObj
};
return openpgp.decrypt(options)
.then((plaintextObject) => {
return plaintextObject.data; // Hello, World!
});
This is the usual process of using PGP with one sender and one receiver (note that the signing of the message and checking the signature is missing).
Now there's also password in the decrypt options.
For that, see the example from the docs:
var options, encrypted;
options = {
data: 'Hello, World!', // input as String
passwords: ['secret stuff'] // multiple passwords possible
};
openpgp.encrypt(options).then(function(ciphertext) {
encrypted = ciphertext.data; // '-----BEGIN PGP MESSAGE ... END PGP MESSAGE-----'
});
options = {
message: openpgp.message.readArmored(encrypted), // parse armored message
password: 'secret stuff' // decrypt with password
};
openpgp.decrypt(options).then(function(plaintext) {
return plaintext.data; // 'Hello, World!'
});
In this case, a password is used to encrypt and decrypt a message - no public or private key at all.
I hope that helps!
Related
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
});
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)
);
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.
I'm trying to encrypt text with OpenPGPJS in react application.
I have a function in react class:
const openpgp = require('openpgp');
...
class XYZ extends React.Component{
...
async encrypt(text, targetKey){
await openpgp.initWorker({ path: 'openpgp.worker.js' });
var publicKey = (await openpgp.key.readArmored(targetKey.trim())).keys;
console.log(publicKey);
const result = await openpgp.encrypt({
message: openpgp.message.fromText(text),
publicKeys: publicKey
});
console.log(result.data);
}
...
const public_key = `-----BEGIN PGP PUBLIC KEY BLOCK----- .......`
const text = 'encrypt this'
this.encrypt(text, public_key);
for encryption.
I get an error message: Unhandled Rejection (Error): Error encrypting message: No keys, passwords, or session key provided.
I find out that (await openpgp.key.readArmored(targetKey.trim())).keys; return empty array, idk why.
I am trying to implement a very simple dialogflow agent integration with nodejs.
Here is what I did so far
I followed the code from Intent detection
I added the service account private key file .json to my server.
I added the environment variable GOOGLE_APPLICATION_CREDENTIALS with the path to my .json private key file.
Here is the code I am trying to run right now:
require('dotenv').config()
const projectId = 'gg-chatbot-216808';
const sessionId = 'quickstart-session-id';
const query = 'hello';
const languageCode = 'en-US';
// Instantiate a DialogFlow client.
const dialogflow = require('dialogflow');
const sessionClient = new dialogflow.SessionsClient();
// Define session path
const sessionPath = sessionClient.sessionPath(projectId, sessionId);
// The text query request.
const request = {
session: sessionPath,
queryInput: {
text: {
text: query,
languageCode: languageCode,
},
},
};
// This prints the private key path correctly.
console.log(process.env.GOOGLE_APPLICATION_CREDENTIALS);
// Send request and log result
sessionClient
.detectIntent(request)
.then(responses => {
console.log('Detected intent');
const result = responses[0].queryResult;
console.log(` Query: ${result.queryText}`);
console.log(` Response: ${result.fulfillmentText}`);
if (result.intent) {
console.log(` Intent: ${result.intent.displayName}`);
} else {
console.log(` No intent matched.`);
}
})
.catch(err => {
console.error('ERROR:', err);
});
Then I get this error in the console when I run this file
Auth error:Error: invalid_user: Robot is disabled.
ERROR: { Error: 14 UNAVAILABLE: Getting metadata from plugin failed with error: invalid_user: Robot is disabled.
at Object.exports.createStatusError (/var/www/html/google_auth/node_modules/grpc/src/common.js:87:15)
at Object.onReceiveStatus (/var/www/html/google_auth/node_modules/grpc/src/client_interceptors.js:1188:28)
at InterceptingListener._callNext (/var/www/html/google_auth/node_modules/grpc/src/client_interceptors.js:564:42)
at InterceptingListener.onReceiveStatus (/var/www/html/google_auth/node_modules/grpc/src/client_interceptors.js:614:8)
at callback (/var/www/html/google_auth/node_modules/grpc/src/client_interceptors.js:841:24)
code: 14,
metadata: Metadata { _internal_repr: {} },
details: 'Getting metadata from plugin failed with error: invalid_user: Robot is disabled.' }
i also faced a similar issue for my angular bot.
What i did was, instead of using using the google_credentials from the json file, i created an object with private_key,client_email {these values can be taken from the service account private key file .json}, and passed the object while setting up the session client.
var config = {
credentials: {
private_key: "YOUR_PRIVATE_KEY",
client_email: "YOUR_CLIENT_EMAIL"
}
}
const sessionClient = new dialogflow.SessionsClient(config);
note: do copy the full private_key string from .json. It will start as "-----BEGIN PRIVATE KEY-----\n......" .
Also, in GCP go to the project->IAM then try setting role for the service as DIALOGLOW API ADMIN. Check if this works.
If this has not been resolved yet , the solution is to provide "fileKey" inside sessionClient.
const sessionClient = new dialogflow.SessionsClient({
fileKey:" path of your credentials.json file"
});
or
let filePath = process.env.GOOGLE_APPLICATION_CREDENTIALS ="Location of credentials file".
const sessionClient = new dialogflow.SessionsClient({
fileKey:filePath
});
this will even work if there is no system env variable is set as GOOGLE_APPLICATION_CREDENTIALS.
Hope this is helpful.