I have text of the form crypto.randomBytes(30).toString("hex") that I need encrypted.
Below is the encrypt and decrypt algorithms that I use.
import crypto from "crypto";
const ALGORITHM = "aes-256-ctr";
const IV_LENGTH = 16;
const ENCRYPTION_KEY = crypto.randomBytes(32);
export const encrypt = (text: string) => {
const iv = crypto.randomBytes(IV_LENGTH);
const cipher = crypto.createCipheriv(ALGORITHM, ENCRYPTION_KEY, iv);
const encryptedText = cipher.update(text, "utf8", "base64") + cipher.final("base64");
return `${iv.toString("hex")}:${encryptedText}`;
};
export const decrypt = (text: string) => {
const textParts = text.split(":");
const iv = Buffer.from(textParts.shift(), "hex");
const decipher = crypto.createDecipheriv(ALGORITHM, ENCRYPTION_KEY, iv);
const encryptedText = Buffer.from(textParts.join(":"), "base64");
return decipher.update(encryptedText, "base64", "utf8") + decipher.final("utf8");
};
I run node in my terminal and am able to mess around with these functions in my repl-like environment.
When I am within that node session, I see the following:
const encryptedText = encrypt("0e1819ff39ce47ec80488896a16520bc6b8fcd7d55dc918c96c61ff8e426")
// Output: "9fa7486458345eae2b46687a81a9fcf5:LOrlVD06eotggmIPAq0z9yzP/EHoeQyZyK6IiBYKZMIWvWYLekmSe73OjlgXdWJVOrcTyWS/eP3UU2yv"
const decryptedText = decrypt(encryptedText);
// Output: "0e1819ff39ce47ec80488896a16520bc6b8fcd7d55dc918c96c61ff8e426"
Just like I want!
If I exit the node session, and open a new node session and copy and paste to decrypt the same string I get the following:
const decryptedText = decrypt(ENCRYPTED_TEXT_FROM_ABOVE)
// Output: "�Z<�\r����S78V��z|Z\u0013��\u001a}�����#ߩ����Ɣh���*����y\b�\u001d���l'�m�'�"
Why is this happening? What changed? Clearly it seems like the Node no longer knows how to display the characters or something. I don't know what encoding it is now.
I came across this because I store the encrypted data in Postgres and upon retrieving it, I sometimes need to decrypt it. For some reason, when I restart the node session it forgets how to read it.
The interesting thing is I can decrypt(encrypt("another string")) => "another string" in the new node terminal and it'll work, but the original string no longer does.
The decryption step is failing here since you are generating a new key for each session in the line:
const ENCRYPTION_KEY = crypto.randomBytes(32);
If you log the key like so:
console.log( { key: ENCRYPTION_KEY.toString("hex") });
You'll see the key is different for each run. So it makes sense that we fail to decrypt the encrypted data from a previous session!
If you change to using a fixed key:
const ENCRYPTION_KEY = Buffer.from("8b3d2068cf410479451eef41fe07d43e62ec80b962ae30cd99f7698499acfd61", "hex");
The output from each session should be decrypted in the next one.
Of course we won't want to leave keys in code, so it would be best to use an environment variable for this purpose.
Related
I'm trying to port the following Go functions to nodeJS using crypt or crypt-js but i'm having issues trying to figure out what's wrong:
The Go encryption code is available at https://go.dev/play/p/O88Bslwd-qh ( both encrypt and decrypt work)
The current nodejs implementation is:
var decryptKey= "93D87FF936DAB334C2B3CC771C9DC833B517920683C63971AA36EBC3F2A83C24";
const crypto = require('crypto');
const algorithm = 'aes-256-cfb';
const BLOCK_SIZE = 16;
var message = "8a0f6b165236391ac081f5c614265b280f84df882fb6ee14dd8b0f7020962fdd"
function encryptText(keyStr, text) {
const hash = crypto.createHash('sha256');
//Decode hex key
keyStr = Buffer.from(keyStr, "hex")
hash.update(keyStr);
const keyBytes = hash.digest();
const iv = crypto.randomBytes(BLOCK_SIZE);
const cipher = crypto.createCipheriv(algorithm, keyBytes, iv);
cipher.setAutoPadding(true);
let enc = [iv, cipher.update(text,'latin1')];
enc.push(cipher.final());
return Buffer.concat(enc).toString('hex');
}
function decryptText(keyStr, text) {
const hash = crypto.createHash('sha256');
//Decode hex key
keyStr = Buffer.from(keyStr, "hex")
hash.update(keyStr);
const keyBytes = hash.digest();
const contents = Buffer.from(text, 'hex');
const iv = contents.slice(0, BLOCK_SIZE);
const textBytes = contents.slice(BLOCK_SIZE);
const decipher = crypto.createDecipheriv(algorithm, keyBytes, iv);
decipher.setAutoPadding(true);
let res = decipher.update(textBytes,'latin1');
res += decipher.final('latin1');
return res;
}
console.log(message)
result = decryptText(decryptKey,message);
console.log(result);
message = encryptText(decryptKey,'hola').toString();
console.log(message)
result = decryptText(decryptKey,message);
console.log(result);
Any idea why it is not working as expected?
Note: I know that padding is not required with cfb but i can't modify the encryption code, it just for reference.
I don't know Go or the specifics of aes.NewCipher(key), but from its documentation it doesn't look like it's hashing the key in any way. The Go code you're linking to also doesn't hash it, so I'm not sure why you're hashing it in the Node.js code.
This should be sufficient:
function encryptText(keyStr, text) {
const keyBytes = Buffer.from(keyStr, "hex")
…
}
function decryptText(keyStr, text) {
const keyBytes = Buffer.from(keyStr, 'hex');
…
}
As an aside: it looks like you may be encrypting JSON blocks with these functions. If so, I would suggest not using any encoding (like latin1) during the encryption/decryption process, given that JSON text must be encoded using UTF-8.
I am trying to get familiar with encryption/decryption. I am using deno as it supports the web crypto API.
I can encrypt and decrypt to get back the original plaintext using AES-CBC.
What I am now doing now is to encrypt, then manually modify the ciphertext and then decrypt. My expectation is that this would still work since I understand that AES-CBC does not provide integrity and authenticity check. (AES-GCM is the one that is AEAD)
But when I modify the cipher text and try to decrypt, it fails with the following error:
error: Uncaught (in promise) OperationError: Decryption failed
let deCryptedPlaintext = await window.crypto.subtle.decrypt(param, key, asByteArray);
^
at async SubtleCrypto.decrypt (deno:ext/crypto/00_crypto.js:598:29)
at async file:///Users/me/delete/run.js:33:26
Does AES-CBC also have integrity checks? Or why is the decryption failing?
In Deno I had a similar issue while encrypting a jwt around server and client and could not rely on the TextDecoder class for the same reason:
error: OperationError: Decryption failed
After some hours I played around and found a solution, a bit tricky, but is doing the job right:
(async ()=> {
const textEncoder = new TextEncoder();
const textDecoder = new TextDecoder();
const rawKey = crypto.getRandomValues(new Uint8Array(16));
// we import the key that we have previously generated
const cryptoKey = await crypto.subtle.importKey(
"raw",
rawKey,
"AES-CBC",
true,
["encrypt", "decrypt"],
);
// we generate the IV
const iv = crypto.getRandomValues(new Uint8Array(16));
// here is the string we want to encrypt
const stringToEncrypt = "foobar"
// we encrypt
const encryptedString = await crypto.subtle.encrypt(
{ name: "AES-CBC", iv: iv },
cryptoKey,
textEncoder.encode(stringToEncrypt),
);
// we transform the encrypted string to an UInt8Array
const uint8ArrayEncryptedString = new Uint8Array(encryptedString);
// we transform the Array to a String so we have a representation we can carry around
const stringifiedEncryption =
String.fromCharCode(...uint8ArrayEncryptedString);
/* now is time to decrypt again the message, so we transform the string into
a char array and for every iteration we transform
the char into a byte, so in the end we have a byte array
*/
const stringByteArray =
[...stringifiedEncryption].map((v) => v.charCodeAt(0))
// we transform the byte array into a Uint8Array buffer
const stringBuffer = new Uint8Array(stringByteArray.length);
// we load the buffer
stringByteArray.forEach((v, i) => stringBuffer[i] = v)
// we decrypt again
const againDecrString = await crypto.subtle.decrypt(
{ name: "AES-CBC", iv: iv },
cryptoKey,
stringBuffer,
);
console.log(textDecoder.decode(againDecrString))
})()
For some of you relying on the class for the same purpose, I suggest you to use this solution. The underlying implementation pheraps loses some information while converting back and forth the string (I needed it as string after the encryption) and so the decryption fails.
I am using crypto in node.js for encryption and decryption of user credentials, which works fine. On the other hand, I have to use the same approach to getting the exact result of encryption and decryption in Angular. As it's confirmed that crypto is not working in Angular. I would like to know any way to achieve the required result.
I added npm i --save-dev #types/node but this is not working for me. Also added crypto but the module was not found. I also use npm crypto-js encryption is done but not the same as my js method.
This is my js file code, I need to implement the same method in my Angular project:
'use strict';
const crypto = require('crypto');
const IV_LENGTH = 16;
encrypt(text: any, key: any) {
console.log('Before Encrption Data is-->', text, key)
let iv = crypto.randomBytes(this.IV_LENGTH);
let cipher = crypto.createCipheriv('aes-256-cbc', Buffer.from(key), iv);
let encrypted = cipher.update(text);
encrypted = Buffer.concat([encrypted, cipher.final()]);
let returnable = iv.toString('hex') + ':' + encrypted.toString('hex');
return returnable;
}
decrypt(text: any, key: any) {
let textParts = text.split(':');
let iv = Buffer.from(textParts.shift(), 'hex');
let encryptedText = Buffer.from(textParts.join(':'), 'hex');
let decipher = crypto.createDecipheriv('aes-256-cbc', Buffer.from(key), iv);
let decrypted = decipher.update(encryptedText);
decrypted = Buffer.concat([decrypted, decipher.final()]);
return decrypted.toString();
}
When I added crypto this type of error occurred:
Backend developers have encrypted a value in nodejs using crypto module. The code is shown below:
const _encrypt = async function(text){
var cipher = crypto.createCipher('aes-256-cbc','123|a123123123123123#&12')
var crypted = cipher.update(text,'utf8','hex')
crypted += cipher.final('hex');
console.log("in generic function....encrpted val", crypted)
return crypted;
}
I need to decrypt this value in the front end (Angular). So I tried decrypting like below:
let bytes = CryptoJS.AES.decrypt("e0912c26238f29604f5998fa1fbc78f6",'123|a123123123123123#&12');
if(bytes.toString()){
let m = JSON.parse(bytes.toString(CryptoJS.enc.Utf8));
console.log("data ",m);
}
using hardcoded value. But Im getting Error: Malformed UTF-8 data error. Can anybody please tell me how to decrypt this in angular side?
This is a tricky enough one.. the crypto.createCipher function creates a key and IV from the password you provide (See the createCipher documentation for details).
This is implemented using the OpenSSL function EVP_BytesToKey.
A JavaScript implementation is available here: openssl-file.. we'll use this to get a key and IV from the password.
So there are two steps here:
Get a key and IV from your password.
Use these with Crypto.js to decode your encoded string.
Step 1: Get key and IV (Run in Node.js )
const EVP_BytesToKey = require('openssl-file').EVP_BytesToKey;
const result = EVP_BytesToKey(
'123|a123123123123123#&12',
null,
32,
'MD5',
16
);
console.log('key:', result.key.toString('hex'));
console.log('iv:', result.iv.toString('hex'));
Step 2: Decrypt string:
const encryptedValues = ['e0912c26238f29604f5998fa1fbc78f6', '0888e0558c3bce328cd7cda17e045769'];
// The results of putting the password '123|a123123123123123#&12' through EVP_BytesToKey
const key = '18bcd0b950de300fb873788958fde988fec9b478a936a3061575b16f79977d5b';
const IV = '2e11075e7b38fa20e192bc7089ccf32b';
for(let encrypted of encryptedValues) {
const decrypted = CryptoJS.AES.decrypt({ ciphertext: CryptoJS.enc.Hex.parse(encrypted) }, CryptoJS.enc.Hex.parse(key), {
iv: CryptoJS.enc.Hex.parse(IV),
mode: CryptoJS.mode.CBC
});
console.log('Ciphertext:', encrypted);
console.log('Plain text:', decrypted.toString(CryptoJS.enc.Utf8));
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/crypto-js/3.1.9-1/crypto-js.min.js"></script>
Note that if you change the password you need to generate a new key and iv using EVP_BytesToKey.
I should note that createCipher is now deprecated, so use with caution. The same applies to EVP_BytesToKey.
I have a tricky problem to resolve. Not sure how to explain it correctly but will try my best. So here is what I am trying to do: I am trying to use a 3rd Party API, which wants me to encrypt a value and submits it. I successfully achieved it through C# code using the following block:
public string Encrypt(byte[] dataToEncrypt, byte[] keyBytes)
{
AesManaged tdes = new AesManaged();
tdes.KeySize = 256;
tdes.BlockSize = 128;
tdes.Key = keyBytes;
tdes.Mode = CipherMode.ECB;
tdes.Padding = PaddingMode.PKCS7;
ICryptoTransform crypt = tdes.CreateEncryptor();
byte[] cipher = crypt.TransformFinalBlock(dataToEncrypt, 0, dataToEncrypt.Length);
tdes.Clear();
return Convert.ToBase64String(cipher, 0, cipher.Length);
}
Now, I am trying to achieve the same in Node. I wrote the following function.
encrypt(buffer){
var buffbytes = new Buffer('my app key goes here to be used as password','utf8'); //converts the app key into buffer stream
return this.encrypt_key(new Buffer(buffer,'utf8'), buffbytes);
},
encrypt_key(buffer, keybytes){
var cipher = crypto.createCipher('aes-128-ecb',keybytes);
var crypted = cipher.update(buffer,'utf8','base64');
crypted = crypted+ cipher.final('base64');
return crypted;
},
This encryption code works fine. It encrypts it fine, but it doesn't encrypt it similar to what c# code does. When I take the encrypted text from C# code, and inject the encrypted result into the API call, it passes through fine, but when I use my encrypted result into the API call, it fails mentioning that the format of my key is incorrect.
I would like to know if these code blocks are same or not. I assume it is same, because both code using 128 bit AES, ECB Cipher and default padding for Crypto Node module is PKCS5 which is same as PKCS7 for 128 bit encryption. Please Help!
Edit: 9/19/2017
Fixed as per #smarx solution:
encrypt(buffer){
var buffbytes = new Buffer(helper.Constants.AppKey,'utf8'); //converts the app key into buffer stream
return this.encrypt_key(new Buffer(buffer,'utf8'), helper.Constants.AppKey);
},
encrypt_key(buffer, key){
var cipher = crypto.createCipheriv('aes-256-ecb',key,new Buffer(0));
var crypted = cipher.update(buffer,'utf8','base64');
crypted = crypted+ cipher.final('base64');
console.log('printed: ', crypted);
return crypted;
},
In your Node.js code, you're using the wrong cipher algorithm. Use aes-256-ecb, since you're using a 256-bit key. Also, be sure to use createCipheriv, since createCipher expects a password from which it derives an encryption key.
One-line fix:
const cipher = crypto.createCipheriv('aes-256-ecb', key, new Buffer(0));
The below two programs produce identical output (Q9VZ73VKhW8ZvdcBzm05mw==).
C#:
var key = System.Text.Encoding.UTF8.GetBytes("abcdefghijklmnopqrstuvwxyz123456");
var data = System.Text.Encoding.UTF8.GetBytes("Hello, World!");
var aes = new AesManaged {
Key = key,
Mode = CipherMode.ECB,
};
Console.WriteLine(Convert.ToBase64String(
aes.CreateEncryptor().TransformFinalBlock(data, 0, data.Length)));
Node.js:
const crypto = require('crypto');
const key = 'abcdefghijklmnopqrstuvwxyz123456';
const data = 'Hello, World!';
const cipher = crypto.createCipheriv('aes-256-ecb', key, new Buffer(0));
console.log(cipher.update(data, 'utf-8', 'base64') + cipher.final('base64'));