AES encryption in JS, decrypt in PHP? - javascript

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.

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.

Is there a way to generate RSA key pair client side with javascript?

I used Cryptico library and it is working fine, but the public key from Cryptico is not compatible with OpenSSL (meaning I can not use it to encrypt data with PHP as an example). I am asking how can I generate a key pair client side with the public key being compatible with OpenSSL. The goal is being able to encrypt data with the public key on IOS, Android or PHP and decrypt it on Javascript (meaning it is compatible cross platforms).
You may use jsbn library in the link below:
http://www-cs-students.stanford.edu/~tjw/jsbn/
and you may see the demo here:
http://www-cs-students.stanford.edu/~tjw/jsbn/rsa2.html
This is the most popular library you may find and you have the chance to customize it based on your requirement.
Also, you have another option with jsencrypt which is available here:
https://github.com/travist/jsencrypt
It is also compatible with openssl.
// Encrypt with the public key...
var encrypt = new JSEncrypt();
encrypt.setPublicKey($('#pubkey').val());
var encrypted = encrypt.encrypt($('#input').val());
// Decrypt with the private key...
var decrypt = new JSEncrypt();
decrypt.setPrivateKey($('#privkey').val());
var uncrypted = decrypt.decrypt(encrypted);
// Now a simple check to see if the round-trip worked.
if (uncrypted == $('#input').val()) {
alert('It works!!!');
}
else {
alert('Something went wrong....');
}

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 to encrypt data in browser with JavaScript and decrypt on server side with Node.js

I'm trying to encrypt a message using AES256 on the browser, send it to the server, and then decrypt it but I keep getting this error server-side :
error:06065064:digital envelope routines:EVP_DecryptFinal_ex:bad decrypt
I tried for hours to find a solution but I don't see where is the issue. I'm using crypto-js for client-side and the standard library for Node.js crypto
This is the code sample that I'm using on client-side.
import * as CryptoJS from 'crypto-js';
const secret = 'example';
const passphrase = 'secret-passphrase'
const encrypted = CryptoJS.AES.encrypt(secret, passphrase);
const encrypted64 = CryptoJS.enc.Base64.stringify(encrypted.ciphertext);
// make a request to a Node.js API with the password encrypted
This is the code sample that I'm using on server-side.
const crypto = require('crypto');
const secret = req.body.secret;
const passphrase = 'secret-passphrase'
const decipher = crypto.createDecipher('aes256', passphrase);
let decrypted = decipher.update(secret, 'base64', 'utf8');
decrypted += decipher.final('utf8');
Any idea?
Thanks.
Using HTTPS will encrypt the payload during the transfer and have it decrypted at the server. HTTPS uses RSA to encrypt the keys used in the encryption of the message.
RSA uses 1024 bit key value which is difficult to crack. The hacker has to factor large numbers into their original prime values to get the keys which makes its almost impossible to crack.
It is generally advisable to use HTTPS in your transmissions.
The error generally occurs when you use the wrong key.
You are using different packages to begin with.
crypto-js will take your passphrase, create a key and encrypt cleartext that you have in secret.
crypto will do the same when you create Decipher, but then there's the issue of padding. In short, key is not same for crypto when decrypting.
Use a 32 bytes key like abcabcabc1abcabcabc1abcabcabc132
Go for client-side encryption like:
var ciphertext= C.AES.encrypt(secret, C.enc.Hex.parse(passphrase), { mode: C.mode.ECB, padding: C.pad.NoPadding }).ciphertext.toString();
And on your server side code go for following (after passing the same passphrase to decipher):
let decrypted = decipher.update(ciphertext, 'hex', 'utf8');
Give it a try. It should work. It will be easier if you use crypto-js on both client and server.
Hope it helps!

PHP Mcrypt and HTML5 Crypto API encryption/decryption

I need to exchange a series of keys for an external platform through a webpage, may happens that user has not a SSL cert configured for use HTTPS so for do it in a secure way before starting the transfer I send a confirmation code to the user mail box, with this code I generate a SHA-256 hash (done with PHP and HTML5 Crypto API) and I use this as key for AES-256 (CBC) encryption of the string with keys, after that I convert it in base64 and send to the client that will convert it into file and triggers download. The problem is that I can't convert the components (the IV is connected to the encrypted string) into the format requested by JavaScript. Here the decryption code:
string = window.atob(string);
promise_key = window.crypto.subtle.generateKey({name: "AES-CBC", length: 256}, false, ["decrypt"]);
promise_key.then(function(key){
key_object = key;
var vector = string.substr(0, 16);
string = string.substr(16);
decrypt_promise = window.crypto.subtle.decrypt({name: "AES-CBC", iv: vector, length: 256}, key_object, string);
decrypt_promise.then(function(result){
decrypted_data = new Uint8Array(result);
console.log(decrypted_data);
});
});
Process:
Client sends an AJAX request to the server for init the key generation;
Server send a confirmation numeric code to user mail box and an ID to AJAX;
User receives the e-mail message, writes code into web page and submits it;
Client sends an AJAX request for the step 2 with the reveived ID;
Server checks ID, generates and encrypts keys and sends them to the client;
Client prepares the decryption key hashing the e-mail code with SHA-256 (also the server done this for encryption);
Client decrypts keys, build a JSON string and trigger the download as file.

Categories