Image encryption using node.js crypto does not work - javascript

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()

Related

Node crypto error when decrypting file - 'Unsupported state or unable to authenticate data'

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()

(NODEJS) AES-256-GCM break pdf,gzip,png encoding after decryption

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.

Encrypt in CryptoJS (Angular) and do the same in Crypto (NodeJS)

I am having trouble encrypting a password in Angular vs NodeJS.
For example, the password I'm using is: test
In Angular I am using CryptoJS:
encryptUsingAES256(password) {
let _key = CryptoJS.enc.Utf8.parse("elservidordelgatotuerto88");
let _iv = CryptoJS.enc.Utf8.parse("elservidordelgatotuerto88");
let encrypted = CryptoJS.AES.encrypt(
password, _key, {
iv: _iv,
format: CryptoJS.format.Hex,
mode: CryptoJS.mode.CTR,
padding: CryptoJS.pad.Pkcs7
}).toString();
return encrypted;
}
The value I get from this function is: b75d0db663be668a24498aaa460f8896
Now, I also want to encrypt the same value in NodeJS using Crypto:
public encrypt(text) {
try {
var cipher = createCipher("aes-256-ctr", "elservidordelgatotuerto88");
var crypted = cipher.update(text,'utf8','hex');
crypted += cipher.final('hex');
return crypted;
} catch (error) {
throw new Error( 'couldn\'t encrypt text' );
}
}
And the value I get is different: 067e0c77
I want to obtain in Angular the same result that NodeJS gives me (067e0c77).
What could be the fixes that I should make in Angular?
Thank you!!
With a few changes we can make the output of the Crypto.js and Node crypto encryption identical.
We need to remove the padding in Crypto.js and also ensure that the key and iv are exactly the same.
The code will look like so:
const CryptoJS = require("crypto-js");
const crypto = require("crypto");
function encryptUsingAES256(password, key, iv) {
let _key = CryptoJS.enc.Utf8.parse(key);
let _iv = CryptoJS.enc.Utf8.parse(iv);
let encrypted = CryptoJS.AES.encrypt(
password, _key, {
iv: _iv,
format: CryptoJS.format.Hex,
mode: CryptoJS.mode.CTR,
padding: CryptoJS.pad.NoPadding
}).toString();
return encrypted;
}
function encryptUsingNodeCrypto(text, key, iv) {
try {
const cipher = crypto.createCipheriv("aes-256-ctr", key, iv);
let crypted = cipher.update(text,'utf8','hex');
crypted += cipher.final('hex');
return crypted;
} catch (error) {
console.error("encryptUsingNodeCrypto: An error occurred: ", error);
throw error;
}
}
const originalText = "test";
const key = "elservidordelgatelservidordelgat";
const iv = "elservidordelgat";
const encryptedTextCryptoJs = encryptUsingAES256(originalText, key, iv);
console.log(`Crypto.js: "${originalText}" was encrypted to "${encryptedTextCryptoJs}"`);
const encryptedTextNodeJs = encryptUsingNodeCrypto(originalText, key, iv);
console.log(`Node.js: "${originalText}" was encrypted to "${encryptedTextNodeJs}"`);
I'm getting identical results for the same input.

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