【问题标题】:File encryption in C# with AES, decryption with phpseclib在 C# 中使用 AES 加密文件,使用 phpseclib 解密
【发布时间】:2017-11-01 08:21:12
【问题描述】:

我正在开发一个安全文件传输项目,该项目在客户端使用 c# 客户端加密文件。我需要使用 php 和 phpseclib 解密服务器端的文件。这里的代码是我从一个 msdn 示例中复制的。但我无法在php中解决解密功能。​​

public static byte[] AES_Encrypt(byte[] bytesToBeEncrypted, byte[] passwordBytes)
        {
            byte[] encryptedBytes = null;
            byte[] saltBytes = passwordBytes;

            using (MemoryStream ms = new MemoryStream())
            {
                using (RijndaelManaged AES = new RijndaelManaged())
                {
                    var key = new Rfc2898DeriveBytes(passwordBytes, saltBytes, 1000);

                    AES.KeySize = 256;
                    AES.BlockSize = 256;
                    AES.Mode = CipherMode.CBC;
                    AES.Padding = PaddingMode.Zeros;
                    AES.Key = key.GetBytes(AES.KeySize / 8);
                    AES.IV = key.GetBytes(AES.BlockSize / 8);                    

                    using (CryptoStream cs = new CryptoStream(ms, AES.CreateEncryptor(), CryptoStreamMode.Write))
                    {
                        cs.Write(bytesToBeEncrypted, 0, bytesToBeEncrypted.Length);
                        cs.Close();
                    }
                    encryptedBytes = ms.ToArray();
                }
            }

            return encryptedBytes;
        }

这是不起作用的php代码:

        $pw = "this_is_my_pw";
        $aes = new Crypt_AES(CRYPT_AES_MODE_CBC);
        $aes->setKey($pw);
        $aes->setKeyLength(256);
        $aes->disablePadding();

        $file = "enc.txt";

        $fh = fopen($file, "r");
        $contents = trim(fread($fh, filesize($file)));
        fclose($fh);

        //echo "Encoded: \n\n" . $contents;

        $contents = $aes->decrypt($contents);
        #$block = mcrypt_get_block_size(MCRYPT_RIJNDAEL_256, MCRYPT_MODE_CBC);
        #$padding = $block - (strlen($clear) % $block);

        #$dec = mcrypt_decrypt(MCRYPT_RIJNDAEL_256, $pw, base64_decode($contents), MCRYPT_MODE_CBC, $pw);

        echo "Decoded: \n\n" . $contents;

谁能帮我解决这个问题或给我一个提示我做错了什么?

【问题讨论】:

    标签: c# php encryption phpseclib


    【解决方案1】:
    1. 解密时不使用初始化向量。您需要将初始化向量 (IV) 与数据一起发送 - 您的 PHP 代码永远不会从 phpseclib 调用 $aes->setIV,因此它会永远无法解密文本,因为如果未设置according to the docs,则 phpseclib 使用全零的 IV。我个人建议使用RijndaelManaged.GenerateIV 从 C# 生成安全随机 IV,但显然从 PBKDF2 密钥派生 IV 是可以接受的。 PBKDF2(在 RFC 2898 中指定)是 Rfc2898DeriveBytes 实现的密钥拉伸算法。无论如何,您需要在 PHP 端重新生成 IV,无论这意味着使用加密数据传输 IV(这完全没问题)还是在 PHP 端重新导出 IV。

    2. 使用密码作为盐是一个非常糟糕的主意。盐需要有足够的长度并以加密方式随机生成。使用密码作为盐完全违背了使用盐的意义。 MSDN has some sample code 展示了如何结合使用 Rfc2898DeriveBytes 生成加密随机盐,但重要的部分在这里:

    byte[] saltBytes = new byte[8];
    using (RNGCryptoServiceProvider rngCsp = new RNGCryptoServiceProvider())
    {
        // Fill the array with a random value.
        rngCsp.GetBytes(salt1);
    }
    
    1. salt 必须与加密数据一起传输。 您需要将 PBKDF2 salt 字节与 IV 字节和加密数据一起发送。 phpseclib 将需要所有这些来正确初始化自身并解密数据。你可能想要使用 phpseclib 的 setPassword 来执行此操作,如下所示:
    $salt = ...; // get the salt to your PHP code somehow
    $iv = ...; // get the IV to your PHP code
    $pw = "this_is_my_pw";
    $aes = new Crypt_AES(CRYPT_AES_MODE_CBC);
    $aes->setPassword($pw, 'pbkdf2' /* key extension algorithm */,
        'sha1' /* hash algorithm */, $salt /* generated salt from C# */,
        1000 /* number of iterations, must be same as C# code */,
        256 / 8 /* key size in bytes, 256 bit key / 8 bits per byte */
    );
    $aes->setIV($iv);
    
    1. 请记住有关块大小的其他答案。 128 位是标准的 AES 块大小,因此请确保 C# 和 phpseclib 都可以在更大的块大小下正常运行,或者只对两者使用 AES 标准。李>

    【讨论】:

    • 在所有伟大的建议。 1. 有趣的是,IV 不正确会导致第一个块被错误地解密,但消息的其余部分将被正确解密,这样的结果有助于指向不正确的 IV。一种最佳做法是将 IV 预先添加到加密数据中。 2. 使用 PBKDF2(在 RFC 2898 中指定)是一种最佳实践并提供互操作性。还需要提供迭代计数以进行解密。
    【解决方案2】:

    如果您尝试使用 AES,请将块大小设置为 128 位,这是唯一受支持的块大小。使用不同的块大小意味着您正在使用 Rijndael 加密,跨平台支持不充分。

    AES 支持 128、192 和 256 位的多种密钥大小。有时在使用 Rijndael 实现来使用 AES 加密时会出现混淆。

    【讨论】:

      【解决方案3】:

      在 Java 代码中,我看到了 AES.BlockSize = 256;。从技术上讲,AES 具有 128 位的固定块大小。 Rijndael 支持可变块大小,但 AES 不支持。如果你想通过 phpseclib 在 PHP 中使用可变块大小,你需要这样做:

          $pw = "this_is_my_pw";
          $aes = new Crypt_Rijndael(CRYPT_RIJNDAEL_MODE_CBC);
          $aes->setKey($pw);
          $aes->setKeyLength(256);
          $aes->setBlockLength(256);
          $aes->disablePadding();
      

      另外,您的密钥长度为 13 个字节。 AES 密钥需要是 16 字节(128 位)长、24 字节(192 位)长或 32 字节(256 位)长。 idk 你正在使用什么 js lib,但是如果 phpseclib 1.0/2.0 null 键不够长,则它们会填充键。最新版本的 phpseclib - 目前正在开发中 - 会引发异常。

      或者您的意思是使用基于密码的密钥派生函数? phpseclib 提供了两个可以通过setPassword() 使用的方法,但如果是这种情况,您需要知道 js lib 使用了哪些方法和参数。

      【讨论】:

      • 使用不完全支持长度的加密密钥取决于非标准密钥填充,这不是一个好主意,并且可能导致互操作性困难。使用文本作为密钥也会降低安全性。在这种情况下,密码表示少于 86 位的密钥材料。
      • @zaph - 很好地了解了密钥大小 - 我实际上并没有看到那个。我刚刚注意到关于块大小的一点,有点跳了哈哈
      • 答案是 Rijndael (setBlockLength(256)) 而不是 AES,Rijndael 允许密钥和块大小分别为 128、160、192、224 或 256 位。
      • @zaph - 正确。然而,OP 正在使用RijndaelManaged AES = new RijndaelManaged()AES.BlockSize = 256;,所以我这样做似乎是完全合理的。我在第二句话中也说了这么多。引用它,从技术上讲,AES 具有 128 位的固定块大小。 Rijndael 支持可变块大小,但 AES 不支持
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2016-09-04
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多