【问题标题】:c# MCRYPT_RIJNDAEL_256 Encryption Decryption Class in phpc# MCRYPT_RIJNDAEL_256 php中的加密解密类
【发布时间】:2018-09-10 19:35:31
【问题描述】:

我正在尝试将 c# 应用程序转换为 php,但我停留在 C# 为基于 RIJNDAEL 算法的加密和解密提供安全类的地方。我正在尝试转换为 php。

注意:我正在使用 php 7.2,因此此版本不推荐使用 mcrypt。

C#代码

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
using System.Threading.Tasks;

namespace pharmarackencryption
{
    class Program
    {
        private const string initVector = "aw90rela942f65u2";

        // This constant is used to determine the keysize of the encryption algorithm.
        private const int keysize = 256;

        public static string Encrypt(string plainText, string passPhrase = "testing")
        {
            byte[] initVectorBytes = Encoding.UTF8.GetBytes(initVector);
            byte[] plainTextBytes = Encoding.UTF8.GetBytes(plainText);
            PasswordDeriveBytes password = new PasswordDeriveBytes(passPhrase, null);
            byte[] keyBytes = password.GetBytes(keysize / 8);
            RijndaelManaged symmetricKey = new RijndaelManaged();
            symmetricKey.Mode = CipherMode.CBC;
            ICryptoTransform encryptor = symmetricKey.CreateEncryptor(keyBytes, initVectorBytes);
            MemoryStream memoryStream = new MemoryStream();
            CryptoStream cryptoStream = new CryptoStream(memoryStream, encryptor, CryptoStreamMode.Write);
            cryptoStream.Write(plainTextBytes, 0, plainTextBytes.Length);
            cryptoStream.FlushFinalBlock();
            byte[] cipherTextBytes = memoryStream.ToArray();
            memoryStream.Close();
            cryptoStream.Close();
            return Convert.ToBase64String(cipherTextBytes);
        }

        public static string Decrypt(string cipherText, string passPhrase = "testing")
        {
            byte[] initVectorBytes = Encoding.ASCII.GetBytes(initVector);
            byte[] cipherTextBytes = Convert.FromBase64String(cipherText);
            PasswordDeriveBytes password = new PasswordDeriveBytes(passPhrase, null);
            byte[] keyBytes = password.GetBytes(keysize / 8);
            RijndaelManaged symmetricKey = new RijndaelManaged();
            symmetricKey.Mode = CipherMode.CBC;
            ICryptoTransform decryptor = symmetricKey.CreateDecryptor(keyBytes, initVectorBytes);
            MemoryStream memoryStream = new MemoryStream(cipherTextBytes);
            CryptoStream cryptoStream = new CryptoStream(memoryStream, decryptor, CryptoStreamMode.Read);
            byte[] plainTextBytes = new byte[cipherTextBytes.Length];
            int decryptedByteCount = cryptoStream.Read(plainTextBytes, 0, plainTextBytes.Length);
            memoryStream.Close();
            cryptoStream.Close();
            return Encoding.UTF8.GetString(plainTextBytes, 0, decryptedByteCount);
        }

        static void Main(string[] args)
        {
            Program p = new Program();
            string enc_password = Encrypt("437217");
            string dec_password = Decrypt("C9xJGa03dRQx9ePm0nLnHg==");
            Console.WriteLine(enc_password);
            Console.WriteLine(dec_password);
        }
    }
}

加密:C9xJGa03dRQx9ePm0nLnHg==

我在php中发现了一些类似的代码

PHP 代码:

<?php 
    // key/iv in ASCII binary data, $str base64
    function decrypt_stuff($key, $str, $iv) {
        // $plaintext_dec = mcrypt_decrypt(MCRYPT_RIJNDAEL_256, $key, base64_decode($str), MCRYPT_MODE_CBC, $iv);
        $plaintext_dec = openssl_decrypt(base64_decode($str), "aes-256-cbc", $key,  OPENSSL_RAW_DATA|OPENSSL_ZERO_PADDING, $iv);
        return $plaintext_dec;
    }

    // key/iv in ascii binary data, $str ascii
    function encrypt_stuff($key, $str, $iv) {
        // $ciphertext = base64_encode(mcrypt_encrypt(MCRYPT_RIJNDAEL_256, $key, $str, MCRYPT_MODE_CBC, $iv));
        if (($l = (strlen($str) & 15)) > 0) { $str .= str_repeat(chr(0), 16 - $l); }
        $ciphertext = base64_encode(openssl_encrypt($str, "aes-256-cbc", $key,  OPENSSL_RAW_DATA|OPENSSL_ZERO_PADDING, $iv));
        return $ciphertext;
    }

    echo encrypt_stuff("testing","437217","aw90rela942f65u2");
    //Result : LTbhEHjFgfa5PDJQXJEdKQ==

两者都在做同样的事情,但结果仍然不同

【问题讨论】:

  • 您可以回到 7.0 版本,没有大的区别,但您会在那个版本中发现 mcrypt
  • openssl_encrypt 是 mcrypt 的替代品
  • 谢谢,我从来不知道。
  • 您可能需要仔细检查填充
  • @sh4dowb,我已经检查了填充,但结果仍然不同

标签: c# php


【解决方案1】:

您的 PHP 代码会产生不同的结果,因为您没有使用相同的密钥。在您的 C# 代码中,您使用 PasswordDeriveBytes 创建密钥,这是 Microsoft 的 PBKDF1 实现。

PHP 不支持 PBKDF1,但我们可以编写一个与 C# 兼容的函数。下面的代码灵感来自 Maarten Bodewes 的 answer,已翻译为 PHP。

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 = passwordDeriveBytes($password, $salt, $iterations - 1, 20);
        $counter = 0;
        while (strlen($key) < $len) {
            $counter += 1;
            $key .= sha1($counter . $hx, true);
        }
    }
    return substr($key, 0, $len);
}

另外,您需要手动对数据进行 bese64 编码和填充。您正在执行零字节填充,但在您的 C# 代码中,您使用的是 PKCS7(默认和首选)填充。最好让openssl 填充并编码您的数据。

function encrypt_stuff($key, $str, $iv) {
    return openssl_encrypt($str, "aes-256-cbc", $key, 0, $iv);
}

function decrypt_stuff($key, $str, $iv) {
    return openssl_decrypt($str, "aes-256-cbc", $key, 0, $iv);
}

使用从passwordDeriveBytes 派生的密钥,此 PHP 代码产生与您的 C# 代码相同的结果。

$key = passwordDeriveBytes("testing", null);
$enc = encrypt_stuff($key,"437217","aw90rela942f65u2");
echo $enc;
//C9xJGa03dRQx9ePm0nLnHg==

但是,出于以下原因,我不建议使用此代码。

  • 最好使用 PBKDF2 作为您的密钥。你可以在 C# 中使用Rfc2898DeriveBytes

    Rfc2898DeriveBytes kdf = new Rfc2898DeriveBytes(password, salt, iterations);
    byte[] key = kdf.GetBytes(32);
    

    hash_pbkdf2 在 PHP 中:

    $key = hash_pbkdf2("sha1", $password, $salt, $iterations, 32, true);
    

    盐的长度至少应为 8 个字节,迭代次数应至少为 10,000。

  • 您没有使用盐。您应该为每个密码使用随机盐,它使您的密钥更强大。

  • 您正在使用静态 IV。 IV 应该是唯一且不可预测的。您可以使用RNGCryptoServiceProvider 创建一个随机 IV:

    byte[] iv = new byte[16];
    RNGCryptoServiceProvider rng = new RNGCryptoServiceProvider(); 
    rng.GetBytes(iv);
    

    openssl_random_pseudo_bytes:

    $iv = openssl_random_pseudo_bytes(16);
    

    您也可以将此代码用于盐。

  • 您没有使用经过身份验证的加密。如果您使用的是 PHP 7,则可以使用 GCM,但不幸的是 .NET 不提供任何 AEAD 算法。但是,如果您选择使用经过身份验证的加密,则可以使用 bouncycastle


但是您的加密代码应该在每次使用时产生不同的结果,因为您应该使用随机 IV。如果您有 IV,则可以正确解密密文。 IV 不必保密。您可以将其存储在密文旁边。

如果您将mcryptMCRYPT_RIJNDAEL_256 一起使用,您仍然可以使您的PHP 代码与C# 兼容,因为RijndaelManaged 支持256 位块大小。

symmetricKey.BlockSize = 256;

但是您不应该使用mcrypt,因为它没有得到维护,并且在 PHP 7 中已被弃用。

【讨论】:

  • 您的 php 脚本返回 "R6CjRlJr+2fXh8ic6JeZBA==" 而不是 "C9xJGa03dRQx9ePm0nLnHg==" 请检查。
  • 说实话,我没有使用你的填充方法。我已更新我的答案以产生所需的结果。
【解决方案2】:

我不熟悉这个问题的 C# 结尾。不过,我会指出一些可能与您的问题相关的信息。

  • RIJNDAEL 函数中的描述指的是块大小,而不是密钥大小。例如,MCRYPT_RIJNDAEL_256 声明您使用的块大小为 256。它没有指定密钥大小。
  • 对于 openssl AES 函数,您正在指定密钥大小。例如,aes-256-cbc 指定您使用的是 256 位密钥。
  • 所有 AES 函数的块大小为 128 位。因此,您可以使用与 AES 块大小匹配的 MCRYPT_RIJNDAEL_128MCRYPT_RIJNDAEL_128aes-256-cbc 都可以使用 256 位密钥,因为它们都使用 128 位块大小。
  • 因此,由于这些原因,aes-256-cbc 不能与 MCRYPT_RIJNDAEL_256 一起使用。它们根本不是一回事。

  • 这个是多余的。如果您使用的是aes-256-cbc,您需要确保您使用的是 256 位密钥。显然,请确保您使用相同的密钥进行加密和解密。确保您的 IV 是正确的 IV。我会将它们都设为静态以进行测试。一旦你得到它,在加密时将IV添加到密码字符串中,并在解密时将IV与密文分开

  • 使用openssl_random_pseudo_bytes() 生成您的 256 位(32 字节)密钥。您也可以使用openssl_random_pseudo_bytes() 来生成您的 IV。

附带说明。我强烈推荐使用LibSodium 库。它现在在最新版本的 PHP 上是原生的,并且还有一个 C# 库。你可以很容易地在 Github 上找到它。

一旦你完成了这项工作,我会学习如何验证/验证你的加密。这是一个很好的起始链接。 Authentication Read

希望对您有所帮助。

【讨论】:

    【解决方案3】:

    不建议使用可逆算法将密码存储在数据库中,应使用昂贵的哈希值存储密码。您应该考虑将密码存储切换为 Argon2 之类的哈希。

    检查这个:http://php.net/manual/en/function.password-hash.php

    【讨论】:

      【解决方案4】:

      mcrypt 已被移动。未删除。你可以试试安装这个

      sudo apt-get -y install gcc make autoconf libc-dev pkg-config
      sudo apt-get -y install php7.2-dev
      sudo apt-get -y install libmcrypt-dev
      sudo pecl install mcrypt-1.0.1
      

      PS:未测试

      【讨论】:

      • 我用的是windows
      • openssl_encrypt 是 mcrypt 的替代品
      猜你喜欢
      • 1970-01-01
      • 2014-07-07
      • 1970-01-01
      • 2012-01-03
      • 2015-10-04
      • 2018-01-24
      • 2013-08-08
      • 1970-01-01
      相关资源
      最近更新 更多