Create PEM keypair in javascript readable by PHP - javascript

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.

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.

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

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.

How do I specify the passphrase for a private key in saml2-js?

I see the option to do this in saml-passport but I've already set things up using saml2-js. My key/cert has a passphrase that is required or I get a bad decrypt error. Is it possible to set this passphrase?
Here are the SP options
var sp_options = {
entity_id: "/startpoint",
private_key: fs.readFileSync(`${dir}src/certs/key.pem`).toString(),
certificate: fs.readFileSync(`${dir}src/certs/cert.pem`).toString(),
assert_endpoint: '/assert',
sign_get_request: true
}
var sp = new saml2.ServiceProvider(sp_options)
I would expect to have a key 'passphrase' under the private_key key but there is no key like that specified in the docs.
It seems that your saml module doesn't handle passphrase for crt/pem.
You can search another saml module on npm which can handle it, i didn't find one for the moment
You can probably manage the decryption of the pem by hand with an openssl wrapper / crypto wrapper but i might be quite complicated.
Otherwise, John Cruz seems the best option for no-code solution.
If you want to do it by hand, your encrypted PEM includes:
-----BEGIN RSA PRIVATE KEY-----
Proc-Type: 4,ENCRYPTED
DEK-Info: DES-EDE3-CBC,05E1DB4ACD187787
Which show you encrypting algorithm used, here : des-ede3-cbc, and the cipher IV in hex format, here 05E1DB4ACD187787
Then you have your base64 encrypted datas:
2rtyxqlZg/ROAHQRnYyHDpkdk9rgYVhsNrGdBzEySzUG+LRwTU/Z+ihSTKK0f2yj
Zpn/qOsXwq4IS6XOb+Q8M5AAbE7t3jKI14YDAvDK/jQpBLk907oxFqeNte3Qvmrm
OjzHJS/P1JXef4dByhrjlrdL/pNV9ov5dM8cyVcxRUbW6cNapXoSrlXrmNPM....
With node crypto module you can now handle the decryption:
const cipher = crypto.createDecipheriv('DES-EDE3-CBC', Buffer.from(secretKeyHex, "hex"), Buffer.from(ivHex, "hex"));
let c = cipher.update(encryptedPemBase64, 'base64','base64')
c += cipher.final('base64');
Note that the key must have a specific length, depending on the algorithm used
It's quite easy if your key is "static", can be tricky if you have to handle a lot of algorithm
By poking around the code in that library, it doesn't look like it has the logic to handle password-protected certs.
One option is to remove the passphrase by doing this:
openssl rsa -in key.pem -out key_nopass.pem
You'll be prompted to enter your password one more time. The newly-created file, key_nopass.pem won't require a password.

How can I produce a SKI from RSA public Key in node js?

I am trying to modify fabric-sdk-node project to use it with RSA certs and keys.
And for now I have no idea on how to produce a SKI (X509v3 Subject Key Identifier) with a RSA public key.
That project is using jsrsasign package on the crypto things.
I have found in the jsrsasign issue page on Github.com which can produce SKI with ECDSA, but still no clue in the RSA ones.
Any advice will be welcomed!
Thanks!
I have almost test every method in package jsrsasign, but still not found anything useful.
So I look outside the box and think, the X509V3 Subject Key Identifier(SKI) was produced by the default sha1 hash algo. However, what we have done in the hyperledger/fabric, defined that the hash algo to be sha256. So what if the jsrsasign has made that hash algo hardcoded with sha1? (This is a guess, please correct me if I am wrong.)
With that thought, I have tried on another package node-forge, which just solved my problem.
Here is the demo:
const nodeForge = require("node-forge");
const pki = nodeForge.pki;
const jsrsa = require("jsrsasign");
const KEYUTIL = jsrsa.KEYUTIL;
const rsaPubKeyPem = `-----BEGIN CERTIFICATE-----
MIID<.........>Y/gRUg==
-----END CERTIFICATE-----
`;
const pubKey = KEYUTIL.getKey(rsaPubKeyPem);
const publicKey = pki.rsa.setPublicKey(pubKey.n,pubKey.e);
console.log(pki.getPublicKeyFingerprint(publicKey,{
md: nodeForge.sha256.create(),
encoding: 'hex',
delimiter: ':'}));
And we have got the exactly same SKI as the openssl did.
This will import another crypto package, and it looked ugly.
Please advise if you have any good ideas.
Thanks!

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