Openpgp npm module - Cannot read property 'readArmored' of undefined - javascript

I am getting error TypeError: Cannot read property 'readArmored' of undefined when running below code , I am not sure what I am missing. I am trying to encrypt and decrypt files with PGP key.
const openpgp = require('openpgp');
(async () => {
const { privateKey, publicKey } = await openpgp.generateKey({
type: 'ecc', // Type of the key, defaults to ECC
curve: 'curve25519', // ECC curve name, defaults to curve25519
userIDs: [{ name: 'Jon Smith', email: 'jon#example.com' }], // you can pass multiple user IDs
passphrase: 'super long and hard to guess secret', // protects the private key
format: 'armored' // output key format, defaults to 'armored' (other options: 'binary' or 'object')
})
console.log(privateKey) // '-----BEGIN PGP PRIVATE KEY BLOCK ... '
console.log(publicKey) // '-----BEGIN PGP PUBLIC KEY BLOCK ... '
const encrypted = await openpgp.encrypt({
message: await openpgp.createMessage({ text: 'Hello, World!' }),
publicKeys: (await openpgp.key.readArmored(`${publicKey}`)).keys
})
console.log(encrypted)
})()

Related

Window Crypto Subtle : How we can use encrypt / decrypt method if we generate public private key with "RSASSA-PKCS1-v1_5" algo?

I want to use the same public-private key to do some encryption/decryption at the code level and want to send encrypted data to the backend along with my public key which I am appending inside of my JWT AUTH Token.
So pls help me to encrypt / decrypt with that approach if possible, as I can't change this code due to reusability
const keyDetails = await window.crypto.subtle.generateKey(
{
name: 'RSASSA-PKCS1-v1_5',
modulusLength: 2048,
publicExponent: new Uint8Array([1, 0, 1]),
hash: { name: 'SHA-256' },
},
true,
['verify', 'sign']
);
I tried that way but got an error.
Also, I want to use my exported public and private keys which I am doing with that approach
const publicKey: any = await window.crypto.subtle.exportKey('jwk', keyDetails.publicKey);
const privateKey: any = await window.crypto.subtle.exportKey('jwk', keyDetails.privateKey);
const enc = new TextEncoder();
const encodedText = enc.encode("testing 1234");
const encryptedText = await window.crypto.subtle.encrypt({
name: "RSASSA-PKCS1-v1_5"
},
publicKey,
encodedText
)
console.log(encryptedText);
const decryptedText = await window.crypto.subtle.decrypt({
name: "RSASSA-PKCS1-v1_5"
},
privateKey,
encryptedText
)
TypeError: Failed to execute 'encrypt' on 'SubtleCrypto': parameter 2 is not of type 'CryptoKey'.
RSASSA-PKCS1-v1_5 is a padding that is applied during signing/verification. It cannot be used for encryption/decryption. The padding for encryption/decryption is RSAES-PKCS1-v1_5, but this is not supported by WebCrypto API. WebCrypto only supports RSAES-OAEP for encryption/decryption. See RFC8017 and WebCrypto API for more details.
Also, the exported JWK keys must first be adapted for encryption/decryption. Then the keys must be imported before they can be used in encryption/decryption.
The following example demonstrates this: First, a key pair for signing/verifying with RSASSA-PKCS1-v1_5 is generated. Both keys are exported as JWK. Then the key_ops and alg parameters are adjusted. Afterwards, the modified keys are re-imported and used for encryption/decryption with RSAES-OAEP:
(async () => {
// Generate
const keyDetails = await window.crypto.subtle.generateKey(
{
name: 'RSASSA-PKCS1-v1_5',
modulusLength: 2048,
publicExponent: new Uint8Array([1, 0, 1]),
hash: { name: 'SHA-256' },
},
true,
['verify', 'sign']
);
console.log(keyDetails)
// Export
const publicKey = await window.crypto.subtle.exportKey('jwk', keyDetails.publicKey);
const privateKey = await window.crypto.subtle.exportKey('jwk', keyDetails.privateKey);
console.log(publicKey)
console.log(privateKey)
// Adapt parameters and import
publicKey.key_ops = ['encrypt'];
privateKey.key_ops = ['decrypt'];
publicKey.alg = 'RSA-OAEP-256';
privateKey.alg = 'RSA-OAEP-256';
const publicKeyReloaded = await window.crypto.subtle.importKey("jwk", publicKey, {name: "RSA-OAEP", hash: {name: "SHA-256"}}, true, ["encrypt"]);
const privateKeyReloaded = await window.crypto.subtle.importKey("jwk", privateKey,{name: "RSA-OAEP", hash: {name: "SHA-256"}}, true, ["decrypt"]);
console.log(publicKeyReloaded)
console.log(privateKeyReloaded)
// Encrypt/Decrypt
const enc = new TextEncoder();
const encodedText = enc.encode("testing 1234");
const encryptedText = await window.crypto.subtle.encrypt({name: "RSA-OAEP"}, publicKeyReloaded, encodedText)
console.log(ab2b64(encryptedText));
const dec = new TextDecoder();
const decryptedText = await window.crypto.subtle.decrypt({name: "RSA-OAEP"}, privateKeyReloaded, encryptedText)
console.log(dec.decode(decryptedText));
// Helper
function ab2b64(arrayBuffer) {
return window.btoa(String.fromCharCode.apply(null, new Uint8Array(arrayBuffer)));
}
})();
Note that in general the same key pair should actually be used for either signing/verifying or encrypting/decrypting, not for both together, see here.
WebCrypto API provides some protection against this misuse by binding the purpose of the key to it (however, this protection can be circumvented all too easily, as shown above).
Well, the answer is already provided by #topaco.
now I Just want to add one more approch here. If someone wants to Encrypt and Decrypt sensitive data with help of [JSON Web Encryption - Ciphertext] JOSE npm lib. with that public/ private key which is generated for sign/ verify only!
const jose = require('jose'); // npm i jose
async encryptDecryptLogic(data: string): Promise<any>{
const keyDetails = await window.crypto.subtle.generateKey(
{
name: 'RSASSA-PKCS1-v1_5',
modulusLength: 2048,
publicExponent: new Uint8Array([1, 0, 1]),
hash: { name: 'SHA-256' },
},
true,
['verify', 'sign']
);
// updating operation from sign-varify to encrypt-decrypt.
// As that private/ public key is generated for sign and verification purposes only but here we extended its purpose. So we need to update a few properties to do encryption/decryption
publicKey.key_ops = ['encrypt'];
privateKey.key_ops = ['decrypt'];
// updating algo from sign-varify[RS256] to encrypt-decrypt[RSA-OAEP]
// Defines the algorithm used to encrypt the Content Encryption Key (CEK). This MUST be set to “RSA-OAEP”.
publicKey.alg = 'RSA-OAEP';
privateKey.alg = 'RSA-OAEP';
const encodedText = await this.jose.jwe.encrypt(publicKey, "lets encrypt me!!")
console.log('encodedText', encodedText);
const decodedText = await this.jose.jwe.decrypt(privateKey, encodedText)
console.log('decodedText', decodedText);
}

TypeError: commands.options?.map() is not a function when setting slash commands

discord js v13.3.1
I have a set up with my bot that allows me to update and push slash commands via a command, deploy. The deploy command looks like this:
module.exports = {
name: "deploy",
description: "deploys slash commands",
disabled: false,
async execute(interaction, args, client) {
if (interaction.author.id !== process.env.OWNERID) return interaction.reply('You must be the owner to use this!');
const cmds = client.commands
.filter(command => command.slash)
.map(command => {
let {
name,
description = "missing description",
options = [],
slash = false,
defaultPermission = true,
slashPermissions = [],
} = command;
if (typeof name === "string") name = [name];
const cmd = { name: name[0], description, options, defaultPermission, permissions: slashPermissions };
return cmd;
});
await setCommands(interaction.guild?.commands)
.then(interaction.reply(`Registered ${cmds.length} commands to this guild!`));
async function setCommands(commandManager) {
const appCommands = await commandManager.set(
commandManager?.guild ? cmds : cmds.filter(cmd => !cmd.permissions.length)
);
if (commandManager?.guild) {
const fullPermissions = appCommands
.map(appCommand => {
const permissions = cmds.find(cmd => cmd.name === appCommand.name).permissions;
return { id: appCommand.id, permissions };
})
.filter(appCommand => appCommand.permissions.length);
await commandManager.permissions.set({ fullPermissions });
}
}
}
}
I stopped work on my bot awhile back, and now am trying to update the rest of my commands to have slash functionality. I have slash commands registered to my guild, so this command has worked in the past as is. Now, when I try to deploy my slash commands, I am getting this error in my console:
main\node_modules\discord.js\src\managers\ApplicationCommandManager.js:246
options: command.options?.map(o => ApplicationCommand.transformOption(o)),
^
TypeError: command.options?.map is not a function
at Function.transformCommand (main\node_modules\discord.js\src\managers\ApplicationCommandManager.js:246:33)
at main\node_modules\discord.js\src\managers\ApplicationCommandManager.js:163:48
at Array.map (<anonymous>)
at GuildApplicationCommandManager.set (main\node_modules\discord.js\src\managers\ApplicationCommandManager.js:163:22)
at setCommands (main\commands\admin\deploy.js:34:54)
at Object.execute (main\commands\admin\deploy.js:29:19)
at module.exports (main\events\guild\messageCreate.js:28:51)
at processTicksAndRejections (node:internal/process/task_queues:96:5)
For reference, my commands are built as such:
module.exports = {
name: "move",
description: "Move all users from one vc to another",
usage: `\`${process.env.PREFIX}move <vc1> <vc2>\``,
alias: ["mv"],
disabled: false,
slash: true,
options: [
{
name: 'target',
type: 'CHANNEL',
channelTypes: ['GUILD_VOICE'],
description: 'Channel to move users from',
required: true
},
{
name: 'destination',
type: 'CHANNEL',
channelTypes: ['GUILD_VOICE'],
description: 'Channel to move users into',
required: true
}
],
permission: ['MOVE_MEMBERS'],
async execute(interaction, args){
}
}
Is this an issue with how I am building my options blocks in my commands themselves, or how I am parsing them to send to guildCommandManager? I'm assuming the former because the error is a TypeError, but I am historically bad at working with maps and objects, so I could be wrong, and it's hard for me to figure out since the error is being thrown from the djs module and not my code itself
Just use the rest method which you can view the code of it here on the discord.js guide.
https://discordjs.guide/creating-your-bot/creating-commands.html#command-deployment-script
You can create global commands with: applicationCommands
and create local commands for 1 server with: applicationGuildCommands

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

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.

Openpgpjs wont read key

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.

Openpgp.js can't decrypt pgp message

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!

Categories

Resources