My project was in Angular 6 and it had following lines of code
const crypto = require('crypto-js');
const Buffer = require('buffer').Buffer;
const decrypt = new Buffer(data.result.encr, 'base64');
const privatekey = Buffer.from(data.result.pk, 'base64');
this.decrypted = crypto.privateDecrypt(privatekey, decrypt).toString('utf-8');
return this.decrypted;
Which was working fine.
Now I migrated my code to Angular 9. And I find out that crypto has no longer support from NPM
https://www.npmjs.com/package/crypto
It says that I have to use inbuild library of crypto. But I have no idea how to use it.
I thought crypto-js would help me. But it didn't.
If someone knows how to use crypto in Angular 9 or how to convert upper lines for crypto-js then it would be great.
Note: Encryption is happening on server side using crypto only as they have nodejs.
Thanks in advance.
After 3-4 days I am finally able to resolve this.
I installed crypto-browserify.
Delete node_modules folder and then again installed all dependencies by using npm-install
crypto-browserify provides same features as crypto
I recently acheived this in my MEAN Stack app. After installing crypto-js with following command:
npm i crypto-js --save
Following service in Angular-9 which can be used through out the project for encryption and decryption.
import { Injectable } from '#angular/core';
import * as CryptoJS from 'crypto-js';
import { environment } from 'src/environments/environment';
#Injectable({
providedIn: 'root'
})
export class CryptoJsService {
constructor() { }
get jsonFormatter() {
return {
stringify: (cipherParams: any) => {
const jsonObj = { ct: cipherParams.ciphertext.toString(CryptoJS.enc.Base64), iv: null, s: null };
if (cipherParams.iv) {
jsonObj.iv = cipherParams.iv.toString();
}
if (cipherParams.salt) {
jsonObj.s = cipherParams.salt.toString();
}
return JSON.stringify(jsonObj);
},
parse: (jsonStr) => {
const jsonObj = JSON.parse(jsonStr);
// extract ciphertext from json object, and create cipher params object
const cipherParams = CryptoJS.lib.CipherParams.create({
ciphertext: CryptoJS.enc.Base64.parse(jsonObj.ct)
});
if (jsonObj.iv) {
cipherParams.iv = CryptoJS.enc.Hex.parse(jsonObj.iv);
}
if (jsonObj.s) {
cipherParams.salt = CryptoJS.enc.Hex.parse(jsonObj.s);
}
return cipherParams;
}
};
}
/* Method for Encryption */
encrypt(value: any) {
const key = environment.crypto_js_key; // SECRET KEY FOR ENCRYPTION
value = value instanceof String ? value : JSON.stringify(value);
const encrypted = CryptoJS.AES.encrypt(value,key,
{ format: this.jsonFormatter, mode: CryptoJS.mode.CBC }).toString();
return encrypted;
}
/* Method for Decryption */
decrypt(value: any): any {
const key = environment.crypto_js_key; //SECRET KEY FOR ENCRYPTION
const decrypted = CryptoJS.AES.decrypt(value, key, {format: this.jsonFormatter }).toString(CryptoJS.enc.Utf8);
return JSON.parse(decrypted);
}
}
In Nodejs, following utility could be used through out the app:
var CryptoJS = require('crypto-js');
var config = require('../config/environment');
module.exports.encrypt = function(value){
var JsonFormatter = {
stringify: function(cipherParams){
var jsonObj = { ct: cipherParams.ciphertext.toString(CryptoJS.enc.Base64) };
if (cipherParams.iv) {
jsonObj.iv = cipherParams.iv.toString();
}
if (cipherParams.salt) {
jsonObj.s = cipherParams.salt.toString();
}
return JSON.stringify(jsonObj);
},
parse: function(jsonStr) {
var jsonObj = JSON.parse(jsonStr);
// extract ciphertext from json object, and create cipher params object
var cipherParams = CryptoJS.lib.CipherParams.create({
ciphertext: CryptoJS.enc.Base64.parse(jsonObj.ct)
});
if (jsonObj.iv) {
cipherParams.iv = CryptoJS.enc.Hex.parse(jsonObj.iv);
}
if (jsonObj.s) {
cipherParams.salt = CryptoJS.enc.Hex.parse(jsonObj.s);
}
return cipherParams;
}
}
value = value instanceof String ? value: JSON.stringify(value);
var encrypted = CryptoJS.AES.encrypt(value, config.crypto_js_key, {
format: JsonFormatter, mode: CryptoJS.mode.CBC
}).toString();
return encrypted;
}
module.exports.decrypt = function(value) {
return CryptoJS.AES.decrypt(value, config.crypto_js_key, {format: JsonFormatter }).toString(CryptoJS.enc.Utf8);
}
Depending on the desired hash, the best option for me was ts-md5 lib.
import {Md5} from 'ts-md5/dist/md5';
...
Md5.hashStr('blah blah blah'); // hex:string
Md5.hashStr('blah blah blah', true); // raw:Int32Array(4)
Md5.hashAsciiStr('blah blah blah'); // hex:string
Md5.hashAsciiStr('blah blah blah', true); // raw:Int32Array(4)
Related
I am trying to rewrite old NodeJs encryption algorithm from
crypto.createDecipheriv(algorithm, key, iv[, options])
into webcrypto
subtle.decrypt(algorithm, key, data)
This code work good enough with AES-128-CTR algorithm
const algorithm = 'aes-128-ctr';
const iv = '0123456789ABCDEF0123456789ABCDEF';
const privateKey = '16Random_Letters';
const hexBufferFromIv = Buffer.from(iv, 'hex');
const utfBufferFromPrivateKey = Buffer.from(privateKey, 'utf8');
function oldEncryptData(data: string): string {
const cipher = createCipheriv(
algorithm,
utfBufferFromPrivateKey,
hexBufferFromIv,
);
let crypted = cipher.update(data, 'utf8', 'base64');
crypted += cipher.final('base64');
return crypted;
}
function oldDecryptData(data: string): string {
const decipher = createDecipheriv(
algorithm,
utfBufferFromPrivateKey,
hexBufferFromIv,
);
let dec = decipher.update(data, 'base64', 'utf8');
dec += decipher.final('utf8');
return dec;
}
async function testDecrypt() {
const sourceText = `any text to encrypt!`;
const encryptedText = oldEncryptData(sourceText);
const decryptedText = oldDecryptData(encryptedText);
return sourceText === decryptedText;
}
testDecrypt().then(console.log);
Right now I test this code and WebCrypto examples in nodejs, but as a final result I wont to move webCrypto.subtle.decrypt functionality into NGINX njs and as I know, njs doesn't support other options for decryption except for WebCrypto.
Interface for WebCrypto decrypt for AES-CTR in general looks like
const data = await crypto.subtle.decrypt(
{
name: "AES-CTR",
counter, // BufferSource
length: 128, // 1-128
},
key, // AES key
encData, // BufferSource
);
And I don't undersatnd.
counter is the same thing as the Initialization vector in createDecipheriv method?
How I should generate key for subtle.decrypt method from the same passphrase?
Do I need to do any additional transformation from or to base64 or utf8 encoding to reproduce input and output encoding in cipher.update(data, 'utf8', 'base64'); and in decipher.update(data, 'base64', 'utf8'); methods?
Thanks Topaco for hints. I'll write a more complete answer. Maybe it will be useful for someone.
Yes, Initialization vector and counter can be treated as the same thing.
For generating a key from the same passphrase you should use importKey method. And you should sent the same ArrayBuffer from the passphrase as in createCipheriv method.
Yes, if your old method used some specific encoding and decoding, you should repeat the same encoding/decoding logic after Webcrypto.SubtleCrypto.encrypt() and decrypt() methods.
Full workable example may looks something like
import { webcrypto } from 'crypto';
const iv = '0123456789ABCDEF0123456789ABCDEF';
const privateKey = '16Random_Letters';
const hexBufferFromIv = Buffer.from(iv, 'hex');
const utfBufferFromPrivateKey = Buffer.from(privateKey, 'utf8');
async function generateKeyFromPassPhrase(): Promise<CryptoKey> {
return webcrypto.subtle.importKey(
'raw',
utfBufferFromPrivateKey,
{
name: 'AES-CTR',
},
true,
['decrypt', 'encrypt'],
);
}
async function newEncryptData(data: string): Promise<string> {
const key = await generateKeyFromPassPhrase();
const encryptResult = await webcrypto.subtle.encrypt(
{
name: 'AES-CTR',
length: 128,
counter: hexBufferFromIv,
},
key,
Buffer.from(data),
);
return Buffer.from(encryptResult).toString('base64');
}
async function newDecryptData(data: string): Promise<string> {
const key = await generateKeyFromPassPhrase();
const decryptResult = await webcrypto.subtle.decrypt(
{
name: 'AES-CTR',
length: 128,
counter: hexBufferFromIv,
},
key,
Buffer.from(data, 'base64'),
);
return Buffer.from(decryptResult).toString();
}
async function testDecrypt() {
const sourceText = `any text to encrypt!`;
const encrypted2 = await newEncryptData(sourceText);
const decrypted2 = await newDecryptData(encrypted2);
return sourceText === decrypted2;
}
testDecrypt().then(console.log);
I have a project need to encrypt data to send to backend.
and the crypto is like this:
const NULL_IV = Buffer.from([]) // new Buffer([]);
const crypto = require('crypto'),
algorithm = 'aes-256-ecb'
const { bodyCrypt:{password} } = require('../config/index')
function aesEncrypt(string = '') {
const cipher = crypto.createCipheriv(algorithm, password, NULL_IV)
let encrypted = Buffer.concat([cipher.update(Buffer.from(string, 'utf8')), cipher.final()])
return encrypted.toString('hex')
}
function aesDecrypt(string = '') {
const decipher = crypto.createDecipheriv(algorithm, password, NULL_IV)
let decrypted = Buffer.concat([decipher.update(Buffer.from(string, 'hex')), decipher.final()])
return decrypted.toString()
}
module.exports = {
aesEncrypt,
aesDecrypt,
}
How can I use this in browser?!!
I try to use cryptoJs to encrypt, but the ciphertext is changing and couldnt be decrypt by the code above.
const CryptoJS = require('crypto-js')
const data = '1'
const key = '123456x3bxiinky1xzc95wcgc0p9p2p7'
const cipher = CryptoJS.AES.encrypt(data, key, {
mode: CryptoJS.mode.ECB,
padding: CryptoJS.pad.Pkcs7,
iv: '',
keySize: 256
})
// 将加密后的数据转换成 Base64
const hexText= cipher.ciphertext.toString(CryptoJS.enc.Hex)
If you want to learn how to work with native node crypto and vite, you can read this answer :
TypeError: crypto.createCipheriv is not a function
Thanks #Topaco.
If just want to encrypt request to a server or decrypt payload that a server send to us using "aes-256-ecb" algorithm.
Just use CryptoJS to make it works:
import CryptoJS from 'crypto-js'
import { crypt } from '#/config'
const { password } = crypt
const cryptKey = CryptoJS.enc.Utf8.parse(password)
const cryptoOptions = {
mode: CryptoJS.mode.ECB,
padding: CryptoJS.pad.Pkcs7,
iv: '',
keySize: 256
}
/**
* encrypt
* #param {*} string
* #returns {string}
*/
function aesEncrypt(string = '') {
const cipher = CryptoJS.AES.encrypt(string, cryptKey, cryptoOptions)
return CryptoJS.enc.Hex.stringify(cipher.ciphertext)
}
/**
* decrypt
* #param {*} string
* #returns {string}
*/
function aesDecrypt(string = '') {
const decipher = CryptoJS.AES.decrypt(
// make it params but not string that works.
{ ciphertext: CryptoJS.enc.Hex.parse(string) },
cryptKey,
cryptoOptions
)
return CryptoJS.enc.Utf8.stringify(decipher)
}
export { aesEncrypt, aesDecrypt }
It doesnt need Buffer or crypto module that in nodejs environment.
I am trying to encrypt Credit Card data of customer to AES 256 CBC format but anytime I I call the API I get this error:
RangeError [ERR_HTTP_INVALID_STATUS_CODE]: Invalid status code: ERR_INVALID_ARG_TYPE
at ServerResponse.writeHead (_http_server.js:259:11)
at ServerResponse._implicitHeader (_http_server.js:250:8)
This is my code for the encryption:
const crypto = require('crypto');
const encryptionType = 'aes-256-cbc';
const encryptionEncoding = 'base64';
const bufferEncryption = 'utf8';
export class AesEncryption {
AesKey: string;
AesIV: string;
init() {
this.AesKey = process.env.SECRET as string;
this.AesIV = process.env.SECRET?.slice(0, 16) as string;
}
encrypt(jsonObject: Object): string {
const val = JSON.stringify(jsonObject);
const key = Buffer.from(this.AesKey, bufferEncryption);
const iv = Buffer.from(this.AesIV, bufferEncryption);
const cipher = crypto.createCipheriv(encryptionType, key, iv);
let encrypted = cipher.update(val, bufferEncryption, encryptionEncoding);
encrypted += cipher.final(encryptionEncoding);
return encrypted;
}
}
This is the code of where I am using it:
public async createPayment(data: IPaymentDetails): Promise<IPaymentDetails> {
try {
PaymentService.checkPaymentRequiredFields(data);
data.encryptedData = new AesEncryption().encrypt(data.card)
console.log(data.encryptedData)
...
headers: {
'Content-Type': 'application/json',
Cookie: 'PHPSESSID=7hnkto3se3mlsbnht755ok2ak6',
},
data: JSON.stringify({
merchantId: data.merchantId,
method: 'Card',
id: data.trans_id,
encryptedData: data.encryptedData,
}),
})
Anytime I call the API I get the above error.
The problem is because the key and iv from process.env is not updating correctly, so it was throwing undefined.
I have to pass the process.env.SECRET directly into the function instead of passing them inside a variable.
It worked.
I am using #aws-crypto/client-node npm module to encrypt decrypt file using KMS key.
but when I run the following code.
I get error 'Missing credentials in config'
const {
KmsKeyringNode,
encrypt,
decrypt
} = require("#aws-crypto/client-node");
const encryptData = async (plainText, context) => {
try {
const {
result
} = await encrypt(keyring, plainText, {
encryptionContext: context
});
return result;
} catch (e) {
console.log(e);
}
};
encryptData('hello world', {
stage: "test",
purpose: "poc",
origin: "us-east-1"
})
I can see couple of issues with this code:
You are trying to import encrypt and decrypt functions directly from the module. This is not how aws-crypto works. You need to use build client to create instance which will hold these methods.
You are using keyring variable, but keyring is never declared? You need to create a keyring using .KmsKeyringNode method.
In order to properly use AWS/KMS to encrypt and decrypt data, take a look into the example bellow. (Make a note that this example does not use a context for its simplicity, nor additional keys which you can add. Also this example assumes that you are using same key for encryption and decryption as well)
const {
AMAZON_ENCRYPTION_KEY_ARN
} = process.env;
const awsCrypto = require('#aws-crypto/client-node');
const awsEncryptionClient = awsCrypto.buildClient(
awsCrypto.CommitmentPolicy.REQUIRE_ENCRYPT_REQUIRE_DECRYPT
);
const keyring = new awsCrypto.KmsKeyringNode({
generatorKeyId: AMAZON_ENCRYPTION_KEY_ARN
});
const encrypt = async (data) => {
try {
const { result } = await awsEncryptionClient.encrypt(keyring, data);
return result.toString('base64');
}
catch(err) {
console.log('Encryption error: ', err);
throw err;
}
};
const decrypt = async (encryptedData) => {
try {
const encryptedBuffer = Buffer.from(encryptedData, 'base64');
const { plaintext } = await awsEncryptionClient.decrypt(keyring, encryptedBuffer);
return plaintext.toString('utf8');
}
catch(err) {
console.log('Decryption error: ', err);
throw err;
}
};
module.exports = {
encrypt,
decrypt
};
You can create a file using code above and invoke functions by importing this file somewhere else. You will need to add encryption/decryption key arn. Beside encryption and decryption, encoding and decoding to base64 is added, so final result is suitable for storage (database for example)
For additional code examples take a look here.
I am trying to use the examples provided by AWS KMS team for the client-node encryption in #aws-crypto to encrypt and decrypt the files in the node js with AWS KMS.
**AWS KMS TEAM EXAMPLE **
import {
KmsKeyringNode,
decryptStream,
encryptStream,
MessageHeader // eslint-disable-line no-unused-vars
} from '#aws-crypto/client-node'
import { finished } from 'stream'
import { createReadStream } from 'fs'
import { promisify } from 'util'
const finishedAsync = promisify(finished)
export async function kmsStreamTest (filename: string) {
/* A KMS CMK is required to generate the data key.
* You need kms:GenerateDataKey permission on the CMK in generatorKeyId.
*/
const generatorKeyId = 'arn:aws:kms:us-west-2:658956600833:alias/EncryptDecrypt'
/* The KMS keyring must be configured with the desired CMKs */
const keyring = new KmsKeyringNode({ generatorKeyId })
/* Encryption context is a *very* powerful tool for controlling and managing access.
* It is ***not*** secret!
* Encrypted data is opaque.
* You can use an encryption context to assert things about the encrypted data.
* Just because you can decrypt something does not mean it is what you expect.
* For example, if you are are only expecting data from 'us-west-2',
* the origin can identify a malicious actor.
* See: https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/concepts.html#encryption-context
*/
const context = {
stage: 'demo',
purpose: 'simple demonstration app',
origin: 'us-west-2'
}
/* Create a simple pipeline to encrypt the package.json for this project. */
const stream = createReadStream(filename)
.pipe(encryptStream(keyring, { encryptionContext: context }))
.pipe(decryptStream(new KmsKeyringNode({ discovery: true })))
.on('MessageHeader', ({ encryptionContext }: MessageHeader) => {
/* Verify the encryption context.
* Depending on the Algorithm Suite, the `encryptionContext` _may_ contain additional values.
* In Signing Algorithm Suites the public verification key is serialized into the `encryptionContext`.
* Because the encryption context might contain additional key-value pairs,
* do not add a test that requires that all key-value pairs match.
* Instead, verify that the key-value pairs you expect match.
*/
Object
.entries(context)
.forEach(([key, value]) => {
console.log();''
if (encryptionContext[key] !== value) throw new Error('Encryption Context does not match expected values')
})
})
/* This is not strictly speaking part of the example.
* Streams need a place to drain.
* To test this code I just accumulate the stream.
* Then I can return that Buffer and verify.
* In a real world case you do not want to always buffer the whole stream.
*/
const buff: Buffer[] = []
stream.on('data', (chunk: Buffer) => {
buff.push(chunk)
})
await finishedAsync(stream)
return Buffer.concat(buff)
}
What I am stuck at is, how to separate the logic for encryption and decryption. here they are using Pipes and when I did something like :
** My Implementation **
const crypto = require('#aws-crypto/client-node');
const KmsKeyringNode = crypto.KmsKeyringNode;
const encryptStream = crypto.encryptStream;
const decryptStream = crypto.decryptStream;
const MessageHeader = crypto.MessageHeader;
const finished = require('stream');
const fs = require('fs');
const promisify = require('util');
const finishedAsync = promisify.promisify(finished)
kmsStreamTest = async (filename) => {
try {
const generatorKeyId = 'mykey'
/* The KMS keyring must be configured with the desired CMKs */
const keyring = new KmsKeyringNode({ generatorKeyId })
const context = {
stage: 'demo',
purpose: 'simple demonstration app',
origin: 'us-west-2'
}
/* Create a simple pipeline to encrypt the package.json for this project. */
const encryptedStream = fs.createReadStream(filename)
.pipe(encryptStream(keyring, { encryptionContext: context }))
const buff = []
encryptedStream.on('data', (chunk) => {
console.log(chunk.toString());
buff.push(chunk);
fs.writeFileSync(`${global.appRoot}/fileHandler/encrypted.json`, buff, { flag: 'wx' });
})
await finishedAsync(stream)
return Buffer.concat(buff)
}
catch (err) {
console.log("Errror----->>", Error);
}
}
kmsDecryptionTest =async (filename) => {
try {
const generatorKeyId = 'arn:aws:kms:us-east-2:362495994317:alias/data-key-encryptor';
/* The KMS keyring must be configured with the desired CMKs */
const keyring = new KmsKeyringNode({ generatorKeyId })
const context = {
stage: 'demo',
purpose: 'simple demonstration app',
origin: 'us-west-2'
}
const decryptedStream = fs.createReadStream(filename)
.pipe(decryptStream(new KmsKeyringNode({ discovery: true })))
const decryptedBuffer = []
decryptedStream.on('data', (chunk) => {
console.log("Decryption Buffer ------->".toString());
decryptedBuffer.push(chunk);
})
await finishedAsync(stream)
return Buffer.concat(buff)
}
catch (err) {
console.log("Error Data", err);
}
}
module.exports = { kmsStreamTest, kmsDecryptionTest }
When I try to call the encrypt function the file gets created with some gibberish text (as expected). But when I try to read that file and call the kmsDecryptionTest it gives me error as `malformed Header.
Can someone help me figure this out? why this is happening?
PS: My goal is to encrypt and decrypt a .zip file but right now I am just trying this on JSON.