Decryption Issue with Node Package "node-rsa" - javascript

I am attempting to implement simple public key cryptography with this library's RSA functions, but decryption seems to be broken.
I have two "users", Alice and Bob. Both Alice and Bob (code in separate files) create a new empty key via const key = new nodeRSA(). Then, they both generate a 2048 bit public and private key pair via the function key.generateKeyPair(2048). They both then give each other their public keys by exporting them from the key with key.exportKey('pkcs8-public-pem') and storing them into separate files and reading them in with fs. Alice then attempts to write a message to bob by passing both the string message and bob's public key into the function below
module.exports.writeMessage = (message, key) => {
const k = new rsa(key, 'pkcs8-public-pem')
const cipherText = k.encrypt(message, 'hex');
console.log('Saving "${cipherText}" to ctext.txt');
fs.writeFileSync('ctext.txt', cipherText);
};
Then, when bob goes to read the message, he passes in his full key and decodes the message from ctext.txt as shown in the function below
module.exports.readMessage = key => {
const encryptedMessage = fs.readFileSync('ctext.txt');
const message = key.decrypt(encryptedMessage, 'utf8');
return message;
};
Encryption works just fine, and Alice is able to send the ciphertext to ctext. The problem comes when bob calls the readMessage function and attempts to decipher the text. Both the Alice and Bob programs were activated and their keys remained unchanged throughout this process. The below error occurs on deciphering:
Error: Error during decryption (probably incorrect key). Original error: Error: Incorrect data or key
at NodeRSA.module.exports.NodeRSA.$$decryptKey (/Users/jisacf1/College/SeniorYear/Spring2019/CompSec/HW3/node_modules/node-rsa/src/NodeRSA.js:301:19)
at NodeRSA.module.exports.NodeRSA.decrypt (/Users/jisacf1/College/SeniorYear/Spring2019/CompSec/HW3/node_modules/node-rsa/src/NodeRSA.js:249:21)
at Object.module.exports.readMessage.key [as readMessage] (/Users/jisacf1/College/SeniorYear/Spring2019/CompSec/HW3/Part2/rsaReadWrite.js:7:25)
at inquirer.prompt.then (/Users/jisacf1/College/SeniorYear/Spring2019/CompSec/HW3/Part2/bob.js:42:43)
at processTicksAndRejections (internal/process/next_tick.js:81:5)
I really cannot see how the system thinks it is the incorrect key, since Alice encrypted the message using Bob's public key, and Bob is decoding the message using is private key. I've tried changing padding schemes to no avail as well. Any help would be appreciated greatly. For reference, the library's github is here: https://github.com/rzcoder/node-rsa

As mentioned by Maarten, the issue was that writeFileSync was encoding my cipher text in utf8 rather than the format the cipher text was in. This resulted in reading back incorrect cipher text, causing the key or data mismatch exception. Changing the default encoding for the function to hex solved the issue.

Related

How to verify a JWT signature using Node-jose

I am trying to use node-jose to verify signatures of my JWTs. I know the secret, but am having trouble converting this secret into a JWK used for the verification.
Here is an example of how I am trying to create my key with my secret and verify my token. This results in Error: no key found.
let token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzZXJpYWxfbnVtYmVyIjoiNWYxMGExNjMtMjk2OC00ZDZkLWIyZDgtOGQxNjQwMDNlMmQ0Iiwic2VxIjo1MTI4MTYsIm5hbWUiOiJOYW1lMSIsImlkIjo2NTQsImRlc2NyaXB0aW9uIjoiVGVzdCBEZWNvZGluZyJ9.ahLaTEhdgonxb8rfLG6NjcIg6rqbGzcHkwwFtvb9KTE"
let secret = "SuperSecretKey"
let props = {
kid: "test-key",
alg: "HS256",
use: "sig",
k: secret,
kty: "oct"
}
let key;
jose.JWK.asKey(props).then(function(result) {key = result})
jose.JWS.createVerify(key).verify(token).then(function(result){console.log(result)})
Do I need to modify my token to include the kid header somewhere? Am I generating the key correctly from the known secret for this library?
You have three problems with your code.
due to the asynchronous nature of the promises, key gets a value when the promise is fulfilled (in the .then part), but that happens after the next line gets called.
Place a console.log(key) directly after the line jose.JWK.asKey(... and you see you get "undefined" as a result. So there is actually no key.
the k value in a JWK is treated as a Base64Url encoded octet. When you sign the token, you have to use the base64url decoded value of k, but not k directly.
the secret "SuperSecretKey" is too short for node.jose. For the HS256 algorithm, the secret has to be 256 bits long. node.jose seems to be quite strict, compared to other libs.
To solve the first problem, you can either nest the calls (which quickly becomes hard to read, or use the async/await syntax like shown below:
var jose = require('node-jose')
async function tokenVerifyer()
{
let token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzZXJpYWxfbnVtYmVyIjoiNWYxMGExNjMtMjk2OC00ZDZkLWIyZDgtOGQxNjQwMDNlMmQ0Iiwic2VxIjo1MTI4MTYsIm5hbWUiOiJOYW1lMSIsImlkIjo2NTQsImRlc2NyaXB0aW9uIjoiVGVzdCBEZWNvZGluZyJ9.KK9F14mwi8amhsPT7ppqp_yCYwwOGcHculKByNPlDB8"
let secret = "SuperSecretKeyThatIsLongEnough!!" // A 32 character long secret to get 256 bits.
let props = {
kid: "test-key",
alg: "HS256",
use: "sig",
k: "cynZGe3BenRNOV2AY__-hwxraC9CkBoBMUdaDHgj5bQ",
//k : jose.util.base64url.encode(secret), // alternatively use above secret
kty: "oct"
}
let key = await jose.JWK.asKey(props)
let result = await jose.JWS.createVerify(key).verify(token)
}
tokenVerifyer()
In the above example, k is a key generated on https://mkjwk.org/ and the token was created with that key on https://jwt.io (check 'secret base64 encoded'). Alternatively, you can use your own secret, but have to make sure it's long enough.
Do I need to modify my token to include the kid header somewhere?
The small example above works without putting the kid in the token. For any real applications, you would usually add the kid into the token header. Your keystore could have more keys or rotating keys and the kidhelps to select the correct one.

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!

Having trouble grasping how to securely sign JWT with Private Key

I'm looking at this example here which refers to the javascript functionality of JWT
I am trying to use javasrcipt to sign a piece of data. However, it says I have to use a Private RSA Key and it doesn't allow you to use a public key.
My goal was once a form is submitted via PHP, call this javascript function and encrypt the data.
Pardon my ignorance, but how can you use a private RSA key in javascript and keep it private at the same time?
It appears that you have to give it a private key somehow and wouldn't that private key be visible to a user using simple developer tools in the web browser?
function _genJWS() {
var sHead = '{"alg":"RS256"}';
var sPayload = '{"data":"HI","exp":1300819380}';
var sPemPrvKey = document.form1.pemprvkey1.value;
var jws = new KJUR.jws.JWS();
var sResult = null;
try {
sResult = jws.generateJWSByP1PrvKey(sHead, sPayload, sPemPrvKey);
document.form1.jwsgenerated1.value = sResult;
} catch (ex) {
alert("Error: " + ex);
}
}
What your are looking for is not JWS (signed), but JWE (encrypted).
If you want to send secured data to a server using JWE, you must :
get the public key of the server
encrypt your data using this public key and produce a JWE
send your JWE to the server.
As far as I know, there is no javascript library able to produce JWE (I may be wrong, but I found nothing).

Issues running crypto-js on Parse Cloud Code for iOS application

I'm using Parse as my backend for my iOS application and would like to encrypt all the data that's sent between Parse and my iOS device. As such, I'm using Parse Cloud Code in hopes of being able to perform server-side encryption & decryption to process all data it sends and receives.
Apparently Parse has a 'crypto' module by default, but since I've been unable to find any documentation for it, I've gone ahead and tried using crypto-js by copying the appropriate files for AES encryption + decryption into my Parse Cloud Code /cloud folder.
The issue I'm running into is that I'm not sure what class of object is being returned by crypto-js's AES encryption / decryption function. I *seem* to be getting back an NSDictionary object but have no idea what to do with it. I would have guessed that I would receive an NSString or NSData object, but I seem to have guessed wrong.
Please let me know what additional information I can provide or what incorrect assumptions I may have made.
I needed to encrypt / decrypt on the server side, here is my cloud code which is similar to nodeJS code:
var crypto = require('crypto');
var cryptoAlgorithm = "aes-128-cbc"; //or whatever you algorithm you want to choose see http://nodejs.org/api/crypto.html
var cryptoPassword = "theLongAndRandomPassphrase";
var cipher = crypto.createCipher(cryptoAlgorithm,cryptoPassword);
var decipher = crypto.createDecipher(cryptoAlgorithm,cryptoPassword);
exports.myCiphering = {
encrypt:function(text){
var encrypted = cipher.update(text,'utf8','hex')
encrypted += cipher.final('hex');
return encrypted;
},
decrypt: function(text){
var decrypted = decipher.update(text,'hex','utf8')
decrypted += decipher.final('utf8');
return decrypted;
}
};
If this snippet has been saved in "cloud/ciphering.js", you can then use the ciphering tool like this anywhere in cloud code:
var text = "encryptMe";
var ciphering = require("cloud/ciphering.js").myCiphering;
var encrypted = ciphering.encrypt(text);
var decrypted = ciphering.decrypted(encrypted);
if (decrypted == text){
//the "password" is correct
}
Since Parse uses SSL all data is sent encrypted, SSL is enough to secure the communications.
You may want to encrypt the data so that it is protected on the server but unless you really understand cryptographic security don't.
Plain or encrypted passwords should never be stored, store a properly salted and hashed version of the password.
If you feel your data is substantially valuable enough have a security domain expert design it. Getting security right is hard, one mistake will invalidate it all.

Categories