Nodejs `crypto.publicEncrypt` would not take public key generated by `ssh-keygen rsa` - node.js

I used ssh-keygen rsa to generate an RSA keypair. The generated public key looks like this:
ssh-rsa AAAAB3NzaC1yc2EAAA...
When I try to use crypto in Node.js to encrypt a plain string,
const fs = require('fs');
const { publicEncrypt } = require('crypto');
const publicKey = fs.readFileSync('$path/to/publicKey').toString();
const encryptedToken = publicEncrypt(publicKey, Buffer.from('some plain string'));
it would give out the following error:
Error: error:0909006C:PEM routines:get_name:no start line
at node:internal/crypto/cipher:78:12
...
library: 'PEM routines',
function: 'get_name',
reason: 'no start line',
code: 'ERR_OSSL_PEM_NO_START_LINE'
I am pretty new to cryptography and know only the general idea of public/private key encryption so would really appreciate any advice.
Edit:
I understand that crypto comes with method to generate key pairs, so I guess the question is more about why the ssh-rsa public key did not work here.

The posted public key is in OpenSSH format, which is not supported by NodeJS's crypto module, see documentation of crypto.publicEncrypt(key, buffer).
However, keys in OpenSSH format can be converted to formats that can be processed by NodeJS. This can be done via tools, e.g. ssh-keygen, or via libraries, e.g. node-sshpk.
Alternatively, keys can be generated directly in the required format using OpenSSL.
The following example uses the node-sshpk library and converts an OpenSSH private key to a PEM encoded PKCS8 key and an OpenSSH public key to a PEM encoded X.509/SPKI key. Both target formats are supported by NodeJS:
var sshpk = require('sshpk');
var crypto = require('crypto');
var privateKeyOpenSSH = `-----BEGIN OPENSSH PRIVATE KEY-----
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAABFwAAAAdzc2gtcn
NhAAAAAwEAAQAAAQEApcFmUiObzYdRrKivbYrUwJ8plRfjcgA1nXIHpMKj4q0l4vatprYe
vNS2l06y2Qvz1Txu7AeS9+zT673zczwvLgENcZb6lXA576XMLjcmZhnNKXDRnRV/UCMlea
jdwdhQ93YJpsGRCUewD8k6IOO6G/MVAQ433ybBuCPJysTLWNk2Vv/J3VrLkBWGLiwOUcnZ
pR17/mm6QBOsLRAuBs1jELcid1Jdpc8wfULlrSdCfw/P2mXOqpuTMhSEBy4yE+W6V54MSI
Vw4BaxlrcAmC/Rh7OHJrhuIEmmOStqmMPe7HvFKNTjqzISAiyWmPWWwI9UuP4g7b0p7GAk
0YMevLPQnwAAA9jLWAaqy1gGqgAAAAdzc2gtcnNhAAABAQClwWZSI5vNh1GsqK9titTAny
mVF+NyADWdcgekwqPirSXi9q2mth681LaXTrLZC/PVPG7sB5L37NPrvfNzPC8uAQ1xlvqV
cDnvpcwuNyZmGc0pcNGdFX9QIyV5qN3B2FD3dgmmwZEJR7APyTog47ob8xUBDjffJsG4I8
nKxMtY2TZW/8ndWsuQFYYuLA5RydmlHXv+abpAE6wtEC4GzWMQtyJ3Ul2lzzB9QuWtJ0J/
D8/aZc6qm5MyFIQHLjIT5bpXngxIhXDgFrGWtwCYL9GHs4cmuG4gSaY5K2qYw97se8Uo1O
OrMhICLJaY9ZbAj1S4/iDtvSnsYCTRgx68s9CfAAAAAwEAAQAAAQAstgRxt6U5RX0kg8P+
WmqVItnGm9EAWUodFDs3mEE4zdfgZwXkaE/WQ9KU8eeQYIb/R/PruwdL1Rg9CNn4hY18bV
BBCabCVKlsGV8AQGQdOmx69zGzm67h4Pkk3gYjWcRNXAuybZg/1pSJTZBees8i5ukNhdZQ
XVX347908KyhZFb4jlYws7gbOkVBP7ludHSnnrodL91F2ouKrgplLfWu40sgU3fSpSybby
3Llt+FUW6UjCErp54c2LS5vZtvTJK+wVr2fYF4Y1Sgmv+JZ4bJUkZgaxzcHoOLF5GfuQFo
dvJVxbSXPUhk7JW7ur9hHLkEaJtYf0xf1TwJdPRQxu3xAAAAgQCui5wvYjZrCM9wWDXpWh
1dNGPIJQuX8aCxi2tfTkRYlkPUWFSYd+orQr53/FPTxLdwe9lAe8x0Xkr+wMga6771ywHw
sT6xk7nCabXcN6PQCn5DjYMtLPfa5rY3+yHR01pVSzo9l6JcvangU1xyw7MRYj0LGZVSbp
U/beRO/bXekQAAAIEA0uUTVG9wPiqZRTLT6H8rQ4ZK96VcTF+CnmJUO+3Fsp/D7jfYItZF
2GcqzWYv6tsvumdhZt0dziXBy7fAJDW88k/nq+BNTlDXEYzkEA+x13eaLAzfI08SwlAnF5
zxZUo8yIsjKtyt9gs1+VVgtwpvvqUVDUibjbWxaE4OWtdLjwUAAACBAMk02gcYkJylXw12
4me1vtWpw7/7pga3eK0Y4ZTZgpCZXhrLnl9yWXrVIlvV8dLJQI88s7+1CRU9HPi+2BGtgI
mJ5crYws7q0QgLODg1rDosd4vkkKok5XPUsrKDLxwme+Ipq26IdRyoJF2ZiRHvPG5ajjmK
gWByjcdktmXM+UpTAAAAHHRmbG9yZXNfZHQwMUB0ZmxvcmVzX2R0MDEtUEMBAgMEBQY=
-----END OPENSSH PRIVATE KEY-----`
var publicKeyOpenSSH = `ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQClwWZSI5vNh1GsqK9titTAnymVF+NyADWdcgekwqPirSXi9q2mth681LaXTrLZC/PVPG7sB5L37NPrvfNzPC8uAQ1xlvqVcDnvpcwuNyZmGc0pcNGdFX9QIyV5qN3B2FD3dgmmwZEJR7APyTog47ob8xUBDjffJsG4I8nKxMtY2TZW/8ndWsuQFYYuLA5RydmlHXv+abpAE6wtEC4GzWMQtyJ3Ul2lzzB9QuWtJ0J/D8/aZc6qm5MyFIQHLjIT5bpXngxIhXDgFrGWtwCYL9GHs4cmuG4gSaY5K2qYw97se8Uo1OOrMhICLJaY9ZbAj1S4/iDtvSnsYCTRgx68s9Cf whatever`;
// Convert
var privateKey = sshpk.parsePrivateKey(privateKeyOpenSSH, 'ssh');
var publicKey = sshpk.parseKey(publicKeyOpenSSH, 'ssh');
var privateKeyPkcs8 = privateKey.toBuffer('pkcs8');
var publicKeyX509 = publicKey.toBuffer('pkcs8');
console.log(privateKey.toString('ssh'));
console.log(publicKey.toString('ssh'));
console.log(privateKeyPkcs8.toString('utf8')); // -----BEGIN PRIVATE KEY-----...
console.log(publicKeyX509.toString('utf8')); // -----BEGIN PUBLIC KEY-----...
// Encrypt/Decrypt
var plaintext = Buffer.from('The quick brown fox jumps over the lazy dog', 'utf8');
var ciphertext = crypto.publicEncrypt(publicKeyX509.toString('utf8'), plaintext);
var decryptedText = crypto.privateDecrypt(privateKeyPkcs8.toString('utf8'), ciphertext);
console.log('Ciphertext, base64 encoded: ', ciphertext.toString('base64'));
console.log('Decrypted text: ', decryptedText.toString('utf8'));

const {NodeSSH} = require('node-ssh')
ssh = new NodeSSH()
var sshpk = require('sshpk');
var crypto = require('crypto');
var privateKeyOpenSSH = `private key ecdsa `
var publicKeyOpenSSH = `public key ecdsa`;
// Convert
var privateKey = sshpk.parsePrivateKey(privateKeyOpenSSH, 'ssh');
var publicKey = sshpk.parseKey(publicKeyOpenSSH, 'ssh');
var privateKeyPkcs8 = privateKey.toBuffer('pkcs8');
var publicKeyX509 = publicKey.toBuffer('pkcs8');
ssh.connect({
host: process.env.SSH_host,
username: process.env.SSH_user,
privateKey: Buffer.from('C:/Users/aniru/.ssh/id_ecdsa'),
passphrase:'passphrase string'
})
.then(() => {
console.log("ssh connected");
ssh.execCommand("redis-cli ping", { cwd: "/var/www" }).then((result) =>
{
console.log("STDOUT: " + result.stdout);
console.log("STDERR: " + result.stderr);
});
});

Related

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.

Node.js crypto create ecdh with the tron public address

I am aiming for wallet address encryption, using the TronWeb.createAccount(), I fetch the public address for wallet in base58 and the private key as hex.
Sample Public Address: TPeGpPdJGQoNobV4SEjXLdrjefN3iCAAAA
Sample Private Key: 6B07B82D50B27171F35BF1DEAB14...
I am getting the keys using following code.
const TronWeb = require('tronweb');
function createAccount() {
try {
const tronWeb = new TronWeb(fullNode, solidityNode, eventServer);
return tronWeb.createAccount();
} catch (error) {
console.log(error);
throw error;
}
}
When I use the getPublicKey() method after setting the private key in bob.createECDH() the code works fine but in actual I will not have the utility of setPrivateKey() method for alice when I am on bob side. So I will have to pass the base58 public address instead of bob.getPublicKey() or alice.getPublicKey() on either side.
const alice_secret = alice.computeSecret('HEX_PUBLIC_KEY','hex');
Following is the full code for encryption and decryption.
const alice = crypto.createECDH('secp256k1');
const bob = crypto.createECDH('secp256k1');
bob.setPrivateKey("PRIVATE_KEY_FOR_BOB", "hex");
alice.setPrivateKey("PRIVATE_KEY_FOR_ALICE", "hex");
const alice_secret = alice.computeSecret(bob.getPublicKey());
console.log("alice's shared Key: " + alice_secret.toString('hex') + "\n");
var algo = 'aes-256-ecb', plainText = "Some secret to share bob";
var cipher = crypto.createCipher(algo, alice_secret)
var encrypted = cipher.update(plainText, 'utf8', 'hex')
encrypted += cipher.final('hex');
console.log("Encrypted: " + encrypted);
const bob_secret = bob.computeSecret(alice.getPublicKey());
console.log("bob's shared Key: " + bob_secret.toString('hex') + "\n");
var decipher = crypto.createDecipher(algo, bob_secret)
var decrypted = decipher.update(encrypted, 'hex', 'utf8')
decrypted += decipher.final('utf8');
console.log("Decrypted: " + decrypted);
if (plainText == decrypted) {
console.log("ECDH Success")
}
The output is expected when I use setPrivateKey() and then use getPublicKey()
alice's shared Key: 238c3eba08585a5cae1006710c79fe2de329545e9ca4c1ef719c53b55eb337b6
app.js:21 Encrypted: 44184052d9e205fd855aaf5f30b5f186c4bab88a5cfdce58d99cd8c696954c8dd5676807e6fe372fbe3ca5b230e54293
app.js:29 bob's shared Key: 238c3eba08585a5cae1006710c79fe2de329545e9ca4c1ef719c53b55eb337b6
app.js:35 Decrypted: QmdUuJDvgZ7EWEpJmEcFCoYwotn9CHyvK4qEhZs82AhZoQ
app.js:40 ECDH Success
When I convert the public key to hex using bs58 or any other package it says
UnhandledPromiseRejectionWarning: Error: Failed to translate Buffer to a EC_POINT
Is there a way to convert this public address and use it in this situation?
I had to study the ECDH supported key format and regenerate the keys according to new format to be able to fix this issue.
There were two formats of the public key that we can use to encrypt data.

RSAES-PKCS1- V1_5 Public encryption using NodeJs crypto

I am facing up a bit of a wall trying to send encrypted data over to a remote server using the NodeJs crypto module.
According to the API docs, the payload needs to be encrypted using the AES-256 algorithm with randomly generated KEY and IV.
The randomly generated KEY and IV [above] are then encrypted with a shared private key using the RSAES-PKCS1-V1_5 standard.
Finally, the encrypted payload is signed with the private key, using the RSASSA-PKCS1-V1_5 signature scheme and then SHA1 hashed.
Once that is done, I compose an HTTP request and pass the encrypted KEY, IV, encrypted payload and the signature to the remove server.
I am not an expert when it comes to cryptography, so I am convinced that I am doing something wrong somewhere.
The server is able to verify the signature, which gives me confidence that there is no problem with the shared private key file.
However, the server fails to decrypt the encrypted KEY and IV which are needed to decrypt the payload.
I am using the code below for testing:
const crypto = require('crypto');
const fs = require('fs');
//Generate random KEY and IV
const randomKey = crypto.randomBytes(32);
const randomIV = crypto.randomBytes(16);
//Load private key from disk
const privateKey = fs.readFileSync(__dirname + '/private.key');
//Get data payload that should be encrypted with AES-256
const payload = 'Payload to be sent';
//Encrypt payload with AES-256
const cipher = crypto.createCipheriv('aes-256-cbc', randomKey, randomIV);
const encryptedPayload = Buffer.concat([cipher.update(payload), cipher.final()]);
//Sign the encrypted payload using the RSASSA-PKCS1-V1_5 algorithm
const signer = crypto.createSign('RSA-SHA1');
signer.update(encryptedPayload);
signer.end();
const signature = signer.sign(privateKey); //Sign with the private key
//Encrypt both KEY and IV
const encryptOptions = {
key: privateKey,
padding: constants.RSA_PKCS1_PADDING
}
const encryptedKey = crypto.publicEncrypt(encryptOptions, randomKey);
const encryptedIV = crypto.publicEncrypt(encryptOptions, randomIV);
//A function that encodes Buffer type data to base64
const encode = buffer => buffer.toString('base64');
const request = {
encryptedKey: encode(encryptedKey),
encryptedIV: encode(encryptedIV),
encryptedPayload: encode(encryptedPayload),
signature: encode(signature)
};
const endPoint = require('./end-point');
endPoint.call(request);
//-> Server successfully verifies signature but fails to decrypt encrypted KEY and IV
Could someone point me to where I am doing it wrong?

Using node.js crypto to verify signatures

I am trying to use AWS lambda to verify signatures created with sec256r1 in swift.
Message: "some text to sign"
Has been hashed with sha256 too
signatures will be in base64
encoding:MEYCIQCPfWhpzxMqu3gZWflBm5V0aetgb2/S+SGyGcElaOjgdgIhALaD4lbxVwa8HUUBFOLz+CGvIioDkf9oihSnXHCqh8yV
and public key will look like so:
-----BEGIN PUBLIC KEY-----
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEXIvPbzLjaPLd8jgiv1TL/X8PXpJN
gDkGRj9U9Lcx1yKURpQFVavcMkfWyO8r7JlZNMax0JKfLZUM1IePRjHlFw==
-----END PUBLIC KEY-----
To clarify,
I am trying to use lambda to verify signatures that come from the client side, and encrypt data with their public key if need be.
Here is code:
const crypto = require('crypto');
const verify = crypto.createVerify('SHA256');
verify.write('some text to sign');
verify.end();
const l1 = "-----BEGIN PUBLIC KEY-----\n"
const l2 =
"MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEXIvPbzLjaPLd8jgiv1TL/X8PXpJNgDkGRj9U9Lcx1yKURpQFVavcMkfWyO8r7JlZNMax0JKfLZUM1IePRjHlFw=="
const l3 = "\n-----END PUBLIC KEY-----"
const publicKey = l1 + l2 + l3
const signature = "MEYCIQCPfWhpzxMqu3gZWflBm5V0aetgb2/S+SGyGcElaOjgdgIhALaD4lbxVwa8HUUBFOLz+CGvIioDkf9oihSnXHCqh8yV";
console.log(verify.verify(publicKey, signature));// Prints: true or false
Here's how to inegrate with Nodejs.Crypto. First, the RSA private and public keys need to be generated. There are several ways to do that, here's an a way to do this online with encrypt.JS. You can use getSignatureByInput function below after private and public keys have been stored into the filesystem which generates a unique signature given a string input:
const crypto = require('crypto')
const fs = require('fs')
const getSignatureByInput = (input) => {
let privatePem = fs.readFileSync('PRIVATE_KEY_FILE_PATH_GOES_HERE')
let key = privatePem.toString('ascii')
let sign = crypto.createSign('RSA-SHA256')
sign.update(input)
let signature = sign.sign(key, 'hex')
return signature
}
Thereafter, to verify a signature, you can use the following function:
const getSignatureVerifyResult = (input) => {
let signatureSignedByPrivateKey = getSignatureByInput(input)
let pem = fs.readFileSync('PUBLIC_KEY_FILE_PATH_GOES_HERE')
let publicKey = pem.toString('ascii')
const verifier = crypto.createVerify('RSA-SHA256')
verifier.update(input, 'ascii')
const publicKeyBuf = new Buffer(publicKey, 'ascii')
const signatureBuf = new Buffer(signatureSignedByPrivateKey, 'hex')
const result = verifier.verify(publicKeyBuf, signatureBuf)
return result;
}
getSignatureVerifyResult will return true/false depending on whether the signature are verified. Keep in mind that there's a plethora of algorithms to choose when it comes to signing.
Please see the fuller solution at this StackOverflow post which shows how to use the verify.update() and verify.verify() methods in node.js.

node-forge how to read a private rsa key from file

This question is related to Issue using RSA encryption in javascript
I have a node script that I am trying to read a PEM RSA private key from a file:
const forge = require('node-forge');
const fs = require('fs');
const path = require('path');
let pkey = fs.readFileSync(path.join(__dirname, 'test.key'), 'utf8');
//let pkeyDer = forge.util.decode64(pkey); // since it's not base64 encoded, i suppose don't need to decode
let pkeyAsn1 = forge.asn1.fromDer(pkey);
let privateKey = forge.pki.privateKeyFromAsn1(pkeyAsn1);
The test.key file has a format like this:
-----BEGIN RSA PRIVATE KEY-----
{mumbo jumbo line1}
{mumbo jumbo line2}
...
-----END RSA PRIVATE KEY-----
When I tried to import the file, the line fails at pkeyAsn1 = forge.asn1.fromDer(pkey);, giving this error:
Too few bytes to read ASN.1 value.
I don't know too much about the file format, would somebody help me?
The private key file i generated is using the following openssl command:
openssl rsa -in encrypted_test.key -out test.key and I entered my passphrase to decrypt such rsa key.
Read pkey as bytes and use forge.pki.privateKeyFromPem.
Working code:
const forge = require('node-forge');
const fs = require('fs');
const path = require('path');
let pkey = fs.readFileSync(path.join(__dirname, 'test.key'));
let privateKey = forge.pki.privateKeyFromPem(pkey);
I think i figured it out. When reading a private key file, you must include the ---BEGIN RSA PRIVATE KEY--- and ---END RSA PRIVATE KEY--- banner.

Resources