aes-256-cbc encryption decryption with initial vector in nodejs - php

I am trying to convert some existing php code into nodejs but node js code returns:
TypeError: Salt must be a buffer
I am using node version => v8.11.2
PHP Code :
class SecurityModel {
protected $key;
protected $method;
protected $data;
protected $iv;
function __construct($data, $key = 'testing', $method = 'AES-256-CBC',$InitialVector = "aw90rela942f65u2") {
$this->data = $data;
$this->key = $this->passwordDeriveBytes($key, null);
$this->method = $method;
$this->iv = $InitialVector;
}
function passwordDeriveBytes($password, $salt, $iterations = 100, $len = 32) {
$key = $password . $salt;
for($i = 0; $i < $iterations; $i++) {
$key = sha1($key, true);
}
if (strlen($key) < $len) {
$hx = $this->passwordDeriveBytes($password, $salt, $iterations - 1, 20);
$counter = 0;
while (strlen($key) < $len) {
$counter += 1;
$key .= sha1($counter . $hx, true);
}
}
return substr($key, 0, $len);
}
function encrypt(): string {
return openssl_encrypt($this->data, "aes-256-cbc", $this->key, 0, $this->iv);
}
function decrypt(): string {
return openssl_decrypt($this->data, "aes-256-cbc", $this->key, 0, $this->iv);
}
}
$objSecurityModel = new SecurityModel('437217');
$Encrypted = $objSecurityModel->encrypt();
echo "Encrypted :".$Encrypted ."<br>"; //returns-->C9xJGa03dRQx9ePm0nLnHg==
$objSecurityModel = new SecurityModel($Encrypted);
echo "Decrypted::".$objSecurityModel->decrypt(); //returns-->437217
I tried some what in nodejs
NodeJs Code :
const express = require('express');
const app = express();
var crypto = require('crypto');
key = 'testing'
plaintext = '437217'
iv = 'aw90rela942f65u2'
crypto.pbkdf2('testing', null, 100, 32, 'AES-256-CBC', (err, derivedKey) => {
if (err) throw err;
console.log(derivedKey.toString('hex')); // '3745e48...08d59ae'
key = derivedKey.toString('hex');
});
cipher = crypto.createCipheriv('aes-256-cbc', key,iv)
decipher = crypto.createDecipheriv('aes-256-cbc', key,iv);
var encryptedPassword = cipher.update(plaintext, 'utf8', 'base64');
encryptedPassword += cipher.final('base64')
var decryptedPassword = decipher.update(encryptedPassword, 'base64', 'utf8');
decryptedPassword += decipher.final('utf8');
console.log('original :', plaintext);
console.log('encrypted :', encryptedPassword);
console.log('decrypted :', decryptedPassword);
//PORT
const port = process.env.PORT || 3000;
app.listen(port,() => console.log(`Listening on port ${port}....`));

PBKDF2 is a great idea and is what the PHP code should have done in the first place. Unfortunately what happens inside passwordDeriveBytes() is nowhere near PBKDF2. You need to reproduce the looping like what happens inside passwordDeriveBytes() if you want to match it.
Oh and "Salt must be a buffer" is solved by converting the IV to a Buffer with Buffer.from(iv) (that is also a sign that a good IV should not be a string but random bytes).
const crypto = require('crypto');
function sha1(input) {
return crypto.createHash('sha1').update(input).digest();
}
function passwordDeriveBytes(password, salt, iterations, len) {
var key = Buffer.from(password + salt);
for(var i = 0; i < iterations; i++) {
key = sha1(key);
}
if (key.length < len) {
var hx = passwordDeriveBytes(password, salt, iterations - 1, 20);
for (var counter = 1; key.length < len; ++counter) {
key = Buffer.concat([key, sha1(Buffer.concat([Buffer.from(counter.toString()), hx]))]);
}
}
return Buffer.alloc(len, key);
}
var password = 'testing';
var plaintext = '437217';
var iv = 'aw90rela942f65u2';
//var key = crypto.pbkdf2Sync(password, '', 100, 32, 'sha1'); // How it should be
var key = passwordDeriveBytes(password, '', 100, 32); // How it is
console.log(key.toString('hex'));
var cipher = crypto.createCipheriv('aes-256-cbc', key, Buffer.from(iv));
var decipher = crypto.createDecipheriv('aes-256-cbc', key, Buffer.from(iv));
var part1 = cipher.update(plaintext, 'utf8');
var part2 = cipher.final();
var encrypted = Buffer.concat([part1, part2]).toString('base64');
var decrypted = decipher.update(encrypted, 'base64', 'utf8');
decrypted += decipher.final();
console.log('original :', plaintext);
console.log('encrypted :', encrypted);
console.log('decrypted :', decrypted);
Output:
df07df624db35d0bcf5fe7ff2dfdfffcef93f098939d750ca55595ae1b33925d
original : 437217
encrypted : C9xJGa03dRQx9ePm0nLnHg==
decrypted : 437217

Related

Converting php aes256 decrypt function to typescript gives

I have a PHP aes256 decrypt function I am trying to convert to typescript in ionic 5 but I am faced with the following error using md5:
This expression is not callable. Type 'any[]' has no call
signatures.
PHP decrypt function:
function decrypt(string $jsonStr, string $passphrase)
{
$json = json_decode($jsonStr, true);
$salt = hex2bin($json["s"]);
$iv = hex2bin($json["iv"]);
$ct = base64_decode($json["ct"]);
$concatedPassphrase = $passphrase . $salt;
$md5 = [];
$md5[0] = md5($concatedPassphrase, true);
$result = $md5[0];
for ($i = 1; $i < 3; $i++) {
$md5[$i] = md5($md5[$i - 1] . $concatedPassphrase, true);
$result .= $md5[$i];
}
$key = substr($result, 0, 32);
$data = openssl_decrypt($ct, 'aes-256-cbc', $key, true, $iv);
return json_decode($data, true);
}
Typescript converted:
async dcrypt(data: string, passphrase: string){
const json = JSON.parse(data);
const salt = decode(json["s"]);
const iv = decode(json["iv"]);
const ct = decode(json["ct"]);
const concatedPassphrase = passphrase + salt;
const md5 = [];
md5[0] = md5(concatedPassphrase, true);
let result = md5[0];
for (let i = 1; i < 3; i++) {
md5[i] = md5(md5[i - 1] + concatedPassphrase, true);
result += md5[i];
}
const key = result.substr(0, 32);
const importedKey = await window.crypto.subtle.importKey(
'raw',
new Uint8Array(key),
{
name: 'AES-CBC'
},
false,
['decrypt']
);
const data = await window.crypto.subtle.decrypt(
{
name: 'AES-CBC',
iv: new Uint8Array(iv)
},
importedKey,
new Uint8Array(ct)
);
return JSON.parse(new TextDecoder().decode(data));
}
Both md5 functions: md5(concatedPassphrase, true) && md5(md5[i - 1] + concatedPassphrase, true) are giving me the following error:
This expression is not callable. Type 'any[]' has no call
signatures.
How can I resolve this issue using md5?

Can't encrypt/decrypt on PHP equivalent NodeJS crypto

I'm having some trouble to get work an AES-256-CTR encrypt/decrypt in PHP, having a previosly encrypted string made with NodeJS crypto.
Here my JS code:
var crypto = require('crypto'),
algorithm = 'aes-256-ctr',
password = 'd6F3Efeq';
function encrypt(text){
var cipher = crypto.createCipher(algorithm,password)
var crypted = cipher.update(text,'utf8','hex')
crypted += cipher.final('hex');
return crypted;
}
function decrypt(text){
var decipher = crypto.createDecipher(algorithm,password)
var dec = decipher.update(text,'hex','utf8')
dec += decipher.final('utf8');
return dec;
}
Here my PHP code:
$text = "pereira";
echo bin2hex(openssl_encrypt($text, "aes-256-ctr", "d6F3Efeq",OPENSSL_RAW_DATA));
The JS's version return this on encrypt:
148bc695286379
The PHP's version return this on my encrypt test:
2f2ad5bb09fb56
Am I missing something here? Obviosly, I neither can decrypt correctly in PHP.
Thanks in advance.
You must set iv (initialization vector) in both side.
Node JS code:
var crypto = require('crypto'),
password = '1234567890abcdef1234567890abcdef',
iv = '1234567890abcdef',
text = "pereira";
function encrypt(iv, text, password){
var cipher = crypto.createCipheriv('aes-256-ctr', password, iv)
var crypted = cipher.update(text,'utf8','hex')
crypted += cipher.final('hex');
return crypted;
}
function decrypt(iv, text, password){
var decipher = crypto.createDecipheriv('aes-256-ctr', password, iv)
var dec = decipher.update(text,'hex','utf8')
dec += decipher.final('utf8');
return dec;
}
console.log(encrypt(iv, text, password));
And PHP code:
$text = 'pereira';
$algorithm = 'aes-256-ctr';
$password = '1234567890abcdef1234567890abcdef'; //node js required 32 byte length key
$iv = '1234567890abcdef'; //must be 16 byte length
echo bin2hex(openssl_encrypt($text, $algorithm, $password, OPENSSL_RAW_DATA, $iv));
Finally I got a solution for my question, thanks to #Alex K and #Artjom B.
As I mention in a comment, I must get a PHP encrypt/descrypt compatible with the NodeJS createCipher because the NodeJS application is a thrid party one.
So, the JS code is like this:
var crypto = require('crypto'),
algorithm = 'aes-256-ctr',
password = 'd6F3Efeq';
function encrypt(text){
var cipher = crypto.createCipher(algorithm,password)
var crypted = cipher.update(text,'utf8','hex')
crypted += cipher.final('hex');
return crypted;
}
function decrypt(text){
var decipher = crypto.createDecipher(algorithm,password)
var dec = decipher.update(text,'hex','utf8')
dec += decipher.final('utf8');
return dec;
}
I wrote a little PHP class to achieve this encrypt/decrypt job (Based on #Artjon's solution):
class Crypto
{
const METHOD = 'aes-256-ctr';
public function encrypt($plaintext, $password, $salt='', $encode = false)
{
$keyAndIV = self::evpKDF($password, $salt);
$ciphertext = openssl_encrypt(
$plaintext,
self::METHOD,
$keyAndIV["key"],
OPENSSL_RAW_DATA,
$keyAndIV["iv"]
);
$ciphertext = bin2hex($ciphertext);
if ($encode)
{
$ciphertext = base64_encode($ciphertext);
}
return $ciphertext;
}
public function decrypt($ciphertext, $password, $salt='', $encoded = false)
{
if ( $encoded )
{
$ciphertext = base64_decode($ciphertext, true);
if ($ciphertext === false)
{
throw new Exception('Encryption failure');
}
}
$ciphertext = hex2bin($ciphertext);
$keyAndIV = self::evpKDF($password, $salt);
$plaintext = openssl_decrypt(
$ciphertext,
self::METHOD,
$keyAndIV["key"],
OPENSSL_RAW_DATA,
$keyAndIV["iv"]
);
return $plaintext;
}
public function evpKDF($password, $salt, $keySize = 8, $ivSize = 4, $iterations = 1, $hashAlgorithm = "md5")
{
$targetKeySize = $keySize + $ivSize;
$derivedBytes = "";
$numberOfDerivedWords = 0;
$block = NULL;
$hasher = hash_init($hashAlgorithm);
while ($numberOfDerivedWords < $targetKeySize)
{
if ($block != NULL)
{
hash_update($hasher, $block);
}
hash_update($hasher, $password);
hash_update($hasher, $salt);
$block = hash_final($hasher, TRUE);
$hasher = hash_init($hashAlgorithm);
// Iterations
for ($i = 1; $i < $iterations; $i++)
{
hash_update($hasher, $block);
$block = hash_final($hasher, TRUE);
$hasher = hash_init($hashAlgorithm);
}
$derivedBytes .= substr($block, 0, min(strlen($block), ($targetKeySize - $numberOfDerivedWords) * 4));
$numberOfDerivedWords += strlen($block)/4;
}
return array(
"key" => substr($derivedBytes, 0, $keySize * 4),
"iv" => substr($derivedBytes, $keySize * 4, $ivSize * 4)
);
}
}
According to the NodeJS documentation, It derives the IV from the password with no salt, so I used an empty string as salt.
I'm using this PHP class in CodeIgniter like this:
$this->load->library("Crypto");
$plain_text = "pereira";
$password = "d6F3Efeq";
$enc_text = $this->crypto->encrypt($plain_text,$password);
$pla_text = $this->crypto->decrypt($enc_text,$password);
echo $enc_text."<br>";
echo $pla_text;
Both of them (NodeJS and PHP) return the same result for encrypt and decrypt function:
pereira = 148bc695286379
Regards.

How to send a SecretKey from Java to PHP ? (Using of Android Studio & PhpStorm)

How can I send the data of a SecretKeySpec or SecretKey from Java to PHP? When I convert it to String (to encrypt with RSA and post to PHP), I get some diamond with a question mark in it.
I tried to use utf8_decode but this problem is still remains.
EDIT:
I generated RSA Keypair in PHP, and sent the Public Key to Java. In Java I want to generate an AES_Key and encrypt a plaintext using AES, and send an encrypted plaintext and AES_Key (that is encrypted with RSA_Public_Key) from Java to PHP.
The following codes represent the AES Encryption/Decryption steps of this project:
Java:
public static class ApiCrypter {
private String iv = "0000000000000000";
private byte[] secretkey; //="mysecretkeyyyyyy";
private IvParameterSpec ivspec;
private SecretKeySpec keyspec;
private Cipher cipher;
public ApiCrypter(byte[] key)
{
secretkey=key;
ivspec = new IvParameterSpec(iv.getBytes());
keyspec = new SecretKeySpec(secretkey, "AES");
try {
cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} catch (NoSuchPaddingException e) {
e.printStackTrace();
}
}
public byte[] encrypt(String text) throws Exception
{
if(text == null || text.length() == 0) {
throw new Exception("Empty string");
}
byte[] encrypted = null;
try {
cipher.init(Cipher.ENCRYPT_MODE, keyspec, ivspec);
encrypted = cipher.doFinal(text.getBytes("UTF-8"));
}
catch (Exception e) {
throw new Exception("[encrypt] " + e.getMessage());
}
return encrypted;
}
public byte[] decrypt(String code) throws Exception
{
if(code == null || code.length() == 0) {
throw new Exception("Empty string");
}
byte[] decrypted = null;
try {
cipher.init(Cipher.DECRYPT_MODE, keyspec, ivspec);
decrypted = cipher.doFinal(hexToBytes(code));
}
catch (Exception e) {
throw new Exception("[decrypt] " + e.getMessage());
}
return decrypted;
}
public static String bytesToHex(byte[] data)
{
if (data==null) {
return null;
}
int len = data.length;
String str = "";
for (int i=0; i<len; i++) {
if ((data[i]&0xFF)<16) {
str = str + "0" + java.lang.Integer.toHexString(data[i]&0xFF);
}
else {
str = str + java.lang.Integer.toHexString(data[i]&0xFF);
}
}
return str;
}
public static byte[] hexToBytes(String str) {
if (str==null) {
return null;
}
else if (str.length() < 2) {
return null;
}
else {
int len = str.length() / 2;
byte[] buffer = new byte[len];
for (int i=0; i<len; i++) {
buffer[i] = (byte) Integer.parseInt(str.substring(i*2,i*2+2),16);
}
return buffer;
}
}
}
PHP:
<?php
class ApiCrypter
{
private $iv = '0000000000000000';
private $key;// = '89432hjfsd891787';
public function __construct($keyy)
{
$this->key = $keyy;
}
public function encrypt($str)
{
$encrypted = openssl_encrypt($str, 'AES-128-CBC', $this->key, 0, $this->iv);//
return bin2hex($encrypted);
}
public function decrypt($code)
{
$code = $this->hex2bin($code);
$decrypted = openssl_decrypt($code, 'AES-128-CBC', $this->key, 0, $this->iv);
$ut = utf8_encode(trim($decrypted));
// return $this->pkcs5_unpad($ut);
return $ut;
}
protected function hex2bin($hexdata)
{
$bindata = '';
for ($i = 0; $i < strlen($hexdata); $i += 2) {
$bindata .= chr(hexdec(substr($hexdata, $i, 2)));
}
return $bindata;
}
protected function pkcs5_pad($text)
{
$blocksize = 16;
$pad = $blocksize - (strlen($text) % $blocksize);
return $text . str_repeat(chr($pad), $pad);
}
protected function pkcs5_unpad($text)
{
$pad = ord($text{strlen($text) - 1});
if ($pad > strlen($text)) {
return false;
}
if (strspn($text, chr($pad), strlen($text) - $pad) != $pad) {
return false;
}
return substr($text, 0, -1 * $pad);
}
}
?>
Generating a secretKey (as AES key), encryption of the plaintext(name) by AES key, Encrypt AES key, and preparing them for sending to PHP:
SecureRandom secureRandom = new SecureRandom();
KeyGenerator keyGenerator;
keyGenerator = KeyGenerator.getInstance("AES");
keyGenerator.init(256, secureRandom);
SecretKey key = keyGenerator.generateKey();
secret_key = key.getEncoded();
Byte[] encodedSessionKey = RSA.encryptByPublicKey(secret_key, public_key);
ApiCrypter apiCrypter=new ApiCrypter(encodedSessionKey);
String encryptedName = ApiCrypter.bytesToHex(apiCrypter.encrypt(nameStr));
String encodedStr = Base64.encodeToString(encodedSessionKey, Base64.DEFAULT);
AsyncDataClass asyncRequestObject = new AsyncDataClass();
asyncRequestObject.execute(serverUrl,encodedStr,encryptedName);
PHP code to decrypt AES key using of the RSA private key and decryption of the NameString by decrypted AES key:
$encodedStr = "";
$encryptedName = "";
if(isset($_POST['encodedStr'])){
$encodedSecretKey = $_POST['encodedStr'];
}
if(isset($_POST['encryptedName'])){
$encryptedName = $_POST['encryptedName'];
}
$rsa = new \phpseclib\Crypt\RSA();
$rsa->setHash('sha1');
$rsa->setMGFHash('sha1');
$rsa->setEncryptionMode(1);
$rsa->loadKey(file_get_contents('private.key')); // RSA Private key
$AES_session_key = $rsa->decrypt(base64_decode($encodedSecretKey));
$ApiCrypter= new ApiCrypter($AES_session_key);
$decryptedName = $ApiCrypter->decrypt($encryptedName);
Are these classes match to each other?
How to encrypt a plaintext in Java using of the AES_Key (or a copy of a SecretKey that is generated in Java)?
How to decrypt a plaintext in PHP, using the AES_key(that is decrypted by RSA Private Key) ?
A secret key consists of random bytes. That means that - whatever encoding you use - that some bytes won't print. To represent bytes as text usually you use hexadecimal or base64 encoding. The difference between those two is that a normal person can make out the byte values from hexadecimals while base64 is more efficient (as it uses more characters and therefore can encode 6 bits per character where hexadecimals only encodes 4 bits per character).
Within Java 8 only base 64 is supported by default:
System.out.println(Base64.getEncoder().encodeToString(secretKey.getEncoded()));
and for Android:
Base64.encodeToString(secretKey.getEncoded(),Base64.DEFAULT);
should work fine (for SecretKeySpec as well, as it implements SecretKey).
Of course sending an unprotected secret key should be performed in a secure environment and should in general be avoided at all costs.

Decrypting string encrypted in C# with RijndaelManaged class using PHP

Here's some C# code (I've modified it slightly to modify some of the hard coded values in it):
public static string Decrypt(string InputFile)
{
string outstr = null;
if ((InputFile != null))
{
if (File.Exists(InputFile))
{
FileStream fsIn = null;
CryptoStream cstream = null;
try
{
byte[] _b = { 94, 120, 102, 204, 199, 246, 243, 104, 185, 115, 76, 48, 220, 182, 112, 101 };
fsIn = File.Open(InputFile, FileMode.Open, System.IO.FileAccess.Read);
SymmetricAlgorithm symm = new RijndaelManaged();
PasswordDeriveBytes Key = new PasswordDeriveBytes(System.Environment.MachineName, System.Text.Encoding.Default.GetBytes("G:MFX62rlABW:IUYAX(i"));
ICryptoTransform transform = symm.CreateDecryptor(Key.GetBytes(24), _b);
cstream = new CryptoStream(fsIn, transform, CryptoStreamMode.Read);
StreamReader sr = new StreamReader(cstream);
char[] buff = new char[1000];
sr.Read(buff, 0, 1000);
outstr = new string(buff);
}
finally
{
if (cstream != null)
{
cstream.Close();
}
if (fsIn != null)
{
fsIn.Close();
}
}
}
}
return outstr;
}
I need to come up with a function to do the same in PHP. Bear in mind, I did not write the C# code and I cannot modify it, so even if it's bad, I'm stuck with it. I've searched all over and have found bits and pieces around, but nothing that works so far. All examples I've found use mcrypt, which seems to be frowned upon these days, but I'm probably stuck using it. Next, I found the following post which has some useful info: Rewrite Rijndael 256 C# Encryption Code in PHP
So looks like the PasswordDeriveBytes class is the key to this. I created the following PHP code to try to decrypt:
function PBKDF1($pass,$salt,$count,$dklen) {
$t = $pass.$salt;
//echo 'S||P: '.bin2hex($t).'<br/>';
$t = sha1($t, true);
//echo 'T1:' . bin2hex($t) . '<br/>';
for($i=2; $i <= $count; $i++) {
$t = sha1($t, true);
//echo 'T'.$i.':' . bin2hex($t) . '<br/>';
}
$t = substr($t,0,$dklen);
return $t;
}
$input = 'Ry5WdjGS8rpA9eA+iQ3aPw==';
$key = "win7x64";
$salt = implode(unpack('C*', "G:MFX62rlABW:IUYAX(i"));
$salt = pack("H*", $salt);
$it = 1000;
$keyLen = 16;
$key = PBKDF1($key, $salt, $it, $keyLen);
$key = bin2hex(substr($key, 0, 8));
$iv = bin2hex(substr($key, 8, 8));
echo trim(mcrypt_decrypt(MCRYPT_RIJNDAEL_256, $key, base64_decode($input), MCRYPT_MODE_CBC, $iv));
You'll note that for what I believe to be System.Environment.MachineName, I put in a fixed value for now which is the computer name of the machine I'm on, so should be equivalent of what the C# code is doing. Other than that, I've noticed that using MCRYPT_RIJNDAEL_256 doesn't work, it throws the error "The IV parameter must be as long as the blocksize". If I use MCRYPT_RIJNDAEL_128, I don't get that error, but decryption still fails. I assume I'm missing the piece for the byte array _b that's used by the CreateDecryptor function, I have no idea where that's supposed to fit in. Any help is appreciated.
UPDATE
This is the solution, which was made possible by the answer marked correct. Note that the code for the PBKDF1 function is not mine, it was linked to in the answer.
function PBKDF1($pass, $salt, $count, $cb) {
static $base;
static $extra;
static $extracount= 0;
static $hashno;
static $state = 0;
if ($state == 0)
{
$hashno = 0;
$state = 1;
$key = $pass . $salt;
$base = sha1($key, true);
for($i = 2; $i < $count; $i++)
{
$base = sha1($base, true);
}
}
$result = "";
if ($extracount > 0)
{
$rlen = strlen($extra) - $extracount;
if ($rlen >= $cb)
{
$result = substr($extra, $extracount, $cb);
if ($rlen > $cb)
{
$extracount += $cb;
}
else
{
$extra = null;
$extracount = 0;
}
return $result;
}
$result = substr($extra, $rlen, $rlen);
}
$current = "";
$clen = 0;
$remain = $cb - strlen($result);
while ($remain > $clen)
{
if ($hashno == 0)
{
$current = sha1($base, true);
}
else if ($hashno < 1000)
{
$n = sprintf("%d", $hashno);
$tmp = $n . $base;
$current .= sha1($tmp, true);
}
$hashno++;
$clen = strlen($current);
}
// $current now holds at least as many bytes as we need
$result .= substr($current, 0, $remain);
// Save any left over bytes for any future requests
if ($clen > $remain)
{
$extra = $current;
$extracount = $remain;
}
return $result;
}
$input = 'base 64 encoded string to decrypt here';
$key = strtoupper(gethostname());
$salt = 'G:MFX62rlABW:IUYAX(i';
$it = 100;
$keyLen = 24;
$key = PBKDF1($key, $salt, $it, $keyLen);
$iv = implode(array_map('chr', [94, 120, 102, 204, 199, 246, 243, 104, 185, 115, 76, 48, 220, 182, 112, 101]));
_b is a static value that is used as the IV (CreateDecryptor takes a key and IV parameter). Since it is 16 bytes long, this means that you're using Rijndael-128 or more commonly known AES.
Key.GetBytes(24) suggests that a 24 byte key is derived and not a 16 byte key.
Make sure that
System.Text.Encoding.Default is equivalent with implode(unpack('C*', ...,
Default value for iterations of PasswordDeriveBytes is 1000,
Default value for hash of PasswordDeriveBytes is SHA-1
Security problems:
PBKDF1 is obsolete and PBKDF2 isn't that much better. Use up-to-date key derivation algorithms like Argon2 or scrypt.
The IV must be randomly chosen to achieve semantic security. It doesn't have to be secret, so it can be sent along with the ciphertext.
Stretching a key by encoding it to hex doesn't provide any security (don't use bin2hex).
The ciphertext is not authenticated, which means that you cannot detect (malicious) manipulation of encrypted messages. Employ encrypt-then-MAC.

Openssl and Windows CryptoAPI compatibility issue

I have problem in encrypted communication between windows application and server app. Client app is running at Windows, written in C and uses CryptoAPI. Server application uses PHP and Openssl extension. As cipher algorithm AES-256-CBC was chosen. Using the same algorithm Openssl and CryptoAPI produces different results. The same problem I had with RC2-CBC algorithm. This online tool http://asecuritysite.com/Encryption/openssl generates the same result as openssl, so I conclude that bug in C code.
PHP code:
<?php
//$flag = OPENSSL_RAW_DATA;
$flag = false;
//this string will encode
$dataString = 'some data string';
$pass = "1234567812345678";
$method = "aes-256-cbc";
$iv = "Zievrs8NZievrs8N";
echo "original:\n";
var_dump($dataString);
$encryptedMessage = openssl_encrypt($dataString, $method, $pass, $flag, $iv);
echo "after encrypt:\n";
var_dump($encryptedMessage);
echo "vector:\n";
var_dump($iv);
$decryptedMessage = openssl_decrypt($encryptedMessage, $method, $pass, $flag, $iv);
echo "after decrypt:\n";
var_dump($decryptedMessage);
Output:
original:
string(16) "some data string"
after encrypt:
string(44) "9O8UAaRRCfneeRbRCeiYi9nOM8F2KA6gtkAsvPliUdA="
vector:
string(16) "Zievrs8NZievrs8N"
after decrypt:
string(16) "some data string"
C code:
BOOL SetKey(BYTE* szKey, DWORD dwKeySize, HCRYPTPROV* m_hProv, HCRYPTHASH* m_hHash, HCRYPTKEY* m_hKey)
{
BOOL m_fOK= TRUE;
if (*m_hProv == 0) {
m_fOK = CryptAcquireContextA(m_hProv, NULL,
NULL, //MS_DEF_PROV_A,
PROV_RSA_AES,
CRYPT_VERIFYCONTEXT
);
}
if (m_fOK && (*m_hHash != 0)) {
m_fOK = CryptDestroyHash(*m_hHash);
m_hHash = 0;
}
if (m_fOK && (*m_hHash == 0)) {
m_fOK = CryptCreateHash(*m_hProv, CALG_SHA_256, 0, 0, m_hHash);
}
if (m_fOK) {
m_fOK = CryptHashData(*m_hHash, (BYTE*)szKey, dwKeySize, 0);
}
if (m_fOK) {
m_fOK = CryptDeriveKey(*m_hProv, CALG_AES_256, *m_hHash, CRYPT_EXPORTABLE | CRYPT_NO_SALT, m_hKey);
}
if (m_fOK) {
DWORD mode = CRYPT_MODE_CBC;
m_fOK = CryptSetKeyParam(*m_hKey, KP_MODE, (BYTE*)&mode, 0);
}
if (m_fOK) {
BYTE iv[] = {'Z','i','e','v','r','s','8','N','Z','i','e','v','r','s','8','N',0};
m_fOK = CryptSetKeyParam(*m_hKey, KP_IV, (BYTE*)iv, 0);
}
return m_fOK;
}
BOOL EncryptDecrypt(BYTE* pData, BYTE** pRes, DWORD* dwDataLen, BYTE* pKey, DWORD dwKeySize, BOOL fEncrypt)
{
HCRYPTPROV m_hProv = 0;
HCRYPTHASH m_hHash = 0;
HCRYPTKEY m_hKey = 0;
BOOL m_fOK= TRUE;
m_fOK = SetKey(pKey, dwKeySize, &m_hProv, &m_hHash, &m_hKey);
if (fEncrypt) {
DWORD dwTotalBufferSize = 0;
DWORD dwNewLen = *dwDataLen;
if((m_fOK = CryptEncrypt(m_hKey, 0, TRUE, 0, NULL, &dwNewLen, dwTotalBufferSize))) {
*pRes = (BYTE*)malloc(dwNewLen);
memcpy(*pRes, pData, *dwDataLen);
dwTotalBufferSize = dwNewLen;
if(!(m_fOK = CryptEncrypt(m_hKey, 0, TRUE, 0, *pRes, dwDataLen, dwTotalBufferSize))) {
free(*pRes);
*pRes = NULL;
*dwDataLen = 0;
}
}
}
else {
*pRes = (BYTE*)malloc(*dwDataLen);
memcpy(*pRes, pData, *dwDataLen);
if(!(m_fOK = CryptDecrypt(m_hKey, 0, TRUE, 0, *pRes, dwDataLen))) {
DWORD err = GetLastError();
char msg[100];
wsprintfA(msg, "err = %d\n", err);
OutputDebugStringA(msg);
free(*pRes);
*pRes = NULL;
*dwDataLen = 0;
}
}
if (m_hKey) CryptDestroyKey(m_hKey);
if (m_hHash) CryptDestroyHash(m_hHash);
if (m_hProv) CryptReleaseContext(m_hProv, 0);
return m_fOK;
}
void main() {
const char* data = "some data string";
BYTE* res = NULL;
DWORD len = strlen(data);
EncryptDecrypt((BYTE*)data, &res, &len, (BYTE*)"1234567812345678", 16, TRUE);
size_t len_en = 0;
char* base64 = base64_encode(res, len, &len_en);
printf("base64 = %s\n", base64);
}
Output:
base64 = miFMwk4/ZwjMLsnV4Po9UdWxix32TrK5BcSgSKYr384=
Encrypted output is different. It means that key which is ultimately used is different or data is different. But data is same, hence key must be different.
It means in the process of key generation, something is different. It might be possible that OpenSSL may be using some other key deriving function which is not visible here. Try to use some standard algorithm for key generation. Instead of hashing, try not to use hash.
I finally got proper results from CryptoAPI by importing key as PLAINTEXTBLOB.
Here is not final, but workable fixes to SetKey function.
#include <WinCrypt.h>
typedef struct {
BLOBHEADER hdr;
DWORD dwKeySize;
BYTE rgbKeyData[16];
} PLAINTEXTKEYBLOB_t;
BOOL SetKey(BYTE* szKey, DWORD dwKeySize, HCRYPTPROV* m_hProv, HCRYPTHASH* m_hHash, HCRYPTKEY* m_hKey)
{
BOOL m_fOK= TRUE;
if (*m_hProv == 0) {
m_fOK = CryptAcquireContextW(m_hProv, NULL,
MS_ENH_RSA_AES_PROV, //MS_DEF_PROV_A,
PROV_RSA_AES,
CRYPT_NEWKEYSET | CRYPT_VERIFYCONTEXT
);
}
if (m_fOK && (*m_hHash != 0)) {
m_fOK = CryptDestroyHash(*m_hHash);
m_hHash = 0;
}
if(m_fOK) {
BYTE key[] = {0x06, 0xa9, 0x21, 0x40, 0x36, 0xb8, 0xa1, 0x5b, 0x51, 0x2e, 0x03, 0xd5, 0x34, 0x12, 0x00, 0x06};
PLAINTEXTKEYBLOB_t blob;
blob.hdr.bType = PLAINTEXTKEYBLOB;
blob.hdr.bVersion = 2;
blob.hdr.reserved = 0;
blob.hdr.aiKeyAlg = CALG_AES_128;
blob.dwKeySize = 16;
for(int i=0; i<16; i++) {
//blob.rgbKeyData[16-1-i] = key[i];
blob.rgbKeyData[i] = key[i];
}
m_fOK = CryptImportKey(*m_hProv, (BYTE*)&blob, sizeof(PLAINTEXTKEYBLOB_t), 0, NULL, m_hKey);
}
if (m_fOK) {
DWORD mode = CRYPT_MODE_CBC;
m_fOK = CryptSetKeyParam(*m_hKey, KP_MODE, (BYTE*)&mode, 0);
}
if (m_fOK) {
DWORD mode = 0;
DWORD dwDataLen = sizeof(mode);
m_fOK = CryptGetKeyParam(*m_hKey, KP_PADDING, (BYTE*)&mode, &dwDataLen, 0);
mode = 0;
//m_fOK = CryptSetKeyParam(*m_hKey, KP_PADDING, (BYTE*)&mode, 0);
}
if (m_fOK) {
BYTE iv[] = {0x3d, 0xaf, 0xba, 0x42, 0x9d, 0x9e, 0xb4, 0x30, 0xb4, 0x22, 0xda, 0x80, 0x2c, 0x9f, 0xac, 0x41};
m_fOK = CryptSetKeyParam(*m_hKey, KP_IV, (BYTE*)iv, 0);
}
return m_fOK;
}

Categories