Esteban M.
Esteban M.

Reputation: 174

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.

Upvotes: 2

Views: 4021

Answers (2)

Esteban M.
Esteban M.

Reputation: 174

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.

Upvotes: 4

Ermat Kiyomov
Ermat Kiyomov

Reputation: 151

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

Upvotes: 4

Related Questions