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'),
};
};
Related
Iam using node crypto to encrypt files but it gives me error - "Unsupported state or unable to authenticate data"
Iam using AuthTag or else it just decrypts to random text. I have not used encoding cause i wanted to encrypt all types of files txt,png,md,etc
const app = {
encrypt(data, password) {
const salt = crypto.randomBytes(16);
const key = crypto.scryptSync(password, salt, 32, { N: 16384 });
const cipher = crypto.createCipheriv('aes-256-gcm', key, salt)
const encryptedData = Buffer.concat([cipher.update(data), cipher.final()]);
const authTag = cipher.getAuthTag();
return salt+authTag+encryptedData;
},
decrypt(data, password) {
const salt = data.slice(0,16);
const authTag = data.slice(16,32);
const encData = data.slice(32,data.length);
const key = crypto.scryptSync(password, salt, 32, { N: 16384 });
const decipher = crypto.createDecipheriv('aes-256-gcm', key, salt);
decipher.setAuthTag(authTag);
const plainText = Buffer.concat([decipher.update(encData), decipher.final()]);
return plainText;
}
}
const fileLoc = './sample.txt';
const password = 'password';
const file = {
encrypt() {
fs.readFile(fileLoc, (err, data) => {
if (err) return console.log('File not found.');
if (data) {
const cipherText = app.encrypt(data,password);
fs.writeFileSync(fileLoc, cipherText);
console.log('Done');
}
})
},
decrypt() {
fs.readFile(fileLoc, (err, data) => {
if (err) console.log('File not found.')
if (data) {
const plainText = app.decrypt(data,password);
fs.writeFileSync(fileLoc, plainText);
console.log('Done');
}
})
}
}
file.encrypt()
file.decrypt()
I created an encryption app using node.js crypto. But the encryption/decryption seems to only work on .txt files. I encrypted an image file, but when i decrypted it, it doesnt return the original image.
const crypto = require("crypto");
const fs = require("fs");
//
function encrypt(text, password) {
let salt = crypto.randomBytes(16).toString("hex");
let key = crypto.scryptSync(password, salt, 16, { N: 16384 });
key = key.toString("hex");
const cipher = crypto.createCipheriv("aes-256-gcm", key, salt);
const encrypted = cipher.update(text, "utf8", "hex") + cipher.final("hex");
const tag = cipher.getAuthTag().toString("hex");
return salt + tag + encrypted;
}
function decrypt(text, password) {
const salt = text.substr(0, 32);
const tag = Buffer.from(text.substr(32, 32), "hex");
const ciphertext = text.substr(64, text.length);
let key = crypto.scryptSync(password, salt, 16, { N: 16384 });
key = key.toString("hex");
const cipher = crypto.createDecipheriv("aes-256-gcm", key, salt);
cipher.setAuthTag(tag);
try {
let decrypted = cipher.update(ciphertext, "hex", "utf8") + cipher.final("utf8");
return decrypted;
} catch (e) {
return true;
}
}
//
function encryptFile(fileLocation, password) {
fs.readFile(fileLocation, "utf8", (err, data) => {
if (err) return console.log(err);
if (data) {
const encText = encrypt(data, password);
fs.writeFileSync(fileLocation, encText);
console.log("File Encrypted");
}
});
}
function decryptFile(fileLocation, password) {
fs.readFile(fileLocation, "utf8", (err, data) => {
if (err) {
throw err;
} else {
const decText = decrypt(data, password);
if (decText === true) {
console.log("Wrong Password/Salt");
} else {
fs.writeFileSync(fileLocation, decText);
console.log("File Decrypted");
}
}
});
}
//
const fileLocation = "./sample.txt";
// encryptFile(fileLocation, "password");
decryptFile(fileLocation, "password");
I did it on repl so you can have a look - https://replit.com/#sampauls8520/file-enc
I have provided an image file as well so that you can encrypt/decrypt that.
You can't read binary data (this includes images) using the UTF-8 text encoding, nor write them back.
You will need to rework your code so that it doesn't operate on text strings, but with binary buffers.
IOW:
don't specify an encoding for fs.readFile/fs.writeFile ("If no encoding is specified, then the raw buffer is returned." say the docs)
don't specify either input or output encoding for cipher.update()/cipher.final()
I've been working on a JavaScript project that includes a smart contract. Basically the user is able to upload a certificate, and the certificate will be hashed and the hash is stored on the blockchain.
JavaScript Code:
captureFile = (event) => {
event.preventDefault()
//Process file for IPFS
const file = event.target.files[0]
const reader = new window.FileReader()
reader.readAsArrayBuffer(file)
reader.onloadend = () => {
this.setState({buffer: Buffer(reader.result) })
}
}
onSubmit = (event) => {
event.preventDefault()
console.log("submitting the form...")
ipfs.add(this.state.buffer, (error, result) => {
console.log('ipfs result', result)
const certificateHash = result[0].hash
if (error) {
console.error(error)
return
}
this.state.contract.methods.set(certificateHash).send({ from: this.state.account }).then((r) => {
this.setState({ certificateHash })
})
})
}
Smart contract code:
pragma solidity >=0.4.21 <0.6.0;
contract Certificate {
string certificateHash;
//write function
function set(string memory _certificateHash) public {
certificateHash = _certificateHash;
}
//read fucntion
function get() public view returns (string memory){
return certificateHash;
}
}
For testing, I am using Truffle and Ganache. Up until this part, everything is fine. Whenever I upload the file, it is successfully detected by metamask and I am able to confirm the transaction. So I've decided to add 1 more step - Encrypting the file before uploading it into the block chain. I added the following code:
New JS file for encryption methods
const CryptoJS = require("crypto-js");
const passKeyword = CryptoJS.lib.WordArray.random(16);
const passFile = CryptoJS.lib.WordArray.random(16);
const salt = CryptoJS.lib.WordArray.random(16);
const iv = CryptoJS.lib.WordArray.random(16);
const keywordKey = CryptoJS.PBKDF2(passKeyword, salt, {
keySize: 256 / 32
});
const fileKey = CryptoJS.PBKDF2(passFile, salt, {
keySize: 256 / 32
})
var cryptoMethods = {
fileEncryption: function (fileBuffer) {
var toEncFile = CryptoJS.enc.Utf8.parse(fileBuffer);
var encryptedFile = CryptoJS.AES.encrypt(toEncFile, fileKey, {
iv: iv, //offset
mode: CryptoJS.mode.CBC, //encryption mode
padding: CryptoJS.pad.Pkcs7 //padding mode
})
var convEncFile = CryptoJS.enc.Utf8.parse(encryptedFile)
return cryptoMethods.convertWordArrayToUint8Array(convEncFile);
},
//Not yet tested
fileDecryption: function (fileCipher) {
var ciphertextStr = CryptoJS.enc.Base64.parse(fileCipher);
var toDecFile = CryptoJS.enc.Base64.stringify (ciphertextStr);
var decryptedFile = CryptoJS.AES.decrypt (toDecFile, fileKey, {
iv: iv, //offset
mode: CryptoJS.mode.CBC, //encryption mode
padding: CryptoJS.pad.Pkcs7 //padding mode
});
var decryptedFileString = decryptedFile.toString(CryptoJS.enc.Utf8);
return decryptedFileString.toString();
},
convertWordArrayToUint8Array: function (wordArray) {
var len = wordArray.words.length,
u8_array = new Uint8Array(len << 2),
offset = 0, word, i
;
for (i=0; i<len; i++) {
word = wordArray.words[i];
u8_array[offset++] = word >> 24;
u8_array[offset++] = (word >> 16) & 0xff;
u8_array[offset++] = (word >> 8) & 0xff;
u8_array[offset++] = word & 0xff;
}
return u8_array;
},
}
export default cryptoMethods;
Main App.js file:
captureFile = (event) => {
event.preventDefault()
//Process file for IPFS
const file = event.target.files[0]
const reader = new window.FileReader()
reader.readAsArrayBuffer(file)
reader.onloadend = () => {
this.setState({buffer: Buffer(reader.result) })
}
}
onSubmit = (event) => {
event.preventDefault()
console.log("submitting the form...")
var encFile = cryptographicMod.fileEncryption(this.state.buffer);
var convEncFile = new Uint8Array([])
convEncFile = toBuffer(encFile)
console.log(convEncFile)
ipfs.add(convEncFile, (error, result) => {
console.log('ipfs result', result)
const certificateHash = result[0].hash
if (error) {
console.error(error)
return
}
if (this.state.contract) {
this.state.contract.methods.set(certificateHash)
.send({from: this.state.account}).then((r) => {
this.setState({certificateHash})
window.alert('File submitted')
})
} else {
console.log('Contract state is empty')
}
})
}
After implementing the new code and trying to upload a file in the application, I get the following error:
MetaMask - RPC Error: TxGasUtil - Trying to call a function on a non-contract address & Uncaught (in promise) . Metamask is also unable to process the gas fee. Any help is welcomed, thanks!
I wish I had help because i don't know why my implementation of AES-GCM break file encoding.
I have an API that uses 1 function to encrypt/decrypt with AES-256-GCM. (With KEY=buffer of 32 random bytes)
Here is the function:
const aes256gcm = (key) => {
const ALGO = 'aes-256-gcm';
const encrypt = (str) => {
try {
const salt = crypto.randomBytes(64);
const iv = crypto.randomBytes(32);
let derivedkey = crypto.pbkdf2Sync(key, salt, 55000, 32, 'sha512');
const cipher = crypto.createCipheriv(ALGO, derivedkey, iv);
let encrypted = Buffer.concat([cipher.update(str), cipher.final()]);
const tag = cipher.getAuthTag();
let buffer = Buffer.concat([salt, iv, encrypted]);
encrypted = {
tag: tag,
buffer: buffer
}
return encrypted;
} catch (e) {
console.log(e);
}
};
const decrypt = (data, authTag) => {
try {
const salt = data.slice(0, 64);
const iv = data.slice(64, 96);
const text = data.slice(96, data.length);
authTag = new Buffer.from(authTag, 'base64');
let derivedkey = crypto.pbkdf2Sync(key, salt, 55000, 32, 'sha512');
let decipher = crypto.createDecipheriv(ALGO, derivedkey, iv);
decipher.setAuthTag(authTag);
let decrypted = decipher.update(text, 'binary') + decipher.final();
return decrypted;
} catch (e) {
console.log(e);
}
};
return {
encrypt,
decrypt
};
};
With this code i encrypt and write in file the result:
const aesCipher = aes.aes256gcm(aes.loadKey(path.resolve(__dirname, `key`)));
const encrypted = aesCipher.encrypt(file.data);
if (encrypted !== undefined) {
fs.writeFile(`${file.name}.enc`, encrypted.buffer, function (err) {
if (err) return console.log(err);
console.log(`${file.name}.enc successfully created`);
});
}
And finaly with this i decrypt and write the content in a file:
const aesCipher = aes.aes256gcm(aes.loadKey(path.resolve(__dirname, `key`)));
let filename = 'test1.gz';
let authTag = 'puI0FfV4Btiy7iPiZFbwew==';
let encrypted = fs.readFileSync(path.resolve(__dirname, `test1.gz.enc`));
const decrypted = aesCipher.decrypt(encrypted, authTag);
if (decrypted !== undefined) {
const file = fs.createWriteStream(filename);
file.write(new Buffer.from(decrypted, 'ascii'), function (err) {
if (err) return console.log(err);
console.log(`Successfully decrypted`);
file.close();
});
res.send({
status: true,
message: 'File is decrypted',
});
}
Diff of my input/output files :
Diff
So, what am i doing wrong ? Is my encryption process good ? Why this only work well with .txt files ?
Thanks you !
I think a fairly small change to your decrypt function should fix the issue, if you just update it to:
const decrypt = (data, authTag) => {
try {
const salt = data.slice(0, 64);
const iv = data.slice(64, 96);
const text = data.slice(96, data.length);
authTag = new Buffer.from(authTag, 'base64');
let derivedkey = crypto.pbkdf2Sync(key, salt, 55000, 32, 'sha512');
let decipher = crypto.createDecipheriv(ALGO, derivedkey, iv);
decipher.setAuthTag(authTag);
let decrypted = Buffer.concat([decipher.update(text), decipher.final()]);
return decrypted;
} catch (e) {
console.log(e);
}
};
I think the previous implementation was not concatenating the result correctly for non-text files.
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.