无法在PHP等效的NodeJS加密上加密/解密

时间:2018-01-25 11:00:54

标签: php node.js encryption cryptography

我在PHP中使用AES-256-CTR加密/解密工作时遇到了一些麻烦,使用NodeJS加密创建了一个普遍加密的字符串。

这是我的JS代码:

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

这是我的PHP代码:

$text = "pereira";

echo bin2hex(openssl_encrypt($text, "aes-256-ctr", "d6F3Efeq",OPENSSL_RAW_DATA));

JS的版本在加密时返回:

148bc695286379

PHP的版本在我的加密测试中返回:

2f2ad5bb09fb56

我在这里遗漏了什么吗?显而易见,我无法在PHP中正确解密。

提前致谢。

2 个答案:

答案 0 :(得分:4)

您必须在两侧设置iv(初始化向量)。

节点JS代码:

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

和PHP代码:

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

答案 1 :(得分:1)

最后,我得到了一个问题的解决方案,感谢@Alex K和@Artjom B.

正如我在评论中提到的,我必须获得与NodeJS createCipher兼容的PHP加密/解密,因为NodeJS应用程序是第三方。

所以,JS代码是这样的:

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

我写了一个小PHP类来实现这个加密/解密工作(基于@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)
                   );
   }
}

根据NodeJS文档,它从没有盐的密码中导出IV,所以我使用空字符串作为盐。

我在CodeIgniter中使用这个PHP类,如下所示:

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

它们(NodeJS和PHP)都为加密和解密函数返回相同的结果:

pereira = 148bc695286379

问候。