Is it possible to generate RSA keys from a string? - node.js

I am using the node-rsa package to do asymmetric encryption with the RSA function.
Currently, I am generating my public and private keys like so:
generateKeys = function() {
const key = new NodeRSA({ b: 1024 });
return {
public: key.exportKey('public'),
private: key.exportKey('private'),
}
}
Is there any possible way to generate a key from a given string? I want to do this so that my users can easily write down their private key, which is important to my project. After all, it is nearly impossible to write down a private key that is 1024 characters long.
I am hoping for something like this:
const key = new NodeRSA({ b: 1024 }, "Secret goes here")
I think this may be possible because the sha256 function can accept any string to hash. I know RSA encryption isn't really a hashing function, so I am not sure if the same effect is possible.
Any help is appreciated!

It seems that there is a simple way of doing this. There is a package called cryptico
that can do the thing.
First install the package:
npm i cryptico
Example
const cryptico = require('cryptico');
const PassPhrase = "The Moon is a Harsh Mistress.";
// The length of the RSA key, in bits.
const Bits = 1024;
const RSAkey = cryptico.generateRSAKey(PassPhrase, Bits);
const PublicKeyString = cryptico.publicKeyString(RSAkey);
A better and complete example can be found here
Update
So, if you need an ASYMMETRIC encryption (As I know that works with a pair of keys which are called publickey and privatekey) you can simply use a pure Node.js implementation.
Example
const { generateKeyPairSync, publicEncrypt, privateDecrypt } = require('crypto');
const PassPhrase = "The Moon is a Harsh Mistress.";
const Bits = 1024;
const encryptWithRSA = (input, publickey) => {
const buffer = Buffer.from(input, 'utf-8');
const encrypted = publicEncrypt(publicKey, buffer);
return encrypted.toString("base64");
}
const decryptWithRSA = function (input, privatekey) {
const buffer = Buffer.from(input, 'base64');
const decrypted = privateDecrypt(
{
key: privatekey,
passphrase: PassPhrase,
},
buffer,
)
return decrypted.toString("utf8");
};
const { privateKey, publicKey } = generateKeyPairSync('rsa', {
modulusLength: Bits,
publicKeyEncoding: {
type: 'spki',
format: 'pem'
},
privateKeyEncoding: {
type: 'pkcs8',
format: 'pem',
cipher: 'aes-256-cbc',
passphrase: PassPhrase
}
});
const encrypted = encryptWithRSA('yes i know', publicKey)
console.log(encrypted);
console.log(decryptWithRSA(encrypted, privateKey));
Output (The encrypted value is random as you know)
t3Gw+PlKn84gx2wkj99we345C6ZjIElpgDkzXjio2aWRwI28qTMev14H7o219P6Lw9STGnfK4FgTYO/cvLGlzplqRv6X5AgrstwsGQN88wmKHe5+6cxlpzPFNPWLUqtAvsIOPZe+ghaRGCkOT2ETUp4PiqwOTJ2EtmyVqQHt+f4=
yes i know

Related

What is WebCrypto.subtle.decrypt analog for node.js crypto.createDecipheriv (AES-CTR algorithm)?

I am trying to rewrite old NodeJs encryption algorithm from
crypto.createDecipheriv(algorithm, key, iv[, options])
into webcrypto
subtle.decrypt(algorithm, key, data)
This code work good enough with AES-128-CTR algorithm
const algorithm = 'aes-128-ctr';
const iv = '0123456789ABCDEF0123456789ABCDEF';
const privateKey = '16Random_Letters';
const hexBufferFromIv = Buffer.from(iv, 'hex');
const utfBufferFromPrivateKey = Buffer.from(privateKey, 'utf8');
function oldEncryptData(data: string): string {
const cipher = createCipheriv(
algorithm,
utfBufferFromPrivateKey,
hexBufferFromIv,
);
let crypted = cipher.update(data, 'utf8', 'base64');
crypted += cipher.final('base64');
return crypted;
}
function oldDecryptData(data: string): string {
const decipher = createDecipheriv(
algorithm,
utfBufferFromPrivateKey,
hexBufferFromIv,
);
let dec = decipher.update(data, 'base64', 'utf8');
dec += decipher.final('utf8');
return dec;
}
async function testDecrypt() {
const sourceText = `any text to encrypt!`;
const encryptedText = oldEncryptData(sourceText);
const decryptedText = oldDecryptData(encryptedText);
return sourceText === decryptedText;
}
testDecrypt().then(console.log);
Right now I test this code and WebCrypto examples in nodejs, but as a final result I wont to move webCrypto.subtle.decrypt functionality into NGINX njs and as I know, njs doesn't support other options for decryption except for WebCrypto.
Interface for WebCrypto decrypt for AES-CTR in general looks like
const data = await crypto.subtle.decrypt(
{
name: "AES-CTR",
counter, // BufferSource
length: 128, // 1-128
},
key, // AES key
encData, // BufferSource
);
And I don't undersatnd.
counter is the same thing as the Initialization vector in createDecipheriv method?
How I should generate key for subtle.decrypt method from the same passphrase?
Do I need to do any additional transformation from or to base64 or utf8 encoding to reproduce input and output encoding in cipher.update(data, 'utf8', 'base64'); and in decipher.update(data, 'base64', 'utf8'); methods?
Thanks Topaco for hints. I'll write a more complete answer. Maybe it will be useful for someone.
Yes, Initialization vector and counter can be treated as the same thing.
For generating a key from the same passphrase you should use importKey method. And you should sent the same ArrayBuffer from the passphrase as in createCipheriv method.
Yes, if your old method used some specific encoding and decoding, you should repeat the same encoding/decoding logic after Webcrypto.SubtleCrypto.encrypt() and decrypt() methods.
Full workable example may looks something like
import { webcrypto } from 'crypto';
const iv = '0123456789ABCDEF0123456789ABCDEF';
const privateKey = '16Random_Letters';
const hexBufferFromIv = Buffer.from(iv, 'hex');
const utfBufferFromPrivateKey = Buffer.from(privateKey, 'utf8');
async function generateKeyFromPassPhrase(): Promise<CryptoKey> {
return webcrypto.subtle.importKey(
'raw',
utfBufferFromPrivateKey,
{
name: 'AES-CTR',
},
true,
['decrypt', 'encrypt'],
);
}
async function newEncryptData(data: string): Promise<string> {
const key = await generateKeyFromPassPhrase();
const encryptResult = await webcrypto.subtle.encrypt(
{
name: 'AES-CTR',
length: 128,
counter: hexBufferFromIv,
},
key,
Buffer.from(data),
);
return Buffer.from(encryptResult).toString('base64');
}
async function newDecryptData(data: string): Promise<string> {
const key = await generateKeyFromPassPhrase();
const decryptResult = await webcrypto.subtle.decrypt(
{
name: 'AES-CTR',
length: 128,
counter: hexBufferFromIv,
},
key,
Buffer.from(data, 'base64'),
);
return Buffer.from(decryptResult).toString();
}
async function testDecrypt() {
const sourceText = `any text to encrypt!`;
const encrypted2 = await newEncryptData(sourceText);
const decrypted2 = await newDecryptData(encrypted2);
return sourceText === decrypted2;
}
testDecrypt().then(console.log);

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);

CrytoJS AES: Encrypted string gets decrypted to empty string

I was tinkering with crypto-js and AES.
I have a seemingly straightforward piece of code which takes a plainText and encrypts it using AES with a key and an initial vector.
When I try to decrypt the encrypted text, it gets decrypted to empty string for some reason.
This is the snippet:
const { enc, AES } = require("crypto-js");
const KEY = enc.Utf8.parse("this is a key");
const IV = enc.Utf8.parse("this is initial vector");
const originalText = "someone#example.com";
const hash = AES.encrypt(originalText, KEY, { iv: IV });
const hashText = hash.toString();
console.log(`"${originalText}" was encrypted to "${hashText}"`);
const hashTextCopy = `${hashText}`;
const decrypt = AES.decrypt(hashTextCopy, KEY, { iv: IV });
const decryptText = decrypt.toString(enc.Utf8);
console.log(`"${hashTextCopy}" was decrypted to "${decryptText}"`);
The output I get is:
"someone#example.com" was encrypted to "IgyDXGNVD8IokknoZqjamG0QecGvBM/dyxx4il8gCHA="
"IgyDXGNVD8IokknoZqjamG0QecGvBM/dyxx4il8gCHA=" was decrypted to ""
Can someone explain what is going on? I have seen quite a few examples of it over the Internet and they all seem to working just fine. But here, the text is not getting decrypted.
PS: The version I am using is "crypto-js": "^3.1.9-1",
Maybe try changing your code ever so slightly, this works for me.
As stated in the comments, I believe the problem with your initial example was the key length.
const { enc, AES } = require("crypto-js");
// Keep as a string..
const KEY = "this is a key";
const IV = enc.Utf8.parse("this is initial vector");
const originalText = "someone#example.com";
const hash = AES.encrypt(originalText, KEY, { iv: IV });
const hashText = hash.toString();
console.log(`"${originalText}" was encrypted to "${hashText}"`);
const hashTextCopy = `${hashText}`;
const decrypt = AES.decrypt(hashTextCopy, KEY, { iv: IV });
const decryptText = decrypt.toString(enc.Utf8);
console.log(`"${hashTextCopy}" was decrypted to "${decryptText}"`);
This also works:
const { enc, AES } = require("crypto-js");
// 128-bit key works nicely
const KEY = enc.Hex.parse("000102030405060708090a0b0c0d0e0f");
const IV = enc.Utf8.parse("this is initial vector");
const originalText = "someone#example.com";
const hash = AES.encrypt(originalText, KEY, { iv: IV });
const hashText = hash.toString();
console.log(`"${originalText}" was encrypted to "${hashText}"`);
const hashTextCopy = `${hashText}`;
const decrypt = AES.decrypt(hashTextCopy, KEY, { iv: IV });
const decryptText = decrypt.toString(enc.Utf8);
console.log(`"${hashTextCopy}" was decrypted to "${decryptText}"`);
The key:
const KEY = enc.Utf8.parse("abcdfghi");
Will also work correctly (since it's 128-bits). 256 would work too.
const KEY = enc.Utf8.parse("abcdfghijklmnopq");
If you just use a pass phrase, a 256-bit key will be generated from it.

create RSA SHA256 using crypto for jsonwebtoken on nodejs

I see how to create HMAC 256 encrypted JWTs in NodeJS using Crypto, and the jsonwebtoken library. It's pretty easy
//encrypt & sign with HS256
const jwt = require('jsonwebtoken');
const pass = crypto.randomBytes(256).toString('hex');
const A = {algorithm:'HS256'};
const token = jwt.sign({ foo: 'bar' }, pass, A);
//decrypt & verify
jwt.verify(token, pass, A, function(err, decoded)
{
console.log('decode ',decoded);
console.log('err ',err);
res.send({error:err, text:decoded});
});
I would like to replace 'pass' with an 'RSA 256 SHA', and A with {algorithm:'RS256'}
In node crypto JS's documentation I see the command
const sign = crypto.createSign('RSA-SHA256');
to create the rsa-sha256
However then the documentation has the function
getPrivateKeySomehow()
Which is not defined or part of crypto.getPrivateKeySomehow()
So I guess I just need help getting the RSA-SHA-256 string returned from crypto, so that I can pass that into jsonwebtoken to sign my JWT.
Note: I do not want to read a private.key from a static file on my server because i see having a single private key for all my users as too big a security risk, hence why i am generating my own 256 bytes passwords and storing them off-site (not included in this post)
also, i'm not sure if i should do something like this (without using something like openssl from command line?)
const begin = '-----BEGIN RSA PRIVATE KEY-----\n';
const enc = crypto.randomBytes(256).toString('base64') + '\n';
const end = '-----END RSA PRIVATE KEY-----'
const pass = sign.sign(begin + enc + end);
const A = {algorithm:'RS256'};
const token = jwt.sign({ foo: 'bar' }, pass, A);
Take a look at this example bellow:
signExample = (str) => {
crypto.generateKeyPair('rsa', {
modulusLength: 1024,
publicKeyEncoding: {
type: 'spki',
format: 'pem'
},
privateKeyEncoding: {
type: 'pkcs8',
format: 'pem'
}
}, (err, publicKey, privateKey) => {
// sign String
var signerObject = crypto.createSign("RSA-SHA256");
signerObject.update(str);
var signature = signerObject.sign({key:privateKey,padding:crypto.constants.RSA_PKCS1_PSS_PADDING}, "base64");
console.info("signature: %s", signature);
//verify String
var verifierObject = crypto.createVerify("RSA-SHA256");
verifierObject.update(str);
var verified = verifierObject.verify({key:publicKey, padding:crypto.constants.RSA_PKCS1_PSS_PADDING}, signature, "base64");
console.info("is signature ok?: %s", verified);
});
}
Steps:
First you create an key pair with crypto.generateKeyPair('rsa', { ... desired key options
Create a Sign object - crypto.createSign("RSA-SHA256")
The string wanted to be signed - SignerObject.update(str)
Sign the string with your private key - signerObject.sign(
Option to salt - padding:crypto.constants.RSA_PKCS1_PSS_PADDING

How to create a pair private/public keys using Node.js crypto?

I have to generate two keys (private and public) to encrypt a text with the public and let the user with the private key decrypt the text.
Is it possible with the module Crypto?
nodejs v10.12 now supports this natively with crypto.generateKeyPair
const { generateKeyPair } = require('crypto');
generateKeyPair('rsa', {
modulusLength: 4096,
publicKeyEncoding: {
type: 'spki',
format: 'pem'
},
privateKeyEncoding: {
type: 'pkcs8',
format: 'pem',
cipher: 'aes-256-cbc',
passphrase: 'top secret'
}
}, (err, publicKey, privateKey) => {
// Handle errors and use the generated key pair.
});
Use the crypto module from npm to generate KeyPair.
var crypto = require('crypto');
var prime_length = 60;
var diffHell = crypto.createDiffieHellman(prime_length);
diffHell.generateKeys('base64');
console.log("Public Key : " ,diffHell.getPublicKey('base64'));
console.log("Private Key : " ,diffHell.getPrivateKey('base64'));
console.log("Public Key : " ,diffHell.getPublicKey('hex'));
console.log("Private Key : " ,diffHell.getPrivateKey('hex'));
Above is a example snippet. To know more checkout documentation http://nodejs.org/api/crypto.html
The following code works, but I'm not a professional cryptographer, so some comments here would be useful.
I've used the ursa RSA module, instead of crypto.
I am concerned that if similar data were encrypted directly, without a pass of AES or similar, then it might be trivial to break this. Comments please...
var ursa = require('ursa');
var fs = require('fs');
// create a pair of keys (a private key contains both keys...)
var keys = ursa.generatePrivateKey();
console.log('keys:', keys);
// reconstitute the private key from a base64 encoding
var privPem = keys.toPrivatePem('base64');
console.log('privPem:', privPem);
var priv = ursa.createPrivateKey(privPem, '', 'base64');
// make a public key, to be used for encryption
var pubPem = keys.toPublicPem('base64');
console.log('pubPem:', pubPem);
var pub = ursa.createPublicKey(pubPem, 'base64');
// encrypt, with the public key, then decrypt with the private
var data = new Buffer('hello world');
console.log('data:', data);
var enc = pub.encrypt(data);
console.log('enc:', enc);
var unenc = priv.decrypt(enc);
console.log('unenc:', unenc);
After some further investigation http://en.wikipedia.org/w/index.php?title=RSA_%28cryptosystem%29&section=12#Attacks_against_plain_RSA it looks like ursa already does padding.
const crypto = require('crypto');
const { privateKey, publicKey } = crypto.generateKeyPairSync('rsa', {
modulusLength: 2048,
publicKeyEncoding: {
type: 'spki',
format: 'pem'
},
privateKeyEncoding: {
type: 'pkcs8',
format: 'pem'
}
});
If you know how to get what you want from OpenSSL, I think it's perfectly reasonable to run OpenSSL using Node's child_process.
var cp = require('child_process')
, assert = require('assert')
;
var privateKey, publicKey;
publicKey = '';
cp.exec('openssl genrsa 2048', function(err, stdout, stderr) {
assert.ok(!err);
privateKey = stdout;
console.log(privateKey);
makepub = cp.spawn('openssl', ['rsa', '-pubout']);
makepub.on('exit', function(code) {
assert.equal(code, 0);
console.log(publicKey);
});
makepub.stdout.on('data', function(data) {
publicKey += data;
});
makepub.stdout.setEncoding('ascii');
makepub.stdin.write(privateKey);
makepub.stdin.end();
});
I dont know if this helps but I also was looking to do something along these lines.
Here is what I came up with :
As mentioned in the answer by Nelson Owalo you can use the crypto library, as follows :
//import the methods
const { generateKeyPair, createSign, createVerify } = require("crypto");
//generate the key pair
generateKeyPair(
"rsa",
{
modulusLength: 2048, // It holds a number. It is the key size in bits and is applicable for RSA, and DSA algorithm only.
publicKeyEncoding: {
type: "pkcs1", //Note the type is pkcs1 not spki
format: "pem",
},
privateKeyEncoding: {
type: "pkcs1", //Note again the type is set to pkcs1
format: "pem",
//cipher: "aes-256-cbc", //Optional
//passphrase: "", //Optional
},
},
(err, publicKey, privateKey) => {
// Handle errors and use the generated key pair.
if (err) console.log("Error!", err);
console.log({
publicKey,
privateKey,
});//Print the keys to the console or save them to a file.
/*
* At this point you will have to pem files,
* the public key which will start with
* '-----BEGIN RSA PUBLIC KEY-----\n' +
* and the private key which will start with
* '-----BEGIN RSA PRIVATE KEY-----\n' +
*/
//Verify it works by signing some data and verifying it.
//Create some sample data that we want to sign
const verifiableData = "this need to be verified";
// The signature method takes the data we want to sign, the
// hashing algorithm, and the padding scheme, and generates
// a signature in the form of bytes
const signature = require("crypto").sign("sha256", Buffer.from(verifiableData),
{
key: privateKey,
padding: require("crypto").constants.RSA_PKCS1_PSS_PADDING,
});
//Convert the signature to base64 for storage.
console.log(signature.toString("base64"));
// To verify the data, we provide the same hashing algorithm and
// padding scheme we provided to generate the signature, along
// with the signature itself, the data that we want to
// verify against the signature, and the public key
const isVerified = require("crypto").verify(
"sha256",
Buffer.from(verifiableData),
{
key: publicKey,
padding: require("crypto").constants.RSA_PKCS1_PSS_PADDING,
},
Buffer.from(signature.toString("base64"), "base64")
);
// isVerified should be `true` if the signature is valid
console.log("signature verified: ", isVerified);
}
);
I think the key points are which algorithm is used, as older versions of the pem use pkcs1 not pkcs8. The beginning of the key helps identify the version of the key and also includes information on wither it is encrypted or not. Hope this helps!
I have not used it, but this may be useful:
http://ox.no/posts/diffie-hellman-support-in-node-js
Documentation is severely lacking on this (no examples that I could find).
You can use this rsa-json module. It just spawns a openssl process, so it is pretty dependent on the OS (it does not work by default on windows).
child_process route is a terrible and non-scalable solution imo. Stay away.
I chose to go with keypair instead.

Resources