CryptoJS progresive cipher streams fail to decrypt and not sure why - javascript

I made 2 streams, an encryptor stream and a decryptor stream using CryptoJS's progressive ciphers. I'm using streams/pipeline from readable-stream as this is being run on React Native so it has to be Browserfied.
I keep getting the result in Base64. An example of the output I get is (of course it changes every time due to a random salt/iv):
Result: zvkpfuiRtVPzkbL89aR/Xw==
I'm not sure where I'm going wrong here. I'd appreciate any help, thank you so much.
const decryptionTest = () => {
const password = 'password123'
const source = new PassThrough()
const destination = new PassThrough()
const { encryptor, salt, iv } = createEncryptor(password, 'base64')
const decryptStream = createDecryptor(password, salt, iv, 'base64')
destination.on('data', (chunk: Buffer) => console.log('Result:', chunk.toString()))
pipeline(source, encryptor, decryptStream, destination, (error: any) => {
if (error)
console.error('Pipeline Error:', error)
})
source.write('Hello World')
}
const createEncryptor = (password: string, encoding: 'utf8' | 'base64'): EncryptorParams => {
const hashedPassword = SHA256(password)
const salt = WordArray.random(64) // Create 64 byte salt
const iv = WordArray.random(16) // Create 16 byte iv
// Generate key of size 256 bits (keySize is the size the key should be in 32 bit Words)
const key = PBKDF2(hashedPassword, salt, { keySize: 512 / 32, iterations: 1000 })
const encryptor = algo.AES.createEncryptor(key, { iv })
const encoder = encoding === 'base64' ? Base64 : Utf8
const encryptorStream = new Transform({
transform(chunk: Buffer, enc, callback) {
const encodedChunk = chunk.toString(encoding)
const encryptedChunk = encryptor.process(encodedChunk).toString(encoder)
this.push(encryptedChunk)
callback()
}
})
return { encryptor: encryptorStream, salt, iv }
}
const createDecryptor = (password: string, salt: WordArray, iv: WordArray, encoding: 'utf8' | 'base64'): Transform => {
const hashedPassword = SHA256(password)
const key = PBKDF2(hashedPassword, salt, { keySize: 512 / 32, iterations: 1000 })
const decryptor = algo.AES.createDecryptor(key, { iv })
const encoder = encoding === 'base64' ? Base64 : Utf8
return new Transform({
transform(chunk: Buffer, enc, callback) {
const encodedChunk = chunk.toString(encoding)
const decryptedChunk = decryptor.process(encodedChunk).toString(encoder)
this.push(decryptedChunk)
callback()
}
})
}
I've been tinkering with it for awhile so I tried different orders of encodings, using WordArray.create() instead of const encodedChunk = chunk.toString(encoding), but most things I did either led to a UTF8 malform error or the result of the decryption being an empty string after calling toString().

Related

How to properly decrypt string in NodeJS?

I'm working on a NodeJS & MongoDB project where it is required to encrypt the content of the article that it is about to be published.
However, it should display the content if the proper key-codes are entered.
For example, I have a function to create blog where I call my encryText() function:
exports.createBlog = asyncHandler(async (req, res, next) => {
if (req.body.text === ``) {
return next(new ErrorResponse(`Please add some text`, 500));
}
// Encrypt text
req.body.text = encrypText(req.body.text); // The data gets encrypted
// Create document
const blog = await Blog.create(req.body);
blog.save({ validateBeforeSave: true });
// Send response
res.status(201).json({ success: true, data: blog });
});
This encrypText() function returns an object that looks like this:
{
"success": true,
"data": {
"iv": "QkBZfnTyqaofnHbDC4y6Iw==",
"encryptedData": "ebe177048b1063acae4eea3b5303a56b62da0ef7c3fc537b4245cf0cbee0291bf4231d0eb97c53e0c0c33fce50f26561277c25829f7021bbc1532aed6e4dc782"
}
}
Now, nothing is wrong so far. The encryption works great and the decryption also works amazingly well also. The way to decrypt data is by using the same object returned from the encryptText() function and it does as expected but only during few minutes!.
"status": "error",
"error": {
"library": "digital envelope routines",
"function": "EVP_DecryptFinal_ex",
"reason": "bad decrypt",
"code": "ERR_OSSL_EVP_BAD_DECRYPT",
"statusCode": 500,
"status": "error"
},
"message": "error:06065064:digital envelope routines:EVP_DecryptFinal_ex:bad decrypt",
As you can see, the error gets thrown on my screen after 5mins and/or whenever I reset the server.! Why is it that this happens? I mean the string that I'm trying to decrypt has not changed at all?
const crypto = require('crypto');
// Set encryption algorith
const algorithm = 'aes-256-cbc';
/// Private key
const key = crypto.randomBytes(32);
// Random 16 digit initialization vector
const iv = crypto.randomBytes(16);
exports.encrypText = (text) => {
const cipher = crypto.createCipheriv(algorithm, key, iv);
let encryptedData = cipher.update(text, 'utf-8', 'hex');
encryptedData += cipher.final('hex');
// convert the initialization vector to base64 string
const base64data = Buffer.from(iv, 'binary').toString('base64');
return {
iv: base64data,
encryptedData: encryptedData
};
};
exports.decrypText = (text) => {
// Convert initialize vector from base64 to buffer
const originalData = Buffer.from(text.iv, 'base64');
// Decrypt the string using encryption algorith and private key
const decipher = crypto.createDecipheriv(algorithm, key, originalData);
let decryptedData = decipher.update(text.encryptedData, 'hex', 'utf-8');
decryptedData += decipher.final('utf-8');
return decryptedData;
};
Solved it by making a my secret key static and also went into a different approach which does exactly what I wanted.
const crypto = require('crypto');
// Set encryption algorith
const algorithm = 'aes-256-ctr';
/// Private key
// const key = crypto.randomBytes(32);
const key = `${process.env.ENCRYPTION_KEY}`;
// Random 16 digit initialization vector
const iv = crypto.randomBytes(16);
exports.encrypText = (text) => {
const cipher = crypto.createCipheriv(algorithm, key, iv);
const encryptedData = Buffer.concat([cipher.update(text), cipher.final()]);
return {
iv: iv.toString('hex'),
encryptedData: encryptedData.toString('hex')
};
};
exports.decrypText = (text) => {
// Convert initialize vector from base64 to hex
const originalData = Buffer.from(text.iv, 'hex');
// Decrypt the string using encryption algorith and private key
const decipher = crypto.createDecipheriv(algorithm, key, originalData);
const decryptedData = Buffer.concat([
decipher.update(Buffer.from(text.encryptedData, 'hex')),
decipher.final()
]);
return decryptedData.toString();
};

RSA encryption with Java and decryption with JavaScript

I am trying to encrypt with Java (using javax.crypto.Cipher) and decrypt with JavaScript (using crypto.subtle). What I am doing is, I make the JavaScript side generate the key pair, then send the public key to the Java side by the following:
$(window).on("load", function () {
const enc = new TextEncoder();
const dec = new TextDecoder();
crypto.subtle.generateKey({
name: "RSA-OAEP",
modulusLength: 1024,
publicExponent: new Uint8Array([1, 0, 1]),
hash: "SHA-256"
},
true,
["encrypt", "decrypt"]
).then(function ({ privateKey, publicKey }) {
crypto.subtle.exportKey("spki", publicKey).then(function (spki) {
const strPublicKey = spkiToString(spki);
// SEND THE PUBLIC KEY TO THE SERVER (JAVA)
});
});
});
function spkiToString(keydata) {
var keydataS = arrayBufferToString(keydata);
return window.btoa(keydataS);
}
function arrayBufferToString(buffer) {
var binary = '';
var bytes = new Uint8Array(buffer);
var len = bytes.byteLength;
for (var i = 0; i < len; i++) {
binary += String.fromCharCode(bytes[i]);
}
return binary;
}
The server uses the public key for encryption:
try {
String publicKey = ""// this will come from the JS side
String message = "encrypt me"//
byte[] publicBytes = Base64.getDecoder().decode(publicKey).getBytes());
X509EncodedKeySpec keySpec = new X509EncodedKeySpec(publicBytes);
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
PublicKey pubKey = keyFactory.generatePublic(keySpec);
Cipher cipher = Cipher.getInstance("RSA/ECB/OAEPPadding");
OAEPParameterSpec oaepParams = new OAEPParameterSpec("SHA-256", "MGF1", new MGF1ParameterSpec("SHA-256"),
PSource.PSpecified.DEFAULT);
cipher.init(Cipher.ENCRYPT_MODE, pubKey, oaepParams);
return Base64.getEncoder().encode(cipher.doFinal(message));
} catch (Exception e) {
e.printStacktrace();
}
return new byte[0];
The resulted value is then returned to the Javascript side, so it can decrypt the message:
const encryptedToken = "" // this will be obtained from the server
crypto.subtle.decrypt({
name: "RSA-OAEP",
hash: { name: "SHA-256" }
},
privateKey,
enc.encode(atob(encryptedToken))
).then(function (result) {
console.log("decrypted", dec.decode(result))
}).catch(function (e) {
console.log(e);
})
When the Javascript tries to decrypt, it throws a DOMException with no message (check the attached image).
What I am doing wrong? Thank you in advanced.
The problem is the enc.encode(atob(encryptedToken)) line in the decrypt() method of the last JavaScript code snippet. The UTF-8 encoding corrupts the data, preventing successful decryption. If this is changed, decryption works as shown in the following.
The JavaScript code below is essentially the same as the first code snippet from the question, with the addition of exporting the private key in PKCS8 format:
crypto.subtle.generateKey(
{
name: "RSA-OAEP",
modulusLength: 1024,
publicExponent: new Uint8Array([1, 0, 1]),
hash: "SHA-256"
},
true,
["encrypt", "decrypt"]
).then(function ({ privateKey, publicKey }) {
crypto.subtle.exportKey("spki", publicKey).then(function (spki) {
const strPublicKey = spkiToString(spki);
console.log(strPublicKey.replace(/(.{56})/g,'$1\n'));
crypto.subtle.exportKey("pkcs8", privateKey).then(function (pkcs8) {
const strPrivateKey = spkiToString(pkcs8);
console.log(strPrivateKey.replace(/(.{56})/g,'$1\n'));
});
});
});
function spkiToString(keydata) {
var keydataS = arrayBufferToString(keydata);
return window.btoa(keydataS);
}
function arrayBufferToString(buffer) {
var binary = '';
var bytes = new Uint8Array(buffer);
var len = bytes.byteLength;
for (var i = 0; i < len; i++) {
binary += String.fromCharCode(bytes[i]);
}
return binary;
}
This code was used to generate e.g. the following public key in X.509/SPKI format:
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC1F8+EvG9XP8jSXItV89QtlYy/5Z+arMegvMwsasS5IIUdr4b4eE2FGoDalaqyAxWOg/pBkzfBWAQkhuKz3i14OsBYQl1QDDm3yfmI498SsE7tZyrENCfTGrPwoCrQmEwTWOCfIBCh+mGRAUTgcsQO/g8pIFglF3QTTzlu3n0KhQIDAQAB
and private key in PKCS8 format:
MIICdwIBADANBgkqhkiG9w0BAQEFAASCAmEwggJdAgEAAoGBALUXz4S8b1c/yNJci1Xz1C2VjL/ln5qsx6C8zCxqxLkghR2vhvh4TYUagNqVqrIDFY6D+kGTN8FYBCSG4rPeLXg6wFhCXVAMObfJ+Yjj3xKwTu1nKsQ0J9Mas/CgKtCYTBNY4J8gEKH6YZEBROByxA7+DykgWCUXdBNPOW7efQqFAgMBAAECgYAK6oUFVNCHW15xI8f4ZerH1qh11tMgoUKlU0whb0wtdqLfj7mcl6/gkqDqzDPOaDYv8Y+vzT6CppoVU5YtznpCF4YRLuOfeAkY0kT9C7w62lu1C1aFMDS1Eydv0a10t001sp0W5U8J0LMgPpevPlksv2t9gZa08yGsBnVX9BIXjwJBAOrlsV6LsxNBnSKqXhZf1+uQe1vpPPzF3IXTvJzd4LhamcnImYayrg4Zjgj71+/0BFdWT9qGxtKGwJJGIjrMDG8CQQDFXLIrFMHVpdjrsAaQXvPWTSVIfVayi6Uib1HpXKiLJ53snebsBrBiShbAsJjrgWXzdurky6nGIlp5NV7i//pLAkEA4XaxRfe/XhdXtWNjxgQe41ueHH2GbXWZktbGrqcFwM4t2RHz0ueEy8HZpGPfQ9GrrQ0Kvs0o4AA5rO0mg9tBfwJBAJMWuaaH6spatyc4Yjv4uEuv5Sh4WUPp9WGLi4WbS/Whyf4N1It1lME8LGbhdqaWIrBnoTpxWw9SjREmqJgPZK8CQBAAI6IUkCeE+Lwub+akoBFuqyyIdIpIfXu4ntyxnZemmCNdotEfNL3yp0J3Rw6TpXyPDN/4uOrxt/aY2heXAKM=
The public key was applied in the Java code to generate the ciphertext below:
jCt9rD/6Q6OsjH+bd1XKB2FhDYTwzupQsFnwjKkrxulC3ztZx0j9/Zr6hBeCbFrdYFtxZi+j8lyyLJCHv0hpN0S5F/O6v/mhMIgCTWCmpWqcLqKC2zDWo180uL+dMysZm2JaBHzWA9VjnVTdVY3aRTWfu1fpEpTK6W9ESTVSS8Y=
The following JavaScript code is essentially the same as the 3rd code snippet extended by the import of the private key generated above. In addition, the ciphertext generated above is applied:
var pkcs8B64 = "MIICdwIBADANBgkqhkiG9w0BAQEFAASCAmEwggJdAgEAAoGBALUXz4S8b1c/yNJci1Xz1C2VjL/ln5qsx6C8zCxqxLkghR2vhvh4TYUagNqVqrIDFY6D+kGTN8FYBCSG4rPeLXg6wFhCXVAMObfJ+Yjj3xKwTu1nKsQ0J9Mas/CgKtCYTBNY4J8gEKH6YZEBROByxA7+DykgWCUXdBNPOW7efQqFAgMBAAECgYAK6oUFVNCHW15xI8f4ZerH1qh11tMgoUKlU0whb0wtdqLfj7mcl6/gkqDqzDPOaDYv8Y+vzT6CppoVU5YtznpCF4YRLuOfeAkY0kT9C7w62lu1C1aFMDS1Eydv0a10t001sp0W5U8J0LMgPpevPlksv2t9gZa08yGsBnVX9BIXjwJBAOrlsV6LsxNBnSKqXhZf1+uQe1vpPPzF3IXTvJzd4LhamcnImYayrg4Zjgj71+/0BFdWT9qGxtKGwJJGIjrMDG8CQQDFXLIrFMHVpdjrsAaQXvPWTSVIfVayi6Uib1HpXKiLJ53snebsBrBiShbAsJjrgWXzdurky6nGIlp5NV7i//pLAkEA4XaxRfe/XhdXtWNjxgQe41ueHH2GbXWZktbGrqcFwM4t2RHz0ueEy8HZpGPfQ9GrrQ0Kvs0o4AA5rO0mg9tBfwJBAJMWuaaH6spatyc4Yjv4uEuv5Sh4WUPp9WGLi4WbS/Whyf4N1It1lME8LGbhdqaWIrBnoTpxWw9SjREmqJgPZK8CQBAAI6IUkCeE+Lwub+akoBFuqyyIdIpIfXu4ntyxnZemmCNdotEfNL3yp0J3Rw6TpXyPDN/4uOrxt/aY2heXAKM=";
const pkcs8StrDER = atob(pkcs8B64);
const pkcs8DER = str2ab(pkcs8StrDER);
crypto.subtle.importKey(
"pkcs8",
pkcs8DER,
{
name: "RSA-OAEP",
modulusLength: 1024,
publicExponent: new Uint8Array([1, 0, 1]),
hash: "SHA-256",
},
true,
["decrypt"]
).then(function(privateKey){
const dec = new TextDecoder();
const encryptedToken = "jCt9rD/6Q6OsjH+bd1XKB2FhDYTwzupQsFnwjKkrxulC3ztZx0j9/Zr6hBeCbFrdYFtxZi+j8lyyLJCHv0hpN0S5F/O6v/mhMIgCTWCmpWqcLqKC2zDWo180uL+dMysZm2JaBHzWA9VjnVTdVY3aRTWfu1fpEpTK6W9ESTVSS8Y=";
crypto.subtle.decrypt(
{
name: "RSA-OAEP",
},
privateKey,
str2ab(atob(encryptedToken))
).then(function (result) {
console.log("decrypted:", dec.decode(result))
}).catch(function (e) {
console.log(e);
});
});
function str2ab(str) {
const buf = new ArrayBuffer(str.length);
const bufView = new Uint8Array(buf);
for (let i = 0, strLen = str.length; i < strLen; i++) {
bufView[i] = str.charCodeAt(i);
}
return buf;
}
Instead of the UTF-8 encoding which corrupts the data, the function str2ab() is used which converts the Base64 decoded data into an ArrayBuffer.
The substitution _base64ToArrayBuffer(encryptedToken) suggested in my comment is equally possible. Unlike str2ab(), _base64ToArrayBuffer() also performs the base64 decoding.
Running the code results in the plaintext encrypt me of the Java code.

Decrypt an RSA message from browser with window.crypto.subtle APIs

I'm trying to decode an RSA 2048 bit message encoded with a public key using the corresponding private key. The environment is google chrome and I'm using the window.crypto.subtle APIs.
I generated the key couple and encoded the message using openssl tools:
# generate keys and put the private one in file private_key.pem
openssl genpkey -algorithm RSA -out private_key.pem -pkeyopt rsa_keygen_bits:2048
# extract public key in file public_key.pem
openssl rsa -pubout -in private_key.pem -out public_key.pem
# encode message input.txt using the public key
openssl rsautl -encrypt -oaep -inkey public_key.pem -pubin -in input.txt -out msg_rsa.enc
# convert the encoded msg in base 64 format
base64 msg_rsa.enc > msg_rsa_64.enc
This is the javascript code I'm using to decode the message:
function str2ab(str) {
const buf = new ArrayBuffer(str.length);
const bufView = new Uint8Array(buf);
for (let i = 0, strLen = str.length; i < strLen; i++) {
bufView[i] = str.charCodeAt(i);
}
return buf;
}
async function importPrivateKey(pem) {
pem = pem.replace( /[\r\n]+/gm, "" );
// fetch the part of the PEM string between header and footer
const pemHeader = "-----BEGIN PRIVATE KEY-----";
const pemFooter = "-----END PRIVATE KEY-----";
const pemContents = pem.substring(pemHeader.length, pem.length - pemFooter.length);
// base64 decode the string to get the binary data
const binaryDerString = window.atob(pemContents);
// convert from a binary string to an ArrayBuffer
const binaryDer = str2ab(binaryDerString);
return window.crypto.subtle.importKey(
"pkcs8",
binaryDer,
{
name: "RSA-OAEP",
modulusLength: 2048,
publicExponent: new Uint8Array([1, 0, 1]),
hash: "SHA-256",
},
true,
["decrypt"]
);
}
async function decryptRSA(_key, ciphertext) {
let decrypted = await window.crypto.subtle.decrypt(
{
name: "RSA-OAEP"
},
_key,
ciphertext
);
const dec = new TextDecoder();
return dec.decode(decrypted);
}
const fromBase64 = base64String => Uint8Array.from(atob(base64String), c => c.charCodeAt(0));
window.onload = init;
async function init() {
const privateKey = '-----BEGIN PRIVATE KEY-----\
MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQC3jmTi3O1k2YXs\
AM6nNTTIzDq5YWkxYrYb6cpO9eYuzmphgRnVDR6a1YWRXMoCuCfuNXcDGywzudRn\
bBMw0FHKLUqCttVHGpZYu0+0tRR10ubxiz/xnd/aCmRYHcmUNn8Qdh3KU59A9HK5\
HhYFf1vhK8r3fkoO4CjoGo1ROzXyMybUSy+4mSNscUtt5LwrVn48vXvG5i5B4DRT\
nM4cINmutEzA2s5cDt+dzU4Py71fKBRDRIGGn0vdVSoZKbWuhm5WewyRewCk7HFc\
PALCi5/1A7VKDAHUC4FlXmuG2+wzdchEyxMj6oLR7+BkKFQaTmuMM/22cGBjVTVt\
pSr3iDovAgMBAAECggEBAIuTQW+oovNu3IDq1DkdIjgV5AmW4tBkySlMi0OjhBbP\
auEdtDDnOwBtoJU6Q3nx4psmGItKHEBw6+yAp88UeT0NV30x3delhfGO7Trx/s7h\
Qi8lvcfSTqeUA11luSR0lAZGaryw/YX820eccw5XG9yK2ll7tIC/PxvPJOpB5fF2\
XGxGrionTjHDzXJ1OWX0i0aZlNNufInJAHhlt7aT3GiQMKcQs+AUb/+bWxI3Hln8\
KcL13EUlD4pJW8vtTK3gCnQNKKMoPB5Ugqe5BrU8ElkBz+zSKDnVwt5bgjrlucYz\
rKJxWr6/qTRZkzmvkhaJeNzzepfwkFsQ/eHcxYrtuDECgYEA8OXkQ2SqYDZwizCd\
SuVkx2zHm3zXYRSlRtnXYoUdJyTyuZ4k2GvXBrlwRsOJ14emVkHKyR5enmNjwrW5\
dcD2tbBzavcqOYAMiHcKklcS/gWgPx0X5QFHU33vr8u51BQWCz75lxddWNKxVAN/\
cUTugONtS4+EP4dSZhuxHt6RscsCgYEAwxA9QmZrI54hjMkIyqwmviRmIk42S5kk\
OxbhxBbt5qVueLRB092JyGmCR2kYiqdHCYkGFDOE4kni6Bsszq5CSJvhiATFeZeX\
ldFQeZqAiRY2rAd7xD1upMug/cK3ODA8k3k/e72CtyxtBTR01q29SnPx5p/57MrI\
3ogddHlGvK0CgYEA3VqhELwjQh1D9OJK5lM683SlRd7FGdOauyvYmhKu4xU0ZBNI\
0ATnpKoo3R04P+/JjGEQMRXS4794H6ZUMDuLdxAYPiW3ivZ6jbq04BtavEf3I4dc\
OXWfULzbzbFpo9KBHvxS4974S3Hut8AvDqnEbnKML25EmwuBT4oKis8BGVkCgYEA\
nusPDZbFeNou+TUbzYrdcZHUB+TyhTq58s4clxYbMgrbaslozAQ0aavT8Pvle6j2\
zgTth+3FOFr72x+wrJ358I/W+Wrxu7NOU0eZqci/KXCIkDT0l5d5GhewDK3jeYqK\
/5cLqnNmGHfARjpLak9X5V162erBwjIf3nTEkozvnW0CgYB6L1CX3DkTFH3OBcJe\
SvV18RDUfNI8MpUKcpofmwwvgER3GrehSZHRVxVwNbnJOqbh/oiwmmNJieKrFsk9\
EzCRBVWdZByHHYW2js+gCrAp+ghnl1QEAeCU7YTxCJ2fZIAmfB9X4u/7ARtVxnZY\
mOWlm65KUYr5lf2Ws5plL4pCRA==\
-----END PRIVATE KEY-----';
const ciphertext = 'F6/NwENdUZSl+vrgpWVkyWPQuYaTGDNZPIvj4KmIRHVx4qybxN24LPIgk0Rl84KHcLFadZWCjNpM\
vg3l826OaKZAtwvIp9IxVrMbvtNOymY6A1koKvC9ema92SR4DC9hmTtMxhUB6r3XgACtRLFqMfg+\
zYSHfFqQEGJg3yZ43hfzIq/gCfHPk5sZXASq5WY5b9yd4gRonn5D4OCD6xna/r5ovHfrpO/Fwe8N\
eeY2gqTAdtzvtmOw/HLQhGANejpJYr1IriQbepM7jLjBkJX+uCn38O1MxpQb7s5RXTvGvoEoofWV\
Cq8gNFhgnVFuurdZUiY0bn58UwaVFdwzEfDSUQ==';
try {
const key = await importPrivateKey(privateKey);
const decoded = await decryptRSA(key, fromBase64(ciphertext));
console.log(decoded);
} catch(error) {
console.log(error);
}
}
Running the code, I got an exception in window.crypto.subtle.decrypt with the rather useless message "DOMException".
What am I doing wrong?
Thanks
There's only one flaw: The posted code currently uses OAEP with SHA256. The ciphertext can be decrypted with the posted key if OAEP with SHA1 is applied as padding.
In addition, the function fromBase64 can be used to Base64 decode the key into a TypedArray, so the function str2ab is actually not needed (but of course this is not an error, just redundant).
const fromBase64 = base64String => Uint8Array.from(atob(base64String), c => c.charCodeAt(0));
const getPkcs8Der = pkcs8Pem => {
pkcs8Pem = pkcs8Pem.replace( /[\r\n]+/gm, "" );
const pkcs8PemHeader = "-----BEGIN PRIVATE KEY-----";
const pkcs8PemFooter = "-----END PRIVATE KEY-----";
pkcs8Pem = pkcs8Pem.substring(pkcs8PemHeader.length, pkcs8Pem.length - pkcs8PemFooter.length);
return fromBase64(pkcs8Pem);
}
async function importPrivateKey(pkcs8Pem) {
return await window.crypto.subtle.importKey(
"pkcs8",
getPkcs8Der(pkcs8Pem),
{
name: "RSA-OAEP",
hash: "SHA-1", // Replace SHA-256 with SHA-1
},
true,
["decrypt"]
);
}
async function decryptRSA(key, ciphertext) {
let decrypted = await window.crypto.subtle.decrypt(
{
name: "RSA-OAEP"
},
key,
ciphertext
);
const dec = new TextDecoder();
return dec.decode(decrypted);
}
window.onload = init;
async function init() {
const privateKey =
'-----BEGIN PRIVATE KEY-----\
MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQC3jmTi3O1k2YXs\
AM6nNTTIzDq5YWkxYrYb6cpO9eYuzmphgRnVDR6a1YWRXMoCuCfuNXcDGywzudRn\
bBMw0FHKLUqCttVHGpZYu0+0tRR10ubxiz/xnd/aCmRYHcmUNn8Qdh3KU59A9HK5\
HhYFf1vhK8r3fkoO4CjoGo1ROzXyMybUSy+4mSNscUtt5LwrVn48vXvG5i5B4DRT\
nM4cINmutEzA2s5cDt+dzU4Py71fKBRDRIGGn0vdVSoZKbWuhm5WewyRewCk7HFc\
PALCi5/1A7VKDAHUC4FlXmuG2+wzdchEyxMj6oLR7+BkKFQaTmuMM/22cGBjVTVt\
pSr3iDovAgMBAAECggEBAIuTQW+oovNu3IDq1DkdIjgV5AmW4tBkySlMi0OjhBbP\
auEdtDDnOwBtoJU6Q3nx4psmGItKHEBw6+yAp88UeT0NV30x3delhfGO7Trx/s7h\
Qi8lvcfSTqeUA11luSR0lAZGaryw/YX820eccw5XG9yK2ll7tIC/PxvPJOpB5fF2\
XGxGrionTjHDzXJ1OWX0i0aZlNNufInJAHhlt7aT3GiQMKcQs+AUb/+bWxI3Hln8\
KcL13EUlD4pJW8vtTK3gCnQNKKMoPB5Ugqe5BrU8ElkBz+zSKDnVwt5bgjrlucYz\
rKJxWr6/qTRZkzmvkhaJeNzzepfwkFsQ/eHcxYrtuDECgYEA8OXkQ2SqYDZwizCd\
SuVkx2zHm3zXYRSlRtnXYoUdJyTyuZ4k2GvXBrlwRsOJ14emVkHKyR5enmNjwrW5\
dcD2tbBzavcqOYAMiHcKklcS/gWgPx0X5QFHU33vr8u51BQWCz75lxddWNKxVAN/\
cUTugONtS4+EP4dSZhuxHt6RscsCgYEAwxA9QmZrI54hjMkIyqwmviRmIk42S5kk\
OxbhxBbt5qVueLRB092JyGmCR2kYiqdHCYkGFDOE4kni6Bsszq5CSJvhiATFeZeX\
ldFQeZqAiRY2rAd7xD1upMug/cK3ODA8k3k/e72CtyxtBTR01q29SnPx5p/57MrI\
3ogddHlGvK0CgYEA3VqhELwjQh1D9OJK5lM683SlRd7FGdOauyvYmhKu4xU0ZBNI\
0ATnpKoo3R04P+/JjGEQMRXS4794H6ZUMDuLdxAYPiW3ivZ6jbq04BtavEf3I4dc\
OXWfULzbzbFpo9KBHvxS4974S3Hut8AvDqnEbnKML25EmwuBT4oKis8BGVkCgYEA\
nusPDZbFeNou+TUbzYrdcZHUB+TyhTq58s4clxYbMgrbaslozAQ0aavT8Pvle6j2\
zgTth+3FOFr72x+wrJ358I/W+Wrxu7NOU0eZqci/KXCIkDT0l5d5GhewDK3jeYqK\
/5cLqnNmGHfARjpLak9X5V162erBwjIf3nTEkozvnW0CgYB6L1CX3DkTFH3OBcJe\
SvV18RDUfNI8MpUKcpofmwwvgER3GrehSZHRVxVwNbnJOqbh/oiwmmNJieKrFsk9\
EzCRBVWdZByHHYW2js+gCrAp+ghnl1QEAeCU7YTxCJ2fZIAmfB9X4u/7ARtVxnZY\
mOWlm65KUYr5lf2Ws5plL4pCRA==\
-----END PRIVATE KEY-----';
const ciphertext =
'F6/NwENdUZSl+vrgpWVkyWPQuYaTGDNZPIvj4KmIRHVx4qybxN24LPIgk0Rl84KHcLFadZWCjNpM\
vg3l826OaKZAtwvIp9IxVrMbvtNOymY6A1koKvC9ema92SR4DC9hmTtMxhUB6r3XgACtRLFqMfg+\
zYSHfFqQEGJg3yZ43hfzIq/gCfHPk5sZXASq5WY5b9yd4gRonn5D4OCD6xna/r5ovHfrpO/Fwe8N\
eeY2gqTAdtzvtmOw/HLQhGANejpJYr1IriQbepM7jLjBkJX+uCn38O1MxpQb7s5RXTvGvoEoofWV\
Cq8gNFhgnVFuurdZUiY0bn58UwaVFdwzEfDSUQ==';
try {
const key = await importPrivateKey(privateKey);
const decrypted = await decryptRSA(key, fromBase64(ciphertext));
console.log(decrypted);
} catch(error) {
console.log(error);
}
}

NodeJS crypto module: clean cipher reuse

I'm a bit new to the crypto module in Node. I wrote an encryption function that works perfectly fine but looks awful. This there a better way to write this?
const encrypt = data => {
const iv = crypto.randomBytes(16);
const key = crypto.randomBytes(32).toString('hex').slice(0, 32);
const cipher1 = crypto.createCipheriv(algorithm, new Buffer(key), iv);
const cipher2 = crypto.createCipheriv(algorithm, new Buffer(key), iv);
const cipher3 = crypto.createCipheriv(algorithm, new Buffer(key), iv);
const encryptedFile = Buffer.concat([cipher1.update(data.file), cipher1.final()]);
const encryptedFileName = Buffer.concat([cipher2.update(data.fileName), cipher2.final()]).toString('hex');
const encryptedId = Buffer.concat([cipher3.update(data.Id), cipher3.final()]).toString('hex');
return {
file: encryptedFile,
fileName: encryptedFileName,
id: iv.toString('hex') + ':' + encryptedId.toString('hex'),
key
};
};
Input is an object of this structure:
{
file: <Buffer>,
fileName: <String>,
Id: <String>
}
I need to encrypt all of the values with the same key + iv, but not the whole object at once. Is there a way to refactor this to avoid duplication?
Try this:
const encrypt = (data, key, iv) => {
const cipher = crypto.createCipheriv(algorithm, new Buffer(key), iv);
return Buffer.concat([cipher.update(data), cipher.final()]);
};
const encrypt = data => {
const iv = crypto.randomBytes(16);
const key = crypto
.randomBytes(32)
.toString('hex')
.slice(0, 32);
return {
file: encrypt(data.file, key, iv),
fileName: encrypt(data.fileName, key, iv).toString('hex'),
id: `${iv.toString('hex')}:${encrypt(data.Id, key, iv).toString('hex')}`,
key,
};
};

Using node-jose, how do I decrypt the data I just encrypted?

I am trying to implement simple JOSE encrypt and decrypt functions using node-jose.
My code is as follows (written using Node 8.2.1)
const { JWE } = require('node-jose');
const jose = (publicKey, privateKey) => {
async function encrypt(raw) {
if (!raw) throw new Error('Missing raw data.')
const buffer = new Buffer(JSON.stringify(raw));
return JWE.createEncrypt(publicKey).update(buffer).final();
}
async function decrypt(encrypted) {
if (!encrypted) throw new Error('Missing encrypted data.')
const buffer = new Buffer(JSON.stringify(encrypted));
return JWE.createDecrypt(privateKey).decrypt(buffer);
}
return { encrypt, decrypt }
}
module.exports = jose;
I generate an RSA keypair using generate-rsa-keypair.
So testing via this code the encryption side of things works fine
const { JWK } = require('node-jose');
const keygen = require('generate-rsa-keypair');
const jose = require('./src/utils/jose');
const rawKeys = keygen();
const makeKey = pem => JWK.asKey(pem, 'pem');
async function start() {
const publicKey = await makeKey(rawKeys.public)
const privateKey = await makeKey(rawKeys.private)
const raw = {
iss: 'test',
exp: new Date().getTime() + 3600,
sub: {
test: 'This is a test',
},
};
const { encrypt, decrypt } = jose(publicKey, privateKey);
return encrypt(raw).then(encrypted => decrypt(encrypted));
}
return start().then((result) => {
console.log('decrypted', result)
}, (err) => {
console.error(err);
});
the encrypted result is
{
recipients: [ { encrypted_key: 'ciNiK6Unq30zCAXxIl2Dx9b8bZAi79qbpL1yUCwTFnSghFLrIZ11_D2ozt5on3r3ThUu96oDLZPcNShbqWPMV49NvQAsSNGdemhgzmTt3Lf3rJn1YiqvJvqf5NIXdmzjdoEZi-d9224mGpZGVKtIIFeT6-0hYgm5zNqq_aF_X2jy5IiF-mAGspNdXIk_KXPrTVbnU-XL9J5aAoG2Lp51Te1WzGA4Fjg4Ve5ZTzH6TLlQ5R5Ob_14liK-INrSi3armwXrtMgJcTmI_4oBtORtZp8AjaXzecFO_GzifvRVCSKx2vmpy9KaECpskMhZBHVx9RX9cvGKh7hq3Y7vsUucZw' } ],
protected: 'eyJhbGciOiJSU0EtT0FFUCIsImtpZCI6IldLWS1ONDRXM2RnanA4U2ZxSlp3TldqV3AzUG1XZ29UczhjRDh3eWNSUWciLCJlbmMiOiJBMTI4Q0JDLUhTMjU2In0',
iv: 'wvqir2ewtQPfDHQtzl6IUg',
ciphertext: 'ZwIrL_3739LI17rh3gWDUA6lXIL7ewkSh54FO_RwumC0qh9B0DcAr8RyXsfPbW19cV4u7SbZNSRP6B8qNOTy-2iENlqBISfE_kolDt8g5sg',
tag: 'z8nwrJfRgOi1hYMBI9lGeQ'
}
but when I try to decrypt that I get
Error: no key found
at processKey (node_modules/node-jose/lib/jwe/decrypt.js:157:22)
There are very few examples of using node-jose so I am unsure of the following
I am assuming I ought to be decrypting with the private key. But that's just an assumption. None of the examples show use of public/private key pairs, just a single key.
I'm assuming that the results of the encryption can just be strringified and turned into a buffer and passed into decrypt but perhaps that's not the case.
How does this really work?
When using public/private key pairs, the private key is used to decrypt and the public key is used to encrypt.
The input to JWEDecrypter.decrypt() is the promised output from JWEEncrypter.final().
Change your decrypt function to:
async function decrypt(encrypted) {
if (!encrypted) throw new Error('Missing encrypted data.')
return JWE.createDecrypt(privateKey).decrypt(encrypted);
}

Categories