Imported a public RSA key into nodejs - node.js

I am trying to transfer a public RSA key generated in swift into my Nodejs server. I generated the RSA key using the following code.
private var clientPriv: SecKey?
private var clientPub: SecKey?
private init(){
let params: [String: Any] = [
String(kSecAttrKeyType): kSecAttrKeyTypeRSA,
String(kSecAttrKeySizeInBits): 4096
]
SecKeyGeneratePair(params as CFDictionary, &clientPub, &clientPriv)
}
I send the key to my server using this code
...
guard let clientPub = clientPub else { return }
let key = SecKeyCopyExternalRepresentation(clientPub, nil)! as Data
let pem = exportToPEM(data: key, withLabel: "PUBLIC KEY")
let data = ["clientPub": pem]
var urlRequest = URLRequest(url: url)
do {
try urlRequest.httpBody = JSONSerialization.data(withJSONObject: data)
urlRequest.httpMethod = "POST"
urlRequest.setValue("application/json", forHTTPHeaderField: "Content-Type")
}catch let err {
print(err)
}
let task = URLSession.shared.dataTask(with: urlRequest){ data, response, error in
guard let data = data, error == nil else {
return
}
...
The exportToPem helper looks like this.
public func exportToPEM(data: Data, withLabel label: String) -> String {
let key = data.base64EncodedString(options: [.lineLength64Characters])
var pem = "-----BEGIN \(label)-----\n"
pem += key
pem += "\n-----END \(label)-----\n"
return pem
}
On my Nodejs side, I am using express to handle my requests and body-parser to parse my json post data in requests. Here is what my Nodejs receiving code looks like.
app.post('/api/init', jsonParser, function (req, res) {
console.log(req.body.clientPub);
CLIENTPUB = crypto.createPublicKey({ key: req.body.clientPub, format: 'pem', type: 'pkcs1' });
console.log(CLIENTPUB);
res.write(JSON.stringify({'server-pub': SERVERPUB.toString()}));
res.end()
});
The problem is that the function crypto.createPublicKey keeps throwing an error, error:0D0680A8:asn1 encoding routines:asn1_check_tlen:wrong tag. I have tried many different ways to write the string of my key but no matter what it seems that the crypto createPublicKey just refuses to take it. I have tried keeping the format with \n every 64 bytes or without \n at all, removing the header/footer altogether, and many other different combinations. I can not figure out why it keeps refusing to accept any format I send it. I have also tried using just the der format but that also gets refused.
Can anyone please offer me any advice on how to get this function to accept my key format?

SecKeyCopyExternalRepresentation() exports the public key in PKCS#1 format, which is correctly specified on the NodeJS side in the createPublicKey() call with 'pkcs1' for the type parameter.
However, the header and footer texts of a PEM encoded key for PKCS#1 format are BEGIN RSA PUBLIC KEY and END RSA PUBLIC KEY, so when calling exportToPEM(), "RSA PUBLIC KEY" must be passed in the second parameter instead of "PUBLIC KEY":
let pem = exportToPEM(data: key, withLabel: "RSA PUBLIC KEY")
BEGIN PUBLIC KEY and END PUBLIC KEY are used for a PEM encoded public key in X.509/SPKI format. This is what the error message wrong tag means.

Related

core.js:478 Uncaught Error: Malformed UTF-8 Data When Decrypting Url Query Param in Angular

I have a crypto encrypt and decrypt service below and I use it throughout my application to encrypt and decrypt localstorage items. While it works perfectly fine for the localstorage items, when I try to decrypt an encrypted object sent through queryparams, I get the following error:
core.js:478 Uncaught Error: Malformed UTF-8 data
my encrypt/decrypt service is:
export class AESEncryptDecryptService {
secretKey = 'My secret string';
constructor() { }
encrypt(value? : string) : string{
if(!isNil(value)) {
return CryptoJS.AES.encrypt(value, this.secretKey.trim()).toString();
}
}
decrypt(textToDecrypt?){
if(!isNil(textToDecrypt)) {
return CryptoJS.AES.decrypt(textToDecrypt, this.secretKey.trim()).toString(CryptoJS.enc.Utf8);
}
}
}
How I encrypt the object before I sent it:
const user= new User();
this.qrUrl = `${environment.someurl}` +'currentUser=' +this._AESEncryptDecryptService.encrypt(JSON.stringify(user).toString()).toString();
How I decrypt the object:
const url_string = window.location.href;
const url = new URL(url_string);
if(url.searchParams.get('user')) {
this.qrDevice = JSON.parse(this._AESEncryptDecryptService.decrypt(url.searchParams.get('user')));
}
I tried it without the string and by debugging. The same code works fine for other uses but gives this error on url query decrypt.
One thing I noticed is that the query string is replacing the + in the string with a space. How can I fix this and preserve the + sign? the expected and actual of the encrypted objects are posted below.
Expected after object parse from url:
U2FsdGVkX1+8y4FZ0cDq5ikapUndRA+tE5BAVqYPH9NnhBWeea1asYo5zCU80s/6FWKnFU8FghXv7JxPWwnPpJtCR+eXIGpiGBWq4gpq00PoeIuU2jPsDeifSu8aDrFr+D8abcdkIil5WmsHiND5TwVfWHhaBDSSlYMSXbiUXx9DQgRipEAtXXgMEO/r7G5wpuJ9ekEzUfkgXIO3eM/tP6dMu2iWZwbXTDvBZl93J8XZ259YRtIkRXgolSGS2t9yvQOn9I7fobRI1NSCIAftQtGdj/k9pu4B9reicnw9wiNR4dmp8+cpI/3TQSevhwp
Actual after object parse from url:
U2FsdGVkX1 8y4FZ0cDq5ikapUndRA tE5BAVqYPH9NnhBWeea1asYo5zCU80s/6FWKnFU8FghXv7JxPWwnPpJtCR eXIGpiGBWq4gpq00PoeIuU2jPsDeifSu8aDrFr D8abcdkIil5WmsHiND5TwVfWHhaBDSSlYMSXbiUXx9DQgRipEAtXXgMEO/r7G5wpuJ9ekEzUfkgXIO3eM/tP6dMu2iWZwbXTDvBZl93J8XZ259YRtIkRXgolSGS2t9yvQOn9I7fobRI1NSCIAftQtGdj/k9pu4B9reicnw9wiNR4dmp8 cpI/3TQSevhwp
Issue was resolved by changing the qrurl to the following:
this.qrUrl = `${environment.qrOrderURL}user=`+encodeURIComponent(this._AESEncryptDecryptService.encrypt(JSON.stringify(user)));

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.

Nodejs construct the public key using public key string

I have a public key string as follows
let pk_str = "public key strig here" and I am using the library jose to verify a JWS
(async() => {
const decoder = new TextDecoder();
const jws = vc_proof_value;
const { payload, protectedHeader } = await compactVerify(jws, pk_str);
console.log(protectedHeader)
console.log(decoder.decode(payload))
})();
I am getting the following error when trying to run the script
(node:75986) UnhandledPromiseRejectionWarning: TypeError: Key must be one of type KeyObject, CryptoKey, or Uint8Array. Received type string
Is there a way to construct the key ?
In NodeJS (I refer to NodeJS, since you have tagged this), the public key is passed as KeyObject wich is created with crypto.createPublicKey(). You didn't say much about the key, presumably it's PEM encoded (since it's a string). In this case, you simply have to pass the PEM encoded key:
var key = crypto.createPublicKey(pk_str);
If in the compactVerify() call pk_str is replaced by key, verification works.
In addition to PEM keys (default), JWK and DER keys (X.509/SPKI or PKCS#1) are also supported.
Here is the documentation for the key argument of all applicable functions.

How do I verify a key pair matches? (node-forge)

I need to make sure a client generated RSA key pair matches before signing it. I can't seem to find any documentation (npm:node-forge) on how to do so. I'm guessing I could sign something with it, and then verify the signature, but that's not efficient. I currently have this:
const Forge = require("node-forge");
try {
publicKey = Forge.pki.publicKeyFromPem(publicKey);
privateKey = Forge.pki.privateKeyFromPem(privateKey);
} catch(err) {
// ...
}
// ...
Any ideas are appreciated.
I've found my answer: I don't need to be sent the public key in the first place. You can build the public key from the private key like this:
// const privateKey = ...;
const publicKey = Forge.pki.setRsaPublicKey(privateKey.n, privateKey.e);
More information on this solution can be found here: Extract public key from private key pem using only nodejs/javascript.

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.

Resources