How to encrypt data and private key for Fastspring transactions? - javascript

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.

Related

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.

AES encryption in JS, decrypt in PHP?

When my form gets submitted, it will first make a request to this controller action to get the server's public key:
public function preprocessPayment(Request $request) {
// Get public key
$publicKey = $this->EncryptionService->getPublicKey();
// Generate iv
$method = 'aes-256-cbc';
$ivlen = openssl_cipher_iv_length($method);
$iv = openssl_random_pseudo_bytes($ivlen);
return response()->json([
'success' => true,
'data' => [
'public_key' => $publicKey,
'iv' => $iv
]
]);
}
After that, in my client, I'm going to generate a secret key using AES via CryptoJS, that will later be encrypted with the public_key.
Then, the form data will be encrypted in AES using the AES secret key, and then the following payload will be submitted to the server:
{
secret_key: xxx,
iv: xxx,
form_data: {...}
}
The AES encrypted data will be processed here:
public function storePayment(Request $request) {
// Decrypt AES secret key (that was encrypted with the RSA public key),
// using RSA private key
// Decrypt AES client data with secret key
// Store data in database
}
My question is, how will I do the AES secret key generation and encryption on the client side using CryptoJS? Could not seem to find any good documentation about it. How should I format the data so it will be accepted by the server for decryption?
And I'm stuck with decrypting AES in PHP, because it requires a $tag and I don't know where to get that when everything is coming from the client.
$originalData = openssl_decrypt($data, 'aes-128-gcm', $secretKey, $options=0, $iv, $tag);
I found this link: http://cryptojs.altervista.org/js-php/, but I'm not sure how to make it work because I'm not sure where to locate the needed scripts.
Edit:
I made a mistake, for decrypting on the server, I was using aes-128-gcm instead of aes-256-cbc. When I corrected it, I was able to decrypt without the $tag.
An AES-256 key is nothing more than 32 random bytes. So you create the key by using a cryptographically secure random number generator.
However, both RSA PKCS#1 v1.5 and AES-CBC are vulnerable to padding oracle attacks. So not only can an adversary change the message, the message is also not kept confidential. In other words, you can use 256 bit keys as much as you want, but you should not create your own transport protocol, because the perceived security just isn't there.
You could sign the ciphertext, but that has problems as well - generally we sign then encrypt.
Use TLS.

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!

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