Generate RSA key-pair from "N", "E", "D" keys using crypto module - node.js

i'm new to cryptography.
I'm creating the RSA key-pairs using crypto.generateKeyPairSync()
const crypto = require('crypto')
const { publicKey, privateKey } = crypto.generateKeyPairSync('rsa', {
modulusLength: 2048,
publicExponent: 3,
publicKeyEncoding: {
type: 'pkcs1',
format: 'pem'
},
privateKeyEncoding: {
type: 'pkcs1',
format: 'pem'
}
})
console.log(publicKey)
console.log(privateKey)
// print "n", "e", "d" keys
This works fine but the i need to extract the "n", "e", "d" keys so that an other app can encrypt and decrypt the messages. It would be great if this is possible without any 3rd-party libraries only the native NodeJS crypto module.
Also if it's not possible to extract the "n", "e", "d" keys, would it be possible to create a new public and private key using existing "n", "e", "d" keys from an other app?

This works fine but the i need to extract the "n", "e", "d" keys so that an other app can encrypt and decrypt the messages. It would be great if this is possible without any 3rd-party libraries only the native NodeJS crypto module.
First of all, the modulus n, public exponent e and private exponent d are not keys, they are components that are used to make up a key. As an RSA key contains at least two components you need some kind of method to distinguish them from each other. You need some kind of data structure which separates the components, which is exactly what the PKCS#1 ASN.1 specifications provide. Just specifying that you will deliver an RSA key using a PEM formatted PKCS#1 should be enough.
The private key, as the name suggests, should really not be shared. There is no such thing as a shared private key after all. Keys are generated for one specific entity, and are usually kept where they are generated (possibly exempting encrypted backup).
If you use only n and d then you will probably leave out any generated CRT parameters for the private key, which means that in the best case the RSA key operations don't run. In the worst case the RSA implementation is extremely picky and won't run at all.
Finally, as the documentation correctly indicates, you should be using 'spki' as output format (the public key as used within certificates, called the SubjectPublicKeyIdentifier, specified in the X.509 standards) and 'pkcs8' as they contain an indication of the key type used. These are probably better supported by other applications than PKCS#1.
If they are not able to decode it themselves then simply copy the base 64 into this site (online ASN.1 decoder) and copy the relevant values. You can find the order of the values (for 'pkcs1') in the PKCS#1 standard.

You could use the library pem-jwk to extract the components of the public/private keys like this:
const pem2jwk = require('pem-jwk').pem2jwk
console.log(pem2jwk(pemPublicKey));
OUTPUT:
{
kty: '...',
n: '...',
e: '...'
}
console.log(pem2jwk(pemPrivateKey));
OUTPUT:
{
kty: '...',
n: '...',
e: '...',
d: '...',
p: '...',
q: '...',
dp: '...',
dq: '...',
qi: '...'
}

Related

Nodejs crypto: Elliptic Curve to sign message and export public key as text

I want to achieve the following with the Nodejs crypto module:
I want to sign a message with my private key on a defined EC and have the signature as raw buffer/hex.
I want to have the respective public key as raw buffer/hex.
I can achieve both goals individually, but I can not achieve them together currently and it seems strange, that this is so hard to achieve with the node crypto module.
With the following code it is easy to generate a signature on a curve, but I cannot manage to decode the publicKey:
//Get pub and priv key from curve
const {publicKey, privateKey} = crypto.generateKeyPairSync('ec', {'namedCurve' : 'secp128r1'});
var message = "Hello";
var signer = crypto.createSign('sha256');
signer.update(message);
// Signature as raw hex. That's what I want.
var sigString = signer.sign(privateKey, 'hex'); // Needs a proper KeyObject
And with this code it is easy to extract the public key for a private key from a curve but the key returned by curve.getPrivateKey is not allowed for signing:
refCurve = crypto.createECDH('secp128r1');
//Public key split into x and y components. That's what I want.
refPubKey = {
x: '0x' + curve.getPublicKey('hex').slice(2,34),
y: '0x' + curve.getPublicKey('hex').slice(-32)
}
It is not possible for me to achieve both at the same time. The problem is, that the curve object can ONLY export as buffer/hex, while all the signature functions in crypto ONLY accept proper KeyObjects. And at the same time it seems not possible to convert both into each other. The KeyObjects have no functionality to export to buffer and it is not possible to create a KeyObject from the raw key, exported from the curve. What am I missing? I was also trying to set the respective private Key on the curve with curve.setPrivateKey(privateKey) but even this does not work.
Alternatively, I would also be open to use another Node library, but those I found do not seem to support the curve that I want to use (SECP128R1)
For example, this is how easy it is with python and the ecdsa library:
sk = SigningKey.generate(curve=SECP128r1, hashfunc=sha256)
vk = sk.verifying_key
message = b"Hello"
m = sha256(message)
//Signature
signature = sk.sign(message)
//Public Key
[vk.to_string()[:16], vk.to_string()[16:]]
Thankful for any help!
You can export the public key with export() in X.509/SPKI format and DER encoded. For a secp128r1 key the last 32 bytes are the concatenation of x and y:
const { publicKey, privateKey } = crypto.generateKeyPairSync('ec', { 'namedCurve': 'secp128r1' });
// ...
var key = publicKey.export({ type: 'spki', format: 'der' });
var rawX = key.subarray(-32, -16);
var rawY = key.subarray(-16);
console.log(key.toString('hex')); // e.g. 3036301006072a8648ce3d020106052b8104001c03220004d15885cfb3c75417bbeb95625da313dcb4d27ecb6f89923ae539faa7c09b4797
console.log(rawX.toString('hex')); // e.g. d15885cfb3c75417bbeb95625da313dc
console.log(rawY.toString('hex')); // e.g. b4d27ecb6f89923ae539faa7c09b4797
This can be checked in an ASN.1 parser, e.g. https://lapo.it/asn1js/.
Note that this curve should not be used because of its length according to NIST.
Another convenient option is the export as JWK, because here x and y can be extracted directly (Base64url encoded). However, only the curves supported by JWK can be exported this way, secp128r1 is not one of them.

No inverse error during encryption (node-rsa)

I am trying to convert https://clover.app.box.com/s/rz18bni3bpmdrc8wc92xm4w8h9grrtty from Java to node.js
I am using the node-rsa package and getting error:0306E06C:bignum routines:BN_mod_inverse:no inverse when trying to encrypt using an imported public key.
According to the clover example, I want to
Parse the Base64 public key string (returned by the CDN). Obtain the modulus and exponent.
Generate an RSA public key using the modulus and exponent values.
Prepend the prefix value to the card number.
Using the public key, encrypt the combined prefix and card number.
Base64 encode the resulting encrypted data into a string.
I was able to get the modulus and exponent, and it matches the result in Java:
modulus: 24130287975021244042702223805688721202041521798556826651085672609155097623636349771918006235309701436638877260677191655500886975872679820355397440672922966114867081224266610192869324297514124544273216940817802300465149818663693299511097403105193694390420041695022375597863889602539348837984499566822859405785094021038882988619692110445776031330831112388738257159574572412058904373392173474311363017975036752132291687352767767085957596076340458420658040735725435536120045045686926201660857778184633632435163609220055250478625974096455522280609375267155256216043291335838965519403970406995613301546002859220118001163241
exponent: 415029
Now I want to create a public key with it, and encrypt a message:
const key = new NodeRSA();
// generate RSA public key using mod and exp values
key.importKey({
n: Buffer.from('24130287975021244042702223805688721202041521798556826651085672609155097623636349771918006235309701436638877260677191655500886975872679820355397440672922966114867081224266610192869324297514124544273216940817802300465149818663693299511097403105193694390420041695022375597863889602539348837984499566822859405785094021038882988619692110445776031330831112388738257159574572412058904373392173474311363017975036752132291687352767767085957596076340458420658040735725435536120045045686926201660857778184633632435163609220055250478625974096455522280609375267155256216043291335838965519403970406995613301546002859220118001163241', 'hex'),
e: Buffer.from('415029', 'hex')
}, 'components-public');
// using public key, encrypt message
// base64 encode encrypted data to string
const encryptedString = key.encrypt(Buffer.from('message', 'hex'), 'base64', 'hex');
However, I am getting the no inverse error mentioned above. It seems to be caused by the modulus. If I change it to something shorter, the error goes away.

get »fingerprint« from rsa key

By using the crypto module, it is easy to create a private/public key Pair in node. But how can I compute the »fingerprint« of a key?
OpenCrypto has something like that:
crypt.getFingerprint(key, options).then(function (fingerprint) {
console.log(fingerprint)
})
What is the equivalent of nodes crypto module for that?
OpenCrypto.getFingerprint exports a public RSA key in the X509 DER format and creates a hash for these data. The same applies to a private RSA key with the difference that the private key is exported in the Pkcs8 DER format. The digest can be specified in the options (default: SHA 512) and also whether the data are returned as buffer or as hexadecimal string (default: hexadecimal string).
In the NodeJS code, keys can be generated with crypto.generateKeyPair, whereby the key format can be specified explicitly. If the keys aren't already in the appropriate formats (X509 DER and Pkcs8 DER) key conversions can be performed to produce the same fingerprints that OpenCrypto.getFingerprint creates. Suitable functions for these operations are crypto.createPublicKey or crypto.createPrivateKey. Finally the hash has to be generated with crypto.createHash.
Update:
The fingerprint is nothing else than a hash value, e.g. with the digest SHA-512 (as in OpenCrypto.getFingerprint):
var fingerprint = crypto.createHash('sha512').update(key).digest('hex'); // Fingerprint (hash) as hexadecimal string
where key is a public or private key in any format (string, Buffer, ...). If the key is given as X509 DER (public) or as PKCS8 DER (private), then the fingerprint matches that of OpenCrypto.getFingerprint.
If the key is in another format, the fingerprint can also be determined in this way. However, if the fingerprint should match the value provided by OpenCrypto.getFingerprint, the key must of course be converted into the formats used by OpenCrypto.getFingerprint before the hash is generated. This conversion isn't very complex, e.g. the conversion of a public PKCS1 PEM key (publicKey) into a X509 DER key (publicKeyDER) including the generation of the hash:
var publicKeyDER = crypto.createPublicKey(publicKey, { type: 'pkcs1', format: 'pem' }).export({ type: 'spki', format: 'der' }); // Convert a public PKCS1 PEM key into a X509 DER key
var fingerprint = crypto.createHash('sha512').update(publicKeyDER).digest('hex'); // Fingerprint (hash) as hexadecimal string
The bottom line is that these are functionally the same operations that are performed in OpenCrypto.getFingerprint. To my knowledge, there is no OpenCrypto.getFingerprint counterpart in the NodeJS crypto module. But with only little effort you can write your own function using the above crypto functions.

In the Substrate framework, how do you get the private key (secret key) from a Keypair?

I have this code:
use schnorrkel;
use schnorrkel::{
derive::{ChainCode, Derivation, CHAIN_CODE_LENGTH},
signing_context, Keypair, MiniSecretKey, PublicKey, SecretKey,
};
use substrate_primitives::crypto::Pair as PairT;
use substrate_primitives::sr25519;
use substrate_primitives::sr25519::Pair;
fn main() {
let keypair = sr25519::Pair::generate();
let private_key = keypair.somehow_get_the_private_key_func();
}
I tried to use the SecretKey trait (imported at the beginning), but it says SecretKey is private, so the only way I am thinking of is to modify Substrate's sources to add a function that gives me the private key, but I don't want to do it by source code alteration.
What would be the way to go?
What do you actually need to do? You shouldn't be getting a private key in the runtime at all.
If you want to sign something in your runtime, there are helper functions that will sign a message, if you just pass in the public key, with its corresponding private key.
https://substrate.dev/rustdocs/master/sp_io/crypto/fn.sr25519_sign.html
testing: https://substrate.dev/rustdocs/master/sp_core/testing/struct.KeyStore.html

Jose4j: Unable to find a suitable verification key for JWS w/ header

The verification fails because key_ops does not meet the criteria of the SimpleJwkFilter created from static method filterForInboundSigned(JsonWebSignature jws) in SelectorSupport. The public key looks something like this:
{
"kid": "xxx",
"use": "sig",
"key_ops": [
"sign"
],
"kty": "xxx",
"e": "xxx",
"n": "xxx"
}
According to the SimpleJwkFilter "key_ops" either has to be null or contain the value "verify" to match the criteria.
Is there some way to customize this behaviour in jose4j? Maybe skip validation of "key_ops"?
If you're using HttpsJwksVerificationKeyResolver, you could have simple little subclass of HttpsJwks which unsets the "key_ops" on each JWK before the filter sees them. That'd look something like this:
class MyHttpsJwks extends HttpsJwks
{
public MyHttpsJwks(String location)
{
super(location);
}
#Override
public List<JsonWebKey> getJsonWebKeys() throws JoseException, IOException
{
List<JsonWebKey> jsonWebKeys = super.getJsonWebKeys();
for (JsonWebKey jwk : jsonWebKeys)
{
jwk.setKeyOps(null);
}
return jsonWebKeys;
}
}
And then instantiate the resolver like new HttpsJwksVerificationKeyResolver(new MyHttpsJwks("https://bad.example.com/jwks"));
If you're using JwksVerificationKeyResolver, you can just do the same kind thing to the JsonWebKey list before instantiating the resolver with it. Similar preprocessing on the list will also work, if you are using VerificationJwkSelector or the SimpleJwkFilter directly.
FWIW, according to RFC7517 the "use" and "key_ops" parameters shouldn't be used together and if they are, they are supposed to convey the same meaning. I would argue that the JWK in question isn't honoring that because the "key_ops" of "sign" says the key can be used to compute a digital signature while a "use" of "sig" says that the key can be used for digital signature operations in general (sign or verify).

Resources