【问题标题】:Can't encrypt/decrypt on PHP equivalent NodeJS crypto无法在 PHP 等效的 NodeJS 加密上加密/解密
【发布时间】:2018-07-04 14:36:01
【问题描述】:

我在 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 中正确解密。

提前致谢。

【问题讨论】:

  • AES with CTR 不应与 createCipher 一起使用(它会为相同的密钥生成相同的 iv),而是使用 createCipheriv() 并生成您自己的 iv(因此您将知道并可以在解密时使用它在 php 中)
  • 嗨,亚历克斯。是的,我明白这一点;问题是这个 NodeJS 代码来自第三方软件,我无权访问代码来修改它,但我必须使我的 PHP 脚本兼容。
  • @AlexK。我如何知道 createCipher 函数使用的给定密钥生成的 iv 是什么?
  • 它似乎不是documented,但是由于 iv 问题,相同的文档警告您不要使用此模式。如果您别无选择,则必须深入研究来源。
  • @EstebanM。在我的回答中将“cbc”更改为“ctr”可能就足够了:stackoverflow.com/a/27678978/1816580 如果是,请通知我。

标签: php node.js encryption cryptography


【解决方案1】:

你必须在两边都设置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));

【讨论】:

    【解决方案2】:

    感谢@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
    

    问候。

    【讨论】:

    • 请注意,只有一次迭代的evpKDF 在密码较短时是非常不安全的。在这种情况下,您应该使用至少 20 个字符的密码。
    猜你喜欢
    • 1970-01-01
    • 2019-06-26
    • 2019-08-19
    • 2013-03-24
    • 2014-04-21
    • 1970-01-01
    • 1970-01-01
    • 2019-03-06
    • 1970-01-01
    相关资源
    最近更新 更多