RSA encryption text/message using our PublicKey - JavaScript or ReactJS - javascript

What I have to do is encrypt a small message through a public key. I have the public key but I can't encrypt any messages.
I have tried with JSEncrypt, encrypt-rsa, hybrid-crypto-js, NodeRSA and crypto. CanĀ“t encrypt message.
The key is something like
let keyData = `-----BEGIN PUBLIC KEY-----MIIBCgKCAQEAzK1MF/...-----END PUBLIC KEY-----`
The closest to getting anything was through 'NodeRSA', which when doing key.importkey(keyData, 'pkcs8'), returns an error "InvalidAsn1Error: encoding too long"
Can someone give me a hand?
My code is:
import NodeRSA from 'node-rsa';
const key = new NodeRSA();
let keyData = `-----BEGIN PUBLIC KEY-----MIIBCgKCAQEAzK1MF/...-----END PUBLIC KEY-----`
key.importKey(keyData, 'pkcs8');
I stopped here because you gave me the mistake.

Related

How to encrypt data and private key for Fastspring transactions?

I'm trying to encrypt payload and secure key for fastspring transactions.
I follow their documentation https://community.fastspring.com/s/article/Passing-Sensitive-Data-with-Secure-Requests
This is my code for encryption of data
async function encrypt(payload) {
const aesKey = crypto.randomBytes(16);
const iv = new Buffer("");
const cipher = crypto.createCipheriv('aes-128-ecb', aesKey, iv);
var encryptedPayload = cipher.update(new Buffer(JSON.stringify(payload), 'utf8'), 'utf8', 'base64');
const securePayload = encryptedPayload + cipher.final('base64');
const secureKey = crypto.privateEncrypt('MIIEpQIBA.....', iv).toString('base64');
return {
securePayload: securePayload,
secureKey: secureKey
};
};
When I try to call function with payload
{
"contact": {
"email": "xxxxxx",
"firstName": "xxxxxx",
"lastName": "zxxxx"
},
"items": "cart"
}
I have error
TypeError: Cannot read properties of null (reading '2')
at t.exports (worker.js:14:163836)
at u (worker.js:8:103816)
at t.exports [as publicEncrypt] (worker.js:14:210455)
at e.privateEncrypt (worker.js:14:210199)
at worker.js:10:297620
at worker.js:10:300887
Problem is in privateEncrpyt function I'm passing directly private key as string..
I would appreciate if someone knows how to solve it or give me more direction?
I use cloudflare workers to build this.
Thank you
If you used the instruction given in the FastSpring documentation for generating the RSA private key:
openssl genrsa -out privatekey.pem 2048
the generated key is a PEM encoded private RSA key in PKCS#1 format. This can be imported in the first parameter of privateEncrypt() as follows:
var pkcs1pem = `-----BEGIN RSA PRIVATE KEY-----
MII...
-----END RSA PRIVATE KEY-----`;
...
const secureKey = crypto.privateEncrypt(pkcs1pem, aesKey).toString('base64');
For completeness: crypto supports for private RSA keys beside the PKCS#1 format also the PKCS#8 format and beside the PEM encoding also the DER encoding.
Note on the FastSpring encryption approach:
Encrypting the AES key with privateEncrypt() and the private key as specified by FastSpring is actually the wrong approach. privateEncrypt() is intended for creating a signature (as part of a low level signing process). The purpose of a signature is not to keep data secret, but to verify the authenticity of a digital message. From the signature (at least as it is created here) the original data, i.e. the AES key, can easily be reconstructed using the public key. Since in general anyone can come into possession of the public key, the AES key is thus compromised.
The basic problem of the FastSpring approach is that the actual public key must be kept secret, quote FastSpring: Create a 2048-bit RSA public key. Only share this key with FastSpring. The secrecy of the public key, however, is not guaranteed in practice (apart from the fact that it contradicts the concept of a public key). For instance, the public key could be intercepted during the transfer to FastSpring, or it could be accidentally leaked by FastSpring itself, etc.
The correct way is to encrypt the AES key with publicEncrypt() and the public key of FastSpring. Then it is ensured that only FastSpring can decrypt the encrypted AES key, because only FastSpring has the private key.

OpenPGPJS: Invalid session key for decryption

OpenPGP JS throws the following error when I attempt to decrypt some armored data with my private key:
Uncaught (in promise) Error: Error decrypting message: Invalid session key for decryption. at onError (openpgp.min.js:16057) onError # openpgp.min.js:16057
From what I can tell from google, this means something is going wrong with the encryption, but I can't tell what it is. What makes it worse is that it seems to be inconsistent with only certain files (seemingly encrypted around the same time?) failing in this way. The encrypted messages don't seem to be malformed in any way.
If anyone has any tips for debugging this it would be appreciated. What could throw this error? Excerpts of my code are below, based primarily on the openPGPJS example code.
For extra information about what my code is doing, image files are being encrypted on the client side, uploaded to a server, downloaded elsewhere, and then being decrypted.
function encryptData(data) {
var openpgp = window.openpgp;
var options, encrypted;
var pubkey = `-----BEGIN PGP PUBLIC KEY BLOCK-----...-----END PGP PUBLIC KEY BLOCK-----`;
options = {
data: data,
publicKeys: openpgp.key.readArmored(pubkey).keys
};
return openpgp.encrypt(options);
}
function decryptPGP(encData, doneFunc) {
var privkey = `-----BEGIN PGP PRIVATE KEY BLOCK-----...-----END PGP PRIVATE KEY BLOCK-----`;
var pubkey = `-----BEGIN PGP PUBLIC KEY BLOCK-----...-----END PGP PUBLIC KEY BLOCK-----`;
var passphrase = '...';
var privKeyObj = openpgp.key.readArmored(privkey).keys[0];
privKeyObj.decrypt(passphrase);
options = {
message: openpgp.message.readArmored(encData),
publicKeys: openpgp.key.readArmored(pubkey).keys,
privateKey: privKeyObj
};
openpgp.decrypt(options).then(function(plaintext) {
doneFunc(plaintext.data);
});
}
I have had the same issue. To resolve it, encode your result from encryption with base64. That base64 string can then be sent over the internet as you desire. When you want to decrypt, just decrypt base64 first and then
await openpgp.message.readArmored(Base64.decode(encData))
will work!

Create PEM keypair in javascript readable by PHP

Usecase: client creates RSA keypair in browser, sends pubkey to server and then signs his messages using secretkey kept in local storage.
I've looked over JWT but it does not have keypair generation in browser (or I couldn't find it). I have found a library that creates keys (https://github.com/juliangruber/keypair) but I get a pub key that is not readable in PHP from the openssl_pkey_get_public function.
I'm really stuck - I thought it was supposed to be pretty common but after a whole day googling I can't understand a thing. Lots of standards, formats...
Can anyone advise a library or method by which I can create keys at cient in JS and verify message, using pubkey in PHP?
Here is what I have so far:
I've created pubkey in JS, using library mentioned above
-----BEGIN RSA PUBLIC KEY-----
MIGJAoGBAM3CosR73CBNcJsLv5E90NsFt6qN1uziQ484gbOoule8leXHFbyIzPQRozgEpSpi
whr6d2/c0CfZHEJ3m5tV0klxfjfM7oqjRMURnH/rmBjcETQ7qzIISZQ/iptJ3p7Gi78X5ZMh
LNtDkUFU9WaGdiEb+SnC39wjErmJSfmGb7i1AgMBAAE=
-----END RSA PUBLIC KEY-----
Then I saved it to "/pubKey.pem" file and try to read it with PHP:
openssl_pkey_get_public(file_get_contents("/pubKey.pem"));
And it fails. I suppose format is not the one that expected?
openssl_pkey_get_public expects keys to be in the PKCS8 format. eg.
-----BEGIN PUBLIC KEY-----
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDNwqLEe9wgTXCbC7+RPdDbBbeq
jdbs4kOPOIGzqLpXvJXlxxW8iMz0EaM4BKUqYsIa+ndv3NAn2RxCd5ubVdJJcX43
zO6Ko0TFEZx/65gY3BE0O6syCEmUP4qbSd6exou/F+WTISzbQ5FBVPVmhnYhG/kp
wt/cIxK5iUn5hm+4tQIDAQAB
-----END PUBLIC KEY-----
I was able to convert it with phpseclib:
<?php
include('Crypt/RSA.php');
$key = '-----BEGIN RSA PUBLIC KEY-----
MIGJAoGBAM3CosR73CBNcJsLv5E90NsFt6qN1uziQ484gbOoule8leXHFbyIzPQRozgEpSpi
whr6d2/c0CfZHEJ3m5tV0klxfjfM7oqjRMURnH/rmBjcETQ7qzIISZQ/iptJ3p7Gi78X5ZMh
LNtDkUFU9WaGdiEb+SnC39wjErmJSfmGb7i1AgMBAAE=
-----END RSA PUBLIC KEY-----';
$rsa = new Crypt_RSA();
$rsa->loadKey($key);
$rsa->setPublicKeyFormat(CRYPT_RSA_PUBLIC_FORMAT_PKCS8);
$res = openssl_pkey_get_public("$rsa");
var_dump($res);
Maybe there are other non-CLI solutions for converting PKCS1 keys to PKCS8 but if so I'm unaware of it.

How to create a pem key in string format from base64 encoded key

I'm trying to create a pem key from a ecdsa private key, I have to use a pem string in another function to create a JWT but when I'm trying
var base64key = 'MDc4M2UwODJiNmFhMmVmOWIxZDdjN2YwMTYxMGM4M2ZkZTk2OTE3OWVlNzdlYzBmNTJmY2NiNjBhMDBjNTA0OQ==';
var privKey64 = '-----BEGIN PRIVATE KEY-----\n'+base64key+'\n-----END PRIVATE KEY-----';
var sJWS = KJUR.jws.JWS.sign(null, {alg: "ES256"}, privKey64);
So when I'm trying to feed this pem key to sign function I'm getting following error
*init failed:malformed plain PKCS8 private key
So I suspect that the pem key generated is not of valid format

Verifying JWT using jsonwebtoken in node.js with a token generated by jose4j fails

I'm trying to verify a json web token generated by jose4j using jsonwebtoken in node.js and I see the following error:
[Error: PEM_read_bio_PUBKEY failed]
The jose4j code is basically lifted straight from the example:
RsaJsonWebKey key = RsaJwkGenerator.generateJwk(2048);
key.setKeyId("global.authenticated");
byte[] raw = key.getKey().getEncoded();
Base64.Encoder encoder = Base64.getEncoder();
System.out.printf("Public Key [%s]\n", encoder.encodeToString(raw));
JwtClaims claims = new JwtClaims();
claims.setIssuer("global.gen");
claims.setAudience("global.cons");
claims.setExpirationTimeMinutesInTheFuture(12 * 60);
claims.setGeneratedJwtId();
claims.setIssuedAtToNow();
claims.setNotBeforeMinutesInThePast(2);
claims.setSubject("nim");
claims.setClaim("role", "tester");
JsonWebSignature jws = new JsonWebSignature();
jws.setPayload(claims.toJson());
jws.setKey(key.getPrivateKey());
jws.setKeyIdHeaderValue(key.getKeyId());
jws.setAlgorithmHeaderValue(AlgorithmIdentifiers.RSA_USING_SHA256);
String token = jws.getCompactSerialization();
System.out.printf("Generated Token [%s]\n", token);
JwtConsumer jwtConsumer = new JwtConsumerBuilder()
.setRequireExpirationTime() // the JWT must have an expiration time
.setAllowedClockSkewInSeconds(30) // allow some leeway in validating time based claims to account for clock skew
.setRequireSubject() // the JWT must have a subject claim
.setExpectedIssuer("global.gen") // whom the JWT needs to have been issued by
.setExpectedAudience("global.cons") // to whom the JWT is intended for
.setVerificationKey(key.getKey()) // verify the signature with the public key
.build(); // create the JwtConsumer instance
try {
// Validate the JWT and process it to the Claims
JwtClaims jwtClaims = jwtConsumer.processToClaims(token);
System.out.println("JWT validation succeeded! " + jwtClaims);
} catch (InvalidJwtException e) {
// InvalidJwtException will be thrown, if the JWT failed processing or validation in anyway.
// Hopefully with meaningful explanations(s) about what went wrong.
System.out.println("Invalid JWT! " + e);
}
So internally the token validates fine. However when I copy the token and the key (for example below is from a run), the above error is reported:
var jwt = require('jsonwebtoken'); // used to create, sign, and verify tokens
var key = 'MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAkRWAQ0O9LgBoHNAB5m1X8e1sPTzKmBTPCFTSRTzw0AjZozIbN4nIp/3jnQHTbcY0Bf5MDWmtdheSK1a+ew34YcgN2b9Shr+3yZv9PJ97i7gRCqOnI7jbm7PXFBNw1I4aMYc6tV7TKFzvx6008/nYvN3Jey6Z8ItS/FLQRDkV9m/WQkhJpYgvmD6qiwj9d+un+moBQ5/PPgn7Qkg5GyxZUy9PsblUDSrIA0bEiv/wQOXCYUvL9OFzxTUSeIHpdGibhPQVxX3Jnpr293Iq/mOKn3ZO+xBID26m3L8+ik64wte041y1S4HHaE9Q082ai/uBduAwIHcJY5VAHborZYCSaQIDAQAB';
var token = 'eyJraWQiOiJnbG9iYWwuYXV0aGVudGljYXRlZCIsImFsZyI6IlJTMjU2In0.eyJpc3MiOiJnbG9iYWwuZ2VuIiwiYXVkIjoiZ2xvYmFsLmNvbnMiLCJleHAiOjE0NDMwNjMyMDgsImp0aSI6InpweF9ERW8tX1h2Q1hnZmNZTUpiZ0EiLCJpYXQiOjE0NDMwMjAwMDgsIm5iZiI6MTQ0MzAxOTg4OCwic3ViIjoibmltIiwicm9sZSI6InRlc3RlciJ9.inEebSQ8jYPQsTpHnvw-gMpoNbJl5ErUkS8FtkDagWrwijUgG8XYYP8FLi2ZCpdgDqUsP6nE1iG0_2wWuL7B7C7wUpZlrqR2bEOG2cXK9s26VqNAXu8I7BTDaZBKmdOt1aFVWozGsN8iUCsQ7Yt9-GfvNRP1yeOoMgpOxf_wVa0QVzsV18aVi_oSeiMqOkQ_6n7JOjFVdiURm0ew4vh5TBaMcEcS35a9jtPxuFR_Z_FaLUk0g06PDVKcdsK1-FYRAGBlRGDkea8Hs9Zh-ZIxgcs2QfWzq5PSsIKum1dWqNLW04ullWmlbAO-5d0V0NAnkh4FFoi3N7AedvkILJgbqA';
jwt.verify(token, key, { algorithms: ['RS256'] }, function(err, decoded) {
if (err)
console.log(err);
else
console.log(decoded);
});
Is there some bit of magic that I'm missing (either in jose4j or jsonwebtoken) that will allow the generated token to be validated by the public key?
On a side note, pasting the token into jwt.io decodes the header and payload correctly, however the signature fails to be verified using the same public key. I'm guessing the problem really is in the jose4j side - but not sure.
I'll add this as an answer incase someone hits something similiar. The problem stems from the format of the key passed into jsonwebtoken. It can't just be a plain text public key, it has to follow the PEM format specifically. So once I convert the key to a PEM file, specficially by making the following changes:
byte[] raw = key.getKey().getEncoded();
Base64.Encoder encoder = Base64.getMimeEncoder(64, new byte[]{'\n'});
And then wrapping with
-----BEGIN PUBLIC KEY-----
<Key>
-----END PUBLIC KEY-----
Then passing the resulting file into jsonwebtoken allows the authentication to proceed. I didn't realize that the certificate passed into the validation step should be formatted so strictly (i.e. with the line sizes and the wrappers!)
NOTE: the bouncycastle package has a PemObject class and a PemWriter which should make writing the file somewhat easier - however I didn't want to pull in another package just for that. Maybe the maintainers of jose4j can add a little class for that to their package..
It seems as if jsonwebtoken is very strict not only about the -----BEGIN PUBLIC KEY----- header and footer, but also about the line breaks.
A key like
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAkRWAQ0O9LgBoHNAB5m1X8e1sPTzKmBTPCFTSRTzw0AjZozIbN4nIp/3jnQHTbcY0Bf5MDWmtdheSK1a+ew34YcgN2b9Shr+3yZv9PJ97i7gRCqOnI7jbm7PXFBNw1I4aMYc6tV7TKFzvx6008/nYvN3Jey6Z8ItS/FLQRDkV9m/WQkhJpYgvmD6qiwj9d+un+moBQ5/PPgn7Qkg5GyxZUy9PsblUDSrIA0bEiv/wQOXCYUvL9OFzxTUSeIHpdGibhPQVxX3Jnpr293Iq/mOKn3ZO+xBID26m3L8+ik64wte041y1S4HHaE9Q082ai/uBduAwIHcJY5VAHborZYCSaQIDAQAB
has to look like this for it to work with jsonwebtoken (mind the header, footer and line breaks):
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAkRWAQ0O9LgBoHNAB5m1X
8e1sPTzKmBTPCFTSRTzw0AjZozIbN4nIp/3jnQHTbcY0Bf5MDWmtdheSK1a+ew34
YcgN2b9Shr+3yZv9PJ97i7gRCqOnI7jbm7PXFBNw1I4aMYc6tV7TKFzvx6008/nY
vN3Jey6Z8ItS/FLQRDkV9m/WQkhJpYgvmD6qiwj9d+un+moBQ5/PPgn7Qkg5GyxZ
Uy9PsblUDSrIA0bEiv/wQOXCYUvL9OFzxTUSeIHpdGibhPQVxX3Jnpr293Iq/mOK
n3ZO+xBID26m3L8+ik64wte041y1S4HHaE9Q082ai/uBduAwIHcJY5VAHborZYCS
aQIDAQAB
-----END PUBLIC KEY-----
This function does the job for me:
function base64toPem(base64)
{
for(var result="", lines=0;result.length-lines < base64.length;lines++) {
result+=base64.substr(result.length-lines,64)+"\n"
}
return "-----BEGIN PUBLIC KEY-----\n" + result + "-----END PUBLIC KEY-----";
}

Categories