Cryto Failing to Decrypt a String - javascript

I am trying to decrypt a string but test keeps failing and I cannot seem to figure out the issue.
Here are the files involved:
Encryptor.js
import * as crypto from 'crypto'
const hashAlgorithm = 'sha256';
const encryptAlgorithm = 'aes192';
const textEncoding = 'hex';
const encryptionKey = "some secret key";
export const getHash = text => {
try {
const encryptedData = crypto.createHmac(hashAlgorithm, encryptionKey);
encryptedData.setEncoding(textEncoding)
.end(text, () => encryptedData.read())
return encryptedData;
} catch (error) {
console.error(error);
}
}
export const encrypt = text => {
if (!text)
throw Error('Text may not be blank');
try {
const cipher = crypto.createCipher(encryptAlgorithm, encryptionKey);
let cipherText = '';
cipher.on('readable', () => {
let data = cipher.read();
if (data)
cipherText += data.toString(textEncoding);
});
cipher.on('end', () => cipherText);
cipher.write(text);
cipher.end();
}
catch (error) {
return console.error(error);
}
}
export const decrypt = text => {
if (!text)
throw Error('Decrypt: Text may not be blank');
try {
const decipher = crypto.createDecipher(encryptAlgorithm, encryptionKey);
let decrypted = '';
decipher.on('readable', () => {
const data = decipher.read();
if (data)
return decrypted += data.toString('utf8');
});
decipher.on('end', () => decrypted);
decipher.write(text, textEncoding);
decipher.end();
}
catch (error) {
console.error(error);
}
}
Encryptor.test.js
import {getHash, encrypt, decrypt } from './Encryptor.js';
const dataString = "Important information must always be encypted"
it('Should create a hashed message FROM STRING', () => {
expect(getHash(dataString)).not.toEqual(dataString)
})
it('Should encrypt the message FROM STRING', () => {
expect(encrypt(dataString)).not.toEqual(dataString)
})
it('Should decrypt the message FROM ENCRYPTED STRING', () => {
expect(decrypt(encrypt(dataString))).toEqual(dataString)
})
As far as I understand, this code is fine. However, when I run the test, the last one fails with this trace:
If I pass a literal string to the decrypt function like so:
it('Should decrypt the message FROM ENCRYPTED STRING', () => {
expect(decrypt("encypted")).toEqual(dataString)
})
the returned results are undefined and I also get the error:0606506D:digital envelope routines:EVP_DecryptFinal_ex:wrong final block length error
I followed almost exactly the examples here with no success.

Related

Does is matter from which region I'm calling AWS KMS encrypt decrypt API?

Does it matter from where I am calling the API, for example, the region of my KMS is the US but currently I'm calling it from the EU. Does it somehow affect decryption, coz I'm encrypting data fine but decryption gives random output? Other than region I'm not sure if something else is causing the issue, please have a look and any answer is appreciated.
Here is my code for reference:
import { DecryptCommandInput, KMS } from "#aws-sdk/client-kms";
import util from 'util'
import { kmsConfig } from "./constants";
export const region = kmsConfig.region;
export const kms = new KMS({
region: region,
apiVersion: "2014-11-01",
credentials: {
accessKeyId: kmsConfig.accesKeyId,
secretAccessKey: kmsConfig.secretAccessKey,
},
// important for react-native
endpoint: {
hostname: "kms." + region +".amazonaws.com",
path: "",
protocol: "https",
}
});
export async function kmsEncryption(data) {
// a client can be shared by different commands.
try {
let encryptionParams = {
KeyId: kmsConfig.arn,
Plaintext: data,
};
let kmsEncrypt = util.promisify(kms.encrypt).bind(kms);
let encryptedData = await kmsEncrypt(encryptionParams);
//encryptedData contained 2 parts, CiphertextBlob and KeyId
console.log("Encrypted");
return encryptedData;
} catch (error) {
console.log("\nerror => \n", error);
}
}
export const kmsDecryption = async (encryptedData: any) => {
try {
let buff = Buffer.from(encryptedData.CiphertextBlob);
let encryptedBase64data = buff.toString("base64");
console.log("\nencryptedBase64data => \n", encryptedBase64data);
let decryptionParams:DecryptCommandInput = {
CiphertextBlob: encryptedData.CiphertextBlob,
};
let kmsDecrypt = util.promisify(kms.decrypt).bind(kms);
let decryptedData = await kmsDecrypt(decryptionParams);
// decryptedData contained 2 parts, Plaintext and KeyId
console.log("\ndecryptedData => \n", decryptedData);
console.log("\ndecryptedData.Plaintext => \n", decryptedData.Plaintext);
console.log("\ndecryptedData.KeyId => \n", decryptedData.KeyId);
let buff2 = Buffer.from(decryptedData.Plaintext, "base64");
let originalText = buff2.toString();
console.log("\noriginalText => \n", originalText);
return originalText;
} catch (error) {
console.log("\ndecrypt error => \n", error);
}
}
let encode = await kmsEncryption("helloword1234");
let decode = await kmsDecryption(encode);

How to pass parameter message as text in openpgp JS

What I'm trying to do:
get some Azure storage blobs from container DIDE and encrypt them with RSA 2048 and upload them in other container called encrypted-dide
These blobs are downloaded through a stream(here Microsoft did a good job https://learn.microsoft.com/en-us/azure/storage/blobs/storage-quickstart-blobs-nodejs#upload-blobs-to-a-container) and recomposed by func. streamToString(readableStream)
(I'm not using openpgp JS streams as I don't know if Microsoft streams are the same with NodeJs ones)
My code works as expected with unecrypted text and upload blobs in the supposedly encryped container encrypted-dide
I have followed the official documentation of openpgp js and some Internet resources.
The error I am getting is Error: Parameter [message] needs to be of type Message in openpgp JS
the publicKey is harcoded in the file keys.js and and is exported like this:
const publicKey = `-----BEGIN PGP PUBLIC KEY BLOCK-----
xsBNBGDgi3gBCADcZqIcczPDAx3+os5cCFVgaoT62Y+5rRvwPGPaPKKA1ajw
7NvoxS0RsJbqYAwNk0IEneoZvgSPpqkehGQCOBdsEhjcEgxVxaSlbbgPJkPh
avjTBDUhr6pIUc+MkSX7eh5LdkgWRSfzZdLXX2es5ycM5R1ZryzPTAenZh7D
l1g1x9TsyX+scI7gAtuyfbzAybYVqYMIvcHYZdIi8m6pGmxIqb0QW6sgO6nG
GyjVgxLDyMnHzYMInFRmKUV8XUUw9ECLZ6ioW4rthmpjoswh9vmP6vWI9OL/
Y7Zb3xY5XnIT6UFSpAHS5V/TNbEUD/EpoNtEI30bUl2X35UM277fUxetABEB
AAHNG0pvbiBTbWl0aCA8am9uQGV4YW1wbGUuY29tPsLAigQQAQgAHQUCYOCL
eAQLCQcIAxUICgQWAAIBAhkBAhsDAh4BACEJEGHAYnRSOf5GFiEExGMJvxnV
v1dXecI0YcBidFI5/kY5PAgAxL10QcUZIrxRXQIrqk04ZDhO4ehMirPqH/KT
L/MeHppHFqV06Fm4JDAOpGyh8lgleLwP4P9Lrod3AVSOKSX48u+UM/Bo4LtG
foAntS+tC9RjWlpR6PZ0aJA8SqHKLCnkaUvz7wv/M55fgGxeeQbhOLutNxN4
L8rCNhPo3UbWwoB+ifgQ9S4bv4kgyJxXYinyGYG0CD67YxQKxiAt58qjsdmK
x4uKCoFbHd1Oa4wfr6ezXet+2hCQvsf8eJV88+qL7TmpSe3ypiTWHNgxymNx
v77SlOkkzayJVWxrWtFU8ZoatlsfOP3A5tToio2rEhCHcnqYl4KtF1a0WUR8
KG+pJc7ATQRg4It4AQgA0Q2uZL9TIqGWtNzeAygdG0C3o+D+MoEYI/Qx0A6X
aZv7/1v84V++lRD0iuIMUlBgFEJWMsHF7cN1EMlUV1lRxOzyKTv+0FqyoSTr
bWexA+jG/Nb3Q8vSX1+eVHvm1+2H7AGhBH2szVmXeH15bGNaOaw03EmG5pCh
CIaCoXYUXKoavsa+C8827lGSuqLs1uRniCmIjQvkQSZg7a0IH6dpMIpxdHPh
h9Zyt8e74WwfmXW/be6cjWRI9FgBzl9U5EQEEVO1JdLvfzEEXkNthyAAhl+P
Z1oTR2PSs4ZUlYdb3MQrt7XoKeEOqCHHxoHB3gsj+75Jnc/aAbM+hb13imAJ
iwARAQABwsB2BBgBCAAJBQJg4It4AhsMACEJEGHAYnRSOf5GFiEExGMJvxnV
v1dXecI0YcBidFI5/kZYSQgAop0OsPV11O/fzbZ+oEabC3Ye9hNGapJQNdmJ
MJkiJg7Hnl1FO4MDtHK5OJ4YePFAqtlKRDIBCALPiN0E2v9+3yAafs6TQDc9
Lg3iIPDOnrXv7J7pv2WPnnue4o8Gkggpa+wEjbQJcUBLX311xJGBG4pSNIVN
FJcsl1fGoaxXB5ANPy/+UNMv0l/7cQWDzSw8V9WH10SO2Q4dQF7Zxw+UgBdb
mRVXWNHkcTs81WA/hYtAuLw0O5Q1QWfbXzlTJGNPy/lMMsxLF6La8fBPHlE0
CjYd4ZH9HgOvpCACjRtbc1jywaZJEisO2aJcO2BaozSzYUmkr5sH2wjSKcMS
nLviCw==
=Wg0i
-----END PGP PUBLIC KEY BLOCK-----`
The code is:
const { BlobServiceClient } = require('#azure/storage-blob');
// const { v1: uuidv1 } = require('uuid');
// const stream = require('stream').promises
const openpgp = require('openpgp');
// import * as openpgp from 'openpgp'
const { publicKey } = require('./keys')
async function main() {
const AZURE_STORAGE_CONNECTION_STRING = process.env.AZURE_STORAGE_CONNECTION_STRING;
const blobServiceClient = BlobServiceClient.fromConnectionString(AZURE_STORAGE_CONNECTION_STRING);
const containerClient = blobServiceClient.getContainerClient("uploadebs");
const containerEncryptedFiles = blobServiceClient.getContainerClient("encrypted-dide");
await containerEncryptedFiles.createIfNotExists("encrypted-dide")
// console.log(await openpgp.readKey({ armoredKey: publicKey })) <- THIS WORKS!
for await (const blob of containerClient.listBlobsFlat()) {
if (blob.name.match('^DIDE*')) {
const blockBlobClient = containerClient.getBlockBlobClient(blob.name);
const encryptedblockBlobClient = containerEncryptedFiles.getBlockBlobClient(blob.name)
blockBlobClient.download(0)
.then(downloadBlockBlobResponse => streamToString(downloadBlockBlobResponse.readableStreamBody))
.then(blobAsString => openpgp.encrypt({
message: openpgp.createMessage({ text: blobAsString }), // input as Message object
publicKeys: openpgp.readKey({ armoredKey: publicKey }),
}))
// BELOW LINE, SENDS TEXT IN BLOBS, ENCRYPTED OR NOT THROUGH FUNC UPLOAD
.then(encrypted => {encryptedblockBlobClient.upload(encrypted, encrypted.length)})
}
}
}
async function streamToString(readableStream) {
return new Promise((resolve, reject) => {
const chunks = [];
readableStream.on("data", (data) => {
chunks.push(data.toString());
});
readableStream.on("end", () => {
resolve(chunks.join(""));
});
readableStream.on("error", reject);
});
}
main().then(() => console.log('Done')).catch((ex) => console.log(ex.message));
openpgp.createMessage returns a Promise. So you need to do .then or add await before it.
Same with penpgp.readKey. It is also a promise.
For example from the Doc:
const publicKey = await openpgp.readKey({ armoredKey: publicKeyArmored });
const encrypted = await openpgp.encrypt({
message: await openpgp.createMessage({ text: 'Hello, World!' }), // input as Message object
publicKeys: publicKey, // for encryption
privateKeys: privateKey // for signing (optional)
});
EDIT2:
Without using await.
.then(blobAsString => {
return Promise.all([openpgp.createMessage({ text: blobAsString }), openpgp.readKey({ armoredKey: publicKey })])
.then(([message, publicKeys ])=>{
return openpgp.encrypt({
message,
publicKeys,
});
});
})
Used like this:
.then(blobAsString => {
return Promise.all([openpgp.createMessage({ text: blobAsString }), openpgp.readKey({ armoredKey: publicKey })])
.then(([message, publicKeys ])=>{
return openpgp.encrypt({
message,
publicKeys,
})
})
.then(encrypted => {encryptedblockBlobClient.upload(encrypted, encrypted.length)});;
})

Decrypting message form backend with error: BadPaddingException

Hello I have a simple RSA private and public key, I send the public key to backend to encrypt a text and return the encrypted text back to be decrypted. Here is client side code:
SWIFT FRONT END
func createPK(uid: String) {
do {
let keyPair = try SwiftyRSA.generateRSAKeyPair(sizeInBits: 2048)
let privateKey = keyPair.privateKey
let publicKey = keyPair.publicKey
let aa = try publicKey.pemString()
print(aa)
AF.request("http://localhost:5001/cakes-delight/us-central1/encryption", method: .post, parameters: ["publicKey" : aa]).responseJSON { (result) in
if let err = result.error {
print("error req \(err)")
return
}
if let statusCode = result.response?.statusCode {
if statusCode != 200 {
print("err: \(statusCode)")
return
}
}
if let result = result.value {
if let JSON = result as? NSDictionary {
if let id = JSON["res"] as? String {
do {
print("\n\nENCRYPTED?: \(id)\n\n")
var k = id
let encrypted = try EncryptedMessage(base64Encoded: k.toBase64())
print("aa")
let clear = try encrypted.decrypted(with: privateKey, padding: .PKCS1)
print("bb")
let string = try clear.string(encoding: .utf8)
print("\n\nDECRYPTED : \(string)\n\n")
return
} catch let error{
print("failed \(error)")
return
}
}
}
}
}
} catch {
print("err")
}
}
TYPESCRIPT BACKEND Code:
export const encryption = functions.https.onRequest(async(request, response) => {
try {
console.log(request.body["publicKey"])
// let key = await new NodeRSA('-----BEGIN RSA PUBLIC KEY-----\n'+
// request.body["publicKey"] +
// '-----END RSA PUBLIC KEY-----');
let key = await new NodeRSA(
request.body["publicKey"] );
let v = key.encrypt("i am coco!", "base64")
console.log(v)
let responseJSON = {
res: v
}
response.status(200).send(responseJSON);
} catch (err) {
console.log(err)
}
});
SwiftyRSA gives me this error: "chunkDecryptFailed(index: 0)".
I tested my key on a RSA Encryption website 8gwifi. I used the encrypted message coming from my backend and try to decrypt it to get a better error.
I get:
SYSTEM Error Error Performing RSA Encryption javax.crypto.BadPaddingException: Decryption error

php openssl_seal equivalent in Node.js

I have a code snippet in php which I would like to move into node.js but I cannot seem to find the right way to do it.
class EncryptService
{
const PUBLIC_CERT_PATH = 'cert/public.cer';
const PRIVATE_CERT_PATH = 'cert/private.key';
const ERROR_LOAD_X509_CERTIFICATE = 0x10000001;
const ERROR_ENCRYPT_DATA = 0x10000002;
public $outEncData = null;
public $outEnvKey = null;
public $srcData;
public function encrypt()
{
$publicKey = openssl_pkey_get_public(self::PUBLIC_CERT_PATH);
if ($publicKey === false) {
$publicKey = openssl_pkey_get_public("file://".self::PUBLIC_CERT_PATH);
}
if ($publicKey === false) {
$errorMessage = "Error while loading X509 public key certificate! Reason:";
while (($errorString = openssl_error_string())) {
$errorMessage .= $errorString . "\n";
}
throw new Exception($errorMessage, self::ERROR_LOAD_X509_CERTIFICATE);
}
$publicKeys = array($publicKey);
$encData = null;
$envKeys = null;
$result = openssl_seal($this->srcData, $encData, $envKeys, $publicKeys);
if ($result === false)
{
$this->outEncData = null;
$this->outEnvKey = null;
$errorMessage = "Error while encrypting data! Reason:";
while (($errorString = openssl_error_string()))
{
$errorMessage .= $errorString . "\n";
}
throw new Exception($errorMessage, self::ERROR_ENCRYPT_DATA);
}
$this->outEncData = base64_encode($encData);
$this->outEnvKey = base64_encode($envKeys[0]);
}
};
The problem is that I cannot find an implementation of the openssl_sign in Javascript anywhere. I do need to keep this structure because I use both outEncData and outEnvKey.
I managed to find the equivalent implementation of openssl_sign with the crypto package but nothing for openssl_seal.
LE added working solution as an answer
OK I've spent some time to figure this out, in short it is now in the repo: ivarprudnikov/node-crypto-rc4-encrypt-decrypt. But we want to follow SO rules here.
Below assumes that you have public key for signing the generated key and private key for testing if all is great.
Randomly generated secret key used for encryption:
const crypto = require('crypto');
const generateRandomKeyAsync = async () => {
return new Promise((resolve, reject) => {
crypto.scrypt("password", "salt", 24, (err, derivedKey) => {
if (err) reject(err);
resolve(derivedKey.toString('hex'));
});
});
}
Encrypt data with the generated key and then encrypt that key with a given public key. We want to send back both encrypted details and encrypted key as we expect the user on another side to have private key.
const crypto = require('crypto');
const path = require('path');
const fs = require('fs');
const encryptKeyWithPubAsync = async (text) => {
return new Promise((resolve) => {
fs.readFile(path.resolve('./public_key.pem'), 'utf8', (err, publicKey) => {
if (err) throw err;
const buffer = Buffer.from(text, 'utf8');
const encrypted = crypto.publicEncrypt(publicKey, buffer);
resolve(encrypted.toString('base64'));
});
});
}
const encryptStringAsync = async (clearText) => {
const encryptionKey = await generateRandomKeyAsync();
const cipher = await crypto.createCipheriv("RC4", encryptionKey, null);
const encryptedKey = await encryptKeyWithPubAsync(encryptionKey);
return new Promise((resolve, reject) => {
let encryptedData = '';
cipher.on('readable', () => {
let chunk;
while (null !== (chunk = cipher.read())) {
encryptedData += chunk.toString('hex');
}
});
cipher.on('end', () => {
resolve([encryptedKey, encryptedData]); // return value
});
cipher.write(clearText);
cipher.end();
});
}
So now we can encrypt the details:
encryptStringAsync("foo bar baz")
.then(details => {
console.log(`encrypted val ${details[1]}, encrypted key ${details[0]}`);
})
Will print something like:
encrypting foo bar baz
encrypted val b4c6c7a79712244fbe35d4, encrypted key bRnxH+/pMEKmYyvJuFeNWvK3u4g7X4cBaSMnhDgCI9iii186Eo9myfK4gOtHkjoDKbkhJ3YIErNBHpzBNc0rmZ9hy8Kur8uiHG6ai9K3ylr7sznDB/yvNLszKXsZxBYZL994wBo2fI7yfpi0B7y0QtHENiwE2t55MC71lCFmYtilth8oR4UjDNUOSrIu5QHJquYd7hF5TUtUnDtwpux6OnJ+go6sFQOTvX8YaezZ4Rmrjpj0Jzg+1xNGIIsWGnoZZhJPefc5uQU5tdtBtXEWdBa9LARpaXxlYGwutFk3KsBxM4Y5Rt2FkQ0Pca9ZZQPIVxLgwIy9EL9pDHtm5JtsVw==
To test above assumptions it is necessary first to decrypt the key with the private one:
const decryptKeyWithPrivateAsync = async (encryptedKey) => {
return new Promise((resolve) => {
fs.readFile(path.resolve('./private_key.pem'), 'utf8', (err, privateKey) => {
if (err) throw err;
const buffer = Buffer.from(encryptedKey, 'base64')
const decrypted = crypto.privateDecrypt({
key: privateKey.toString(),
passphrase: '',
}, buffer);
resolve(decrypted.toString('utf8'));
});
});
}
After key is decrypted it is possible to decrypt the message:
const decryptWithEncryptedKey = async (encKey, encVal) => {
const k = await decryptKeyWithPrivateAsync(encKey);
const decipher = await crypto.createDecipheriv("RC4", k, null);
return new Promise((resolve, reject) => {
let decrypted = '';
decipher.on('readable', () => {
while (null !== (chunk = decipher.read())) {
decrypted += chunk.toString('utf8');
}
});
decipher.on('end', () => {
resolve(decrypted); // return value
});
decipher.write(encVal, 'hex');
decipher.end();
});
}
Hope this answers the question.
The final and working version that worked for me. My problem was that I used an 128bit random key encrypt the data, instead 256bit worked in the end.
The encryption works in JS and it can be decrypted in php with the openssl_open using your private key, which was what I asked in the original question.
const crypto = require('crypto');
const path = require('path');
const fs = require('fs');
const encryptMessage = (message) => {
const public_key = fs.readFileSync(`${appDir}/certs/sandbox.public.cer`, 'utf8');
const rc4Key = Buffer.from(crypto.randomBytes(32), 'binary');
const cipher = crypto.createCipheriv('RC4', rc4Key, null);
let data = cipher.update(message, 'utf8', 'base64');
cipher.final();
const encryptedKey = crypto.publicEncrypt({
key: public_key,
padding: constants.RSA_PKCS1_PADDING
}, rc4Key);
return {
'data': data,
'env_key': encryptedKey.toString('base64'),
};
};

Encrypt works, but not decrypt in openpgp.js

I am trying to decrypt and encrypt strings with openpgp.js.
Encryption works fine, but I can't decrypt. I really can't get it to work.
Here is a working example of the encryption: https://jsfiddle.net/d4vL8ueh/1/
var message = "secret message";
const encryptMessage = async() => {
if(window.crypto.getRandomValues){
if(message != ""){
const publicKeyString = document.getElementById("pubkey").innerHTML;
var options = {
message: openpgp.message.fromText(message),
publicKeys: (await openpgp.key.readArmored(publicKeyString)).keys
};
openpgp.encrypt(options).then(ciphertext => {
alert(ciphertext.data);
})
}
}
else{
window.alert("This browser does not support basic cryptography!");
}
}
encryptMessage();
But the decryption doesn't work at all: https://jsfiddle.net/pceswg0t/2/
const decryptMessage = async() => {
encrypted = document.getElementById("encrypted").innerHTML;
if(window.crypto.getRandomValues){
if(encrypted != ""){
const privateKeyString = document.getElementById("privkey").innerHTML;
var options = {
message: await openpgp.message.readArmored(message),
privateKeys: (await openpgp.key.readArmored(privateKeyString)).keys,
passphrase: "dfgjk23jkfdklfsdds232334fddf"
};
openpgp.decrypt(options).then(plaintext => {
decrypted = plaintext.data
alert(decrypted);
})
}
}
else{
window.alert("This browser does not support basic cryptography!");
}
}
decryptMessage();
There are two problems:
Firstly, a non-existent message variable is being passed into readArmored when setting options.message. This should be using the encrypted variable instead:
message: await openpgp.message.readArmored(encrypted),
Secondly, the private key is encrypted, so it needs to be decrypted before it can be used. This can be done by calling decrypt() on the key before it is used:
var privateKeys = await openpgp.key.readArmored(privateKeyString);
privateKeys.keys[0].decrypt("dfgjk23jkfdklfsdds232334fddf")
var options = {
message: await openpgp.message.readArmored(encrypted),
privateKeys: privateKeys.keys
};
Here's an updated JSFiddle: https://jsfiddle.net/gfkqbsoz/

Categories

Resources