Nodejs Generate a keys to sign a piece of data - node.js

I have some blob data for example:
const buffer = Buffer.from('ACDFF12BA','hex');
How I can generate keys in order to sign it?

One way to do that is by using the keypair npm package:
const keypair = require('keypair');
const crypto = require('crypto');
const pair = keypair();
// Create Transmitted Signature
const sign = crypto.createSign('RSA-SHA256');
sign.update('abcdef'); // data from your file would go here
const sig = sign.sign(pair.private, 'hex');
console.log(sig);
// Verifying Signature
const verify = crypto.createVerify('RSA-SHA256');
verify.write('abcdef');
verify.end();
console.log(verify.verify(pair.public, sig,'hex'));
As you can see using the keypair() function I generate the public and the private keys via a sign.sign I generate the signature. Keep in mind to sign with a Private Key. The generated the public key in the following format:
--- RSA BEGIN PUBLIC KEY ---
^some base64 here^
--- RSA END PUBLIC KEY ---
And the generated private key will have the same format:
--- RSA BEGIN PRIVATE KEY ---
^some base64 here^
--- RSA END PRIVATE KEY ---
Now the only thing to do is to stringify the buffer as a hex string and do the job. Keep in mind that the receiving end needs to verify the data as hex string as well.
We can verify the signature with the verify.verify and a public key. Keep in mind on the signature's format as well. In case you generate a hex encoded signature then verify it in the same format.
Also the methodology above is recommended when the native crypto.generateKeyPair is not available (eg. in electron applications)

Related

Trying to symmetrically encrypt a value for storage in the client (httpOnly cookie) and having an issue decrypting

I am trying to encrypt a value on my server with a private key to store it on the client within an httpOnly cookie.
I am having trouble with the encryption/decryption lifecycle
function encrypt(input) {
const encryptedData = crypto.privateEncrypt(
privateKey,
Buffer.from(input)
)
return encryptedData.toString('base64')
}
function decrypt(input) {
const decryptedData = crypto.privateDecrypt(
{ key: privateKey },
Buffer.from(input, 'base64'),
)
return decryptedData.toString()
}
const enc = encrypt('something moderately secret')
const dec = decrypt(enc)
console.log(dec) // 'something moderately secret'
However the crypto.privateDecrypt function is throwing with
Error: error:04099079:rsa routines:RSA_padding_check_PKCS1_OAEP_mgf1:oaep decoding error
Side question, is it safe to reuse the same private key the server uses to sign JWTs. It's an rsa key generated using ssh-keygen -t rsa -b 4096 -m PEM -f RS256.key
So, you don't use crypto.privateEncrypt() with crypto.privateDecrypt(). That's not how they work. Those functions are for asymmetric encryption, not for symmetric encryption. You use either of these two pairs:
crypto.publicEncrypt() ==> crypto.privateDescrypt()
crypto.privateEncrypt() ==> crypto.publicDecrypt()
So, that's why you're getting the error you're getting. The nodejs doc for crypto.privateDecript() says this:
Decrypts buffer with privateKey. buffer was previously encrypted using the corresponding public key, for example using crypto.publicEncrypt().
If what you really want is symmetric encryption, there are a bunch of options in the crypto module for that. There are some examples shown here: https://www.section.io/engineering-education/data-encryption-and-decryption-in-node-js-using-crypto/ and https://fireship.io/lessons/node-crypto-examples/#symmetric-encryption-in-nodejs.

Azure Connection Fails for type sftpwithssh - "Invalid private key file." [duplicate]

I'm trying to send a file to some server using SFTP. During this process I'm getting the exception
Renci.SshNet.Common.SshException: Invalid private key file. at Renci.SshNet.PrivateKeyFile.Open(Stream privateKey, String passPhrase)
Generated the keys using PuTTYgen, shown below is an sample format of private key file. It has both the public and the private keys.
PuTTY-User-Key-File-2: ssh-rsa
Encryption:none
comment: rsa-key-20190327
Public-Lines: 4
AAAAB.....
......
Private-Lines: 8
AAAAgQ......
.......
Private-MAC: 54901783....
I copied the private key part from the above file in the config file and I'm accessing it as SftpKey in my code.
Got an OpenSSH format of the above key which looks like
------BEGIN RSA PRIVATE KEY-----
MIIE....
.......
------END RSA PRIVATE KEY-------
I copied only the key part from the above file and copied in my config file and ran my code. Issue was not resolved.
Below is the code i'm using for SFTP upload
var fileLength = data.Length;
var keyStr = ConfigurationManager.ConnectionStrings["SftpKey"].ConnectionString;
using (var keystrm = new MemoryStream(Convert.FromBase64String(keyStr)))
{
var privateKey = new PrivateKeyFile(keystrm);
using (var ftp = new SftpClient(_ftpServer, _ftpUser, new[] { privateKey }))
{
ftp.ErrorOccurred += ErrorOccurred;
ftp.Connect();
ftp.ChangeDirectory(_ftpPath);
using (var dataStream = new MemoryStream(Encoding.UTF8.GetBytes(data)))
{
ftp.UploadFile(dataStream, Path.GetFileName(message.MessageId), true,
(length) => result = fileLength == (int)length);
}
ftp.Disconnect();
}
}
Is there anything wrong with the code or what could be the issue? Any help is much appreciated.
As this is the top answer for that error message, so I think it's worthwhile expanding on a point in your original question - converting to an OpenSSH format key.
Renci.SshNet can't use PuTTY keys that start with:
PuTTY-User-Key-File-2: ssh-rsa
You can use puttygen.exe to convert to the OpenSSH format
load your key file in puttygen.exe
Conversions > Export OpenSSH key (not the "force new file format" option)
This will make a key that starts with:
-----BEGIN RSA PRIVATE KEY-----
and that will work
I copied only the key part from the above file
You need to have complete key file in the MemoryStream. And exactly as in the file (as if you were using FileStream with your text key file). So no Convert.FromBase64String.
var keyStr = #"-----BEGIN RSA PRIVATE KEY-----
MIIEoQIBAAKCAQEAiCYlBq7NITBpCCe48asfXKMpnJJJK+7FQj6wIRJCNuBk76tL
jNooDDPPrnrE9VKxRds4olPftjRj87s9gjm4EirbvijZ9PoDlW9CWFhjJPwCPJpA
onkhaiA7SV+abRDQHm/lst5Fk9tzl+DZcS/EleilGDV7rCYEP692UJRsi3GvzngQ
dpRvVvO4o2rXnEkdp+254KHsah0pSxri23+jqbxPguHKGIMylrswokMI0QKcfm+1
/pjrV64EQCxli3i2yPl4WVh/QaNyHMKoze/WN00Pia99QhE1Rm3YCCarFWFeX+R5
7LgIUhtrE7vZGvimfZN7oBdR2pEq10PIc+8q9QIBJQKCAQAWFAYBFW1fU/VbRLY1
Bv4qsqzNSCeKlWwYlItDohiTRvucfKR3tKyMW23JRFdKYG/GI4yks6e8roy/vX+Y
k7z8BvMzl+v+NmFyLbe7TJp0sz6iCy0TbZa3Q388VLFCHmbwLdI4rmwl0I9JD7SO
5SbMM5BkymcU/z71khMvqV21vym5Ge/ApvX0K0XNJs/N/OLnX46Z8taYEyTmreSR
rxAbma4I5BhqXbH0CMOI5u8zCyycghytl5sYyMr+LIWQKWLzQU+mPNN0qIy0pO5t
r8lGNJh5Lnmu1lQw9yAGo2IPPIERP90X64pVrteIjPtt30n87bWDS8gOiam8S/qk
2ZJVAoGBAPZi6E/KpYpzYGKPAfialu0QN1X7uFio1MUmDum+phk5+xeQb/VvlP6Y
d+/o03EMnhvUsop9p7E2CwLZfT6DO7x3LKtumfceq5dPE5hQSWXi9RkBhcOJaZvZ
z+36c8N8iSZZzlxdA5TeDTUqtuVli4HLrcsXaAaVMxEr/G2JwUgTAoGBAI12Gnoy
k/gsiHz4pDLgxWQE6R8vkBMXfQCWhkzvzKca4twQ8z4ZAb/yt+BCiioJn5g58CVS
dP2zd3Lx8e9kkxggZLcUR3Ao6HceYKeD6mx4vkpHiyCtKJI+qfnkw2A64xwbtvTR
h/O5Aq90SjqP4YcaK9E0W/mWYoL3ctFG8DHXAoGBAKZ6LkPARlag+++RDytvXw7h
cX9JN15/6bWkF+oLMfVehw/r+J7qh0Q9gXiWZVo49TVmM3JU5u1b3e0rKxxmgk7o
vVE85JI3UVhl3M6yyc84fBfQmKa2ytEWoT/uaeTzR+l68zd9Hhh6W/N9udlEnIgh
1kr0I7FruriTV4hIUinHAoGASKRudhn49Q/zD73zdBKO4FWMd8xQ5zWTN6c+C9UW
EJ8ajK7CGPgVp8HUC2BwdnOk+ySrwCNsgkdm2ik3DDqQuVy+GNMP7XzKZq68Av6N
IvHlLQ/7VfgN6jvavpgRTRdSB4Pafbe0hBLltAtItknig6WnzEtR0zGMiHE69dhR
1GcCgYBckoyMXpT0HzOjLXWClSiIaDDfgGcmgEKbYJ7c3mncjLinbCVFdJ0UcrqJ
tiauWBvmecAhnJvQGnmInawNUHetAgJoCbqd7cckjI8VtBgHlQyT93wo9fSDz0Kt
dDHspRvVQkhiR/6IWz1PtCT0QGrHP8fJq/PCbLnJf/EJqJv/xA==
-----END RSA PRIVATE KEY-----
";
using (var keystrm = new MemoryStream(Encoding.ASCII.GetBytes(keyStr)))
{
var privateKey = new PrivateKeyFile(keystrm);
}
Actually with the earlier code i was showing there is an existing implementation where the file is successfully sent.
Then your connection string does not contain what you claim. Check the implementation of PrivateKeyFile.Open. It explicitly checks that the stream starts with ---- BEGIN ... PRIVATE KEY. If not, it throws "Invalid private key file."
It's actually probably even impossible (or difficult) to store multi-line contents into the connection string. If your code ever worked, it must be because your SftpKey connection strings contain a complete key file (including the BEGIN ... PRIVATE KEY envelope) but encoded (again) in Base64 encoding (as a single line). Like this:
Convert.ToBase64String(File.ReadAllBytes(#"C:\path\to\key"))
Which will give you string like:
LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQ0KTUlJRW9RSUJBQUtDQVFFQWlDWWxCcTdOSVRCcENDZTQ4YXNmWEtNcG5KSkpLKzdGUWo2d0lSSkNOdUJrNzZ0TA0Kak5vb0REUFBybnJFOVZLeFJkczRvbFBmdGpSajg3czlnam00RWlyYnZpalo5UG9EbFc5Q1dGaGpKUHdDUEpwQQ0Kb25raGFpQTdTVithYlJEUUhtL2xzdDVGazl0emwrRFpjUy9FbGVpbEdEVjdyQ1lFUDY5MlVKUnNpM0d2em5nUQ0KZHBSdlZ2TzRvMnJYbkVrZHArMjU0S0hzYWgwcFN4cmkyMytqcWJ4UGd1SEtHSU15bHJzd29rTUkwUUtjZm0rMQ0KL3BqclY2NEVRQ3hsaTNpMnlQbDRXVmgvUWFOeUhNS296ZS9XTjAwUGlhOTlRaEUxUm0zWUNDYXJGV0ZlWCtSNQ0KN0xnSVVodHJFN3ZaR3ZpbWZaTjdvQmRSMnBFcTEwUEljKzhxOVFJQkpRS0NBUUFXRkFZQkZXMWZVL1ZiUkxZMQ0KQnY0cXNxek5TQ2VLbFd3WWxJdERvaGlUUnZ1Y2ZLUjN0S3lNVzIzSlJGZEtZRy9HSTR5a3M2ZThyb3kvdlgrWQ0Kazd6OEJ2TXpsK3YrTm1GeUxiZTdUSnAwc3o2aUN5MFRiWmEzUTM4OFZMRkNIbWJ3TGRJNHJtd2wwSTlKRDdTTw0KNVNiTU01Qmt5bWNVL3o3MWtoTXZxVjIxdnltNUdlL0FwdlgwSzBYTkpzL04vT0xuWDQ2Wjh0YVlFeVRtcmVTUg0KcnhBYm1hNEk1QmhxWGJIMENNT0k1dTh6Q3l5Y2doeXRsNXNZeU1yK0xJV1FLV0x6UVUrbVBOTjBxSXkwcE81dA0KcjhsR05KaDVMbm11MWxRdzl5QUdvMklQUElFUlA5MFg2NHBWcnRlSWpQdHQzMG44N2JXRFM4Z09pYW04Uy9xaw0KMlpKVkFvR0JBUFppNkUvS3BZcHpZR0tQQWZpYWx1MFFOMVg3dUZpbzFNVW1EdW0rcGhrNSt4ZVFiL1Z2bFA2WQ0KZCsvbzAzRU1uaHZVc29wOXA3RTJDd0xaZlQ2RE83eDNMS3R1bWZjZXE1ZFBFNWhRU1dYaTlSa0JoY09KYVp2Wg0KeiszNmM4TjhpU1paemx4ZEE1VGVEVFVxdHVWbGk0SExyY3NYYUFhVk14RXIvRzJKd1VnVEFvR0JBSTEyR25veQ0Kay9nc2lIejRwRExneFdRRTZSOHZrQk1YZlFDV2hrenZ6S2NhNHR3UTh6NFpBYi95dCtCQ2lpb0puNWc1OENWUw0KZFAyemQzTHg4ZTlra3hnZ1pMY1VSM0FvNkhjZVlLZUQ2bXg0dmtwSGl5Q3RLSkkrcWZua3cyQTY0eHdidHZUUg0KaC9PNUFxOTBTanFQNFljYUs5RTBXL21XWW9MM2N0Rkc4REhYQW9HQkFLWjZMa1BBUmxhZysrK1JEeXR2WHc3aA0KY1g5Sk4xNS82YldrRitvTE1mVmVody9yK0o3cWgwUTlnWGlXWlZvNDlUVm1NM0pVNXUxYjNlMHJLeHhtZ2s3bw0KdlZFODVKSTNVVmhsM002eXljODRmQmZRbUthMnl0RVdvVC91YWVUelIrbDY4emQ5SGhoNlcvTjl1ZGxFbklnaA0KMWtyMEk3RnJ1cmlUVjRoSVVpbkhBb0dBU0tSdWRobjQ5US96RDczemRCS080RldNZDh4UTV6V1RONmMrQzlVVw0KRUo4YWpLN0NHUGdWcDhIVUMyQndkbk9rK3lTcndDTnNna2RtMmlrM0REcVF1VnkrR05NUDdYektacTY4QXY2Tg0KSXZIbExRLzdWZmdONmp2YXZwZ1JUUmRTQjRQYWZiZTBoQkxsdEF0SXRrbmlnNlduekV0UjB6R01pSEU2OWRoUg0KMUdjQ2dZQmNrb3lNWHBUMEh6T2pMWFdDbFNpSWFERGZnR2NtZ0VLYllKN2MzbW5jakxpbmJDVkZkSjBVY3JxSg0KdGlhdVdCdm1lY0Fobkp2UUdubUluYXdOVUhldEFnSm9DYnFkN2Nja2pJOFZ0QmdIbFF5VDkzd285ZlNEejBLdA0KZERIc3BSdlZRa2hpUi82SVd6MVB0Q1QwUUdySFA4ZkpxL1BDYkxuSmYvRUpxSnYveEE9PQ0KLS0tLS1FTkQgUlNBIFBSSVZBVEUgS0VZLS0tLS0NCg==
The same error can also occur if the private key is incorrectly formatted such as being squashed onto a single line whereby it will not match the regular expression at https://github.com/sshnet/SSH.NET/blob/develop/src/Renci.SshNet/PrivateKeyFile.cs#L156
The private key format must be over multiple lines wrapping at 80 cols.
https://github.com/sshnet/SSH.NET/blob/develop/src/Renci.SshNet/PrivateKeyFile.cs#L68 - the expression contains "{1,80}".
In my case the private key is not protected with a passphrase so I was able to use the following code to correctly reformat it; reintroducing the newlines; before constructing the memory stream.
// PEM Format Private Key substituting newlines with a space
var privateKeyString = #"-----BEGIN RSA PRIVATE KEY----- line1 line2 line3 -----END RSA PRIVATE KEY-----";
// Group 1: "-----BEGIN RSA PRIVATE KEY-----"
// Group 2: " line1 line2 line3 "
// Group 3: "-----END RSA PRIVATE KEY-----"
var regex = new Regex(#"^\s*(-+[^-]+-+)([^-]+)(-+[^-]+-+)");
var matches = regex.Match(privateKeyString);
var formatted = string.Concat(
matches.Groups[1].Value,
matches.Groups[2].Value.Replace(" ", "\r\n"),
matches.Groups[3].Value);
// ASCII encoding is fine because we're dealing with the base64 alphabet.
var ms = new MemoryStream(Encoding.ASCII.GetBytes(formatted));
var privateKeyFile = new PrivateKeyFile(ms);

Create RSA-SHA256 sign with PEM key

I've been creating a gateway for a legacy service, this legacy service needs a signature as a body parameter of a PUT request, in order to create this sign I need to follow the following steps:
Create a hash with certain text as data, this hash needs to be SHA256.
Encrypt the result of the hash using RSA with a PEM key
Encode the result of the RSA to Base64
Following the previous steps I create the following code
export class Signature {
// class body
public static sign(text: string){
const key = readFileSync('key.pem')
const passphrase = '12345678'
const createdSign = createSign('RSA-SHA256')
createdSign.write(text)
createdSign.end()
return createdSign.sign({ key, passphrase }).toString('base64')
}
}
But I'm not sure if this the correct implementation, taking into consideration the previous steps, and the existence of the hash API in NodeJS.
If someone could tell me if I'm correctly implementing this algorithm.

How to generate a PEM-formatted Key from a 64Byte raw hex-formatted Key

I have the following problem:
After recreating the public key from a signed transaction, I try to encrypt some payload with it.
However the node.js-module named "crypto" is expecting a pem-formatted key in the publicEncrypt(key, payload) function.
My Question:
How can I create the pem-formatted key from a raw hex-encoded key?
Here is the recreated 64 Byte public key:
9f9f445051e788461952124dc08647035c0b31d51f6b4653485723f04c9837adb275d41731309f6125c14ea1546d86a27158eec4164c00bab4724eed925e9c60
Information:
I know, that a pem-format-key consists of base64 encoded data, a header and a footer.
-----BEGIN RSA PUBLIC KEY-----
BASE64 ENCODED DATA
-----END RSA PUBLIC KEY-----
I have also found out that within the base64 encoded data the following DER-structure is present:
RSAPublicKey ::= SEQUENCE {
modulus INTEGER, -- n
publicExponent INTEGER -- e
}
So the only question is how to get from the raw hex-encoded key to this DER-structure.
I would appreciate any help!
Problem solved
Thanks to Maarten Bodewes and his comment regarding the key being secp256k1 and not RSA.
After some further research, I finally managed to encrypt/decrypt a message asymmetrically with secp256k1 keys.
With the help of Cryptos ECDH class I managed to create a key-object and then assign the private key to it. When assigned, you can easily derive the public key with getPublicKey(). All participants would create a key object for themselves and assign their private keys to it. Then they share their retrieved public keys (in my case over a shared medium). In addition I used a npm-package named standard-ecies which provides the ECIES encryption-scheme.
Code:
const crypto = require('crypto');
const ecies = require('standard-ecies');
var buffer = new Buffer("Hello World");
var ecdh = crypto.createECDH('secp256k1');
ecdh.setPrivateKey(privateKey);
var encryptedText = ecies.encrypt(ecdh.getPublicKey(), buffer);
var decryptedText = new Buffer(ecies.decrypt(ecdh, encryptedText));
I should have noticed this, because crypto's encryption function (link to the api-doc) clearly works only with RSA keys and not with secp256k1 keys.
Anyway if someone has a similar issue, I hope this answer helps!

NodeRSA key header and footer

I am using Node.js RSA library (https://github.com/rzcoder/node-rsa) to generate a public and private key pair with the following codes:
const key = new nodeRSA();
key.generateKeyPair(2048, 65537);
const pemPublicKey = key.exportKey('pkcs1-public-pem');
const pemPrivateKey = key.exportKey('pkcs1-private-pem');
The key I get is as follows:
-----BEGIN RSA PUBLIC KEY-----\nMIIBCgKCAQEAnvrbDfGOT9pmKWZafkEizt8WfMbhmf46e7zyHMRQNHTxPKuP89fc\n5BAkhXylC9ozfjTjiQb5wDh1yw5HafyAKE4Jh28fzX1TJnVra1ijpQTte4+v1WVe\na8qxBuzUI6bxJtR/AV1XyfeWbYx27lSenw2ynqiut+oQ5MZ9kOxX4ba+/cWYcvMn\ni0OnhnNIQp0a+cY78sfz/LpDMumWDVZKvOTREg1y9KxGkd/yyYrHyxAAsfijY/47\n70KH0c4FjjYrWipVHAHj/ayhoAFRBFY9uI9pqLamf8AfBsjvIT16/viT4LE6kUEu\nU2zxOUevkjTq3tgOZoFomiSDJC1EopVhvQIDAQAB\n-----END RSA PUBLIC KEY-----
Question is: how can I get rid of the header and footer and the \n in the base64 string?
The reason I want to do it is just because I wanna the key to be consistent with what I have in the database
Thanks!
According to Import/Export documentation you can export the key as DER and convert the result to base64. (PEM is DER binary format converted to base64 and adding header and footer)
const derPublicKey = key.exportKey('pkcs1-public-der');
const derPrivateKey = key.exportKey('pkcs1-private-der');
I'm not familiar with node.js. Converting binary to base64 should look something like this
var derB64PublicKey = new Buffer(derPublicKey , 'binary').toString('base64');
var derB64PrivateKey = new Buffer(derPrivateKey , 'binary').toString('base64');
Alternatively it would be easy to delete header, footer and \n from the PEM data, but the option to export to DER seems more reasonable

Resources