【问题标题】:Rewrite Rijndael 256 C# Encryption Code in PHP用 PHP 重写 Rijndael 256 C# 加密代码
【发布时间】:2011-03-31 03:30:18
【问题描述】:

我有一个用 C# 编写的加密/解密算法 - 我需要能够在 PHP 中生成相同的加密,以便我可以通过 HTTP 发送加密文本以在 C# 端解密。 这是用于加密的 C# 代码。

this.m_plainText = string.Empty;
this.m_passPhrase = "passpharse";
this.m_saltValue = "saltvalue";
this.m_hashAlgorithm = "SHA1";
this.m_passwordIterations = 2;
this.m_initVector = "1a2b3c4d5e6f7g8h";
this.m_keySize = 256;

public string Encrypt()
{
    string plainText = this.m_plainText;
    string passPhrase = this.m_passPhrase;
    string saltValue = this.m_saltValue;
    string hashAlgorithm = this.m_hashAlgorithm;
    int passwordIterations = this.m_passwordIterations;
    string initVector = this.m_initVector;
    int keySize = this.m_keySize;

    // Convert strings into byte arrays.
    // Let us assume that strings only contain ASCII codes.
    // If strings include Unicode characters, use Unicode, UTF7, or UTF8 
    // encoding.
    byte[] initVectorBytes = Encoding.ASCII.GetBytes(initVector);
    byte[] saltValueBytes = Encoding.ASCII.GetBytes(saltValue);

    // Convert our plaintext into a byte array.
    // Let us assume that plaintext contains UTF8-encoded characters.
    byte[] plainTextBytes = Encoding.UTF8.GetBytes(plainText);

    // First, we must create a password, from which the key will be derived.
    // This password will be generated from the specified passphrase and 
    // salt value. The password will be created using the specified hash 
    // algorithm. Password creation can be done in several iterations.
    PasswordDeriveBytes password = new PasswordDeriveBytes(
                                                    passPhrase,
                                                    saltValueBytes,
                                                    hashAlgorithm,
                                                    passwordIterations);

    // Use the password to generate pseudo-random bytes for the encryption
    // key. Specify the size of the key in bytes (instead of bits).
    byte[] keyBytes = password.GetBytes(keySize / 8);

    // Create uninitialized Rijndael encryption object.
    RijndaelManaged symmetricKey = new RijndaelManaged();

    // It is reasonable to set encryption mode to Cipher Block Chaining
    // (CBC). Use default options for other symmetric key parameters.
    symmetricKey.Mode = CipherMode.CBC;

    // Generate encryptor from the existing key bytes and initialization 
    // vector. Key size will be defined based on the number of the key 
    // bytes.
    ICryptoTransform encryptor = symmetricKey.CreateEncryptor(
                                                     keyBytes,
                                                     initVectorBytes);

    // Define memory stream which will be used to hold encrypted data.
    MemoryStream memoryStream = new MemoryStream();

    // Define cryptographic stream (always use Write mode for encryption).
    CryptoStream cryptoStream = new CryptoStream(memoryStream,
                                                 encryptor,
                                                 CryptoStreamMode.Write);
    // Start encrypting.
    cryptoStream.Write(plainTextBytes, 0, plainTextBytes.Length);

    // Finish encrypting.
    cryptoStream.FlushFinalBlock();

    // Convert our encrypted data from a memory stream into a byte array.
    byte[] cipherTextBytes = memoryStream.ToArray();

    // Close both streams.
    memoryStream.Close();
    cryptoStream.Close();

    // Convert encrypted data into a base64-encoded string.
    string cipherText = Convert.ToBase64String(cipherTextBytes);

    // Return encrypted string.
    return cipherText;
}

我有一些类似的 PHP 代码可能会有所帮助。它并不完全符合需要,但我认为这可能是一个不错的起点。

<?php

/*
 * DEFINE CONSTANTS
 */
$HashPassPhrase = "passpharse";
$HashSalt = "saltvalue";
$HashAlgorithm = "SHA1";
$HashIterations = "2";
$InitVector = "1a2b3c4d5e6f7g8h";        // Must be 16 bytes
$keySize = "256";

class Cipher {
    private $securekey, $iv;
    function __construct($textkey) {
        $this->securekey = hash($HashAlgorithm,$textkey,TRUE);
        $this->iv = $InitVector;
    }
    function encrypt($input) {
        return base64_encode(mcrypt_encrypt(MCRYPT_RIJNDAEL_256, $this->securekey, $input, MCRYPT_MODE_CBC, $this->iv));
    }
    function decrypt($input) {
        return trim(mcrypt_decrypt(MCRYPT_RIJNDAEL_256, $this->securekey, base64_decode($input), MCRYPT_MODE_CBC, $this->iv));
    }
}

$cipher = new Cipher($HashPassPhrase);

$encryptedtext = $cipher->encrypt("Text To Encrypt");
echo "->encrypt = $encryptedtext<br />";

$decryptedtext = $cipher->decrypt($encryptedtext);
echo "->decrypt = $decryptedtext<br />";

var_dump($cipher);

?>

【问题讨论】:

    标签: c# php encryption aes rijndael


    【解决方案1】:

    您需要使用与PasswordDeriveBytes 中的 C# 代码相同的方式从密码短语派生密钥。根据RFC2898,这被记录为执行 PBKDF1 密钥派生:

    这个类使用了 PKCS#5 中定义的 PBKDF1 算法 v2.0 标准导出适合的字节 用作从一个关键材料 密码。该标准已记录在案 在 IETF RRC 2898 中。

    那里有实现 PBKDF1 的 PHP 库,但基于 RFC 从头开始​​编写一个非常简单:

    PBKDF1 (P, S, c, dkLen)

    选项:哈希
    底层哈希函数

    输入:P
    密码,八位字节字符串 S salt,一个八位字节的字符串 c 迭代次数,正整数 dkLen 派生密钥的八位字节的预期长度, 一个正整数,对于 MD2 最多为 16 要么 SHA-1 的 MD5 和 20

    输出:DK 派生 键,一个 dkLen-octet 字符串

    步骤:

      1. If dkLen > 16 for MD2 and MD5, or dkLen > 20 for SHA-1, output
         "derived key too long" and stop.
    
      2. Apply the underlying hash function Hash for c iterations to the
         concatenation of the password P and the salt S, then extract
         the first dkLen octets to produce a derived key DK:
    
                   T_1 = Hash (P || S) ,
                   T_2 = Hash (T_1) ,
                   ...
                   T_c = Hash (T_{c-1}) ,
                   DK = Tc<0..dkLen-1>
    
      3. Output the derived key DK.
    

    更新

    当您发现自己处于这种情况时,您通常会搜索一个示例实现,该示例会显示每一步的值。例如http://www.di-mgt.com.au/cryptoKDFs.html#examplespbkdf

    Password = "password" 
             = (0x)70617373776F7264
    Salt     = (0x)78578E5A5D63CB06
    Count    = 1000
    kLen     = 16
    Key      = PBKDF1(Password, Salt, Count, kLen)
             = (0x)DC19847E05C64D2FAF10EBFB4A3D2A20
    
    P || S = 70617373776F726478578E5A5D63CB06
    T_1=     D1F94C4D447039B034494400F2E7DF9DCB67C308
    T_2=     2BB479C1D369EA74BB976BBA2629744E8259C6F5
    ...
    T_999=   6663F4611D61571068B5DA168974C6FF2C9775AC
    T_1000=  DC19847E05C64D2FAF10EBFB4A3D2A20B4E35EFE
    Key=     DC19847E05C64D2FAF10EBFB4A3D2A20
    

    现在让我们编写一个 PHP 函数来执行此操作:

    function PBKDF1($pass,$salt,$count,$dklen) { 
        $t = $pass.$salt;
        //echo 'S||P: '.bin2hex($t).'<br/>';
        $t = sha1($t, true); 
        //echo 'T1:' . bin2hex($t) . '<br/>';
        for($i=2; $i <= $count; $i++) { 
            $t = sha1($t, true); 
            //echo 'T'.$i.':' . bin2hex($t) . '<br/>';
        } 
        $t = substr($t,0,$dklen); 
        return $t;      
    }
    

    现在您可以看到您的方法的错误:您没有将所有重要的raw=true 参数指定给sha1。让我们看看我们的函数输出是什么:

    $HashPassPhrase = pack("H*","70617373776F7264");
    $HashSalt = pack("H*","78578E5A5D63CB06"); 
    $HashIterations = 1000; 
    $devkeylength = 16; 
    $devkey = PBKDF1($HashPassPhrase,$HashSalt,$HashIterations,$devkeylength);
    echo 'Key:' . bin2hex(substr($devkey, 0, 8)) . '<br/>';
    echo 'IV:' . bin2hex(substr($devkey, 8, 8)) .'<br/>';
    echo 'Expected: DC19847E05C64D2FAF10EBFB4A3D2A20<br/>';
    

    这个输出完全符合预期的结果:

    Key:dc19847e05c64d2f
    IV:af10ebfb4a3d2a20
    Expected: DC19847E05C64D2FAF10EBFB4A3D2A20
    

    接下来,我们可以验证 C# 函数是否相同:

                byte[] password = Encoding.ASCII.GetBytes("password");
                byte[] salt = new byte[] { 0x78, 0x57, 0x8e, 0x5a, 0x5d, 0x63, 0xcb, 0x06};
    
                PasswordDeriveBytes pdb = new PasswordDeriveBytes(
                    password, salt, "SHA1", 1000);
    
                byte[] key = pdb.GetBytes(8);
                byte[] iv = pdb.GetBytes(8);
    
                Console.Out.Write("Key: ");
                foreach (byte b in key)
                {
                    Console.Out.Write("{0:x} ", b);
                }
                Console.Out.WriteLine();
    
                Console.Out.Write("IV: ");
                foreach (byte b in iv)
                {
                    Console.Out.Write("{0:x} ", b);
                }
                Console.Out.WriteLine();
    

    这会产生完全相同的输出:

    Key: dc 19 84 7e 5 c6 4d 2f
    IV: af 10 eb fb 4a 3d 2a 20
    

    QED

    奖金说明

    如果你不知道确切你在做什么,请不要进行加密。即使你得到正确的 PHP 实现,你发布的 C# 代码也有一些严重的问题。您将字节数组与表示十六进制转储的stirng混合在一起,您使用硬编码的IV而不是从密码和盐中派生出来,这完全是错误的。请使用现成的加密方案,如 SSL 或 S-MIME,不要重新发明自己的方案。你会得到它错误

    【讨论】:

    • 感谢您的回复,我想我明白您在这里想说什么,但还不足以真正投入使用。是否有可能的完整编码示例?
    • 这是我为 PBKDF1 提出的 PHP 函数 $HashPassPhrase = "passpharse"; $HashSalt = "盐值"; $HashIterations = 2; $devkeylength = 32; $devkey = PBKDF1($HashPassPhrase,$Hashsalt,$HashIterations,$devkeylength);函数 PBKDF1($pass,$salt,$count,$dklen) { $t = sha1($pass.$salt); for($i=1; $i
    • // warning: no checking on $dklen, must be 0..20 function PBKDF1($pass,$salt,$count,$dklen) { $t = $pass.$salt;; for($i=0; $i &lt; $count; $i++) { $t = sha1($t, true); } return substr($t,0,$dklen); }
    • 重要提示:PasswordDeriveBytes 与 PBKDF1 兼容,当且仅当 - 请求的长度为 20 或更少字节,即 SHA1 的 PBKDF1 的最大输出。 PasswordDeriveBytes 使用专有方案,该方案不安全,甚至不一致(例如,要求 32 和 16 字节会产生与要求 48 字节不同的结果)。请参阅here 了解更多信息。
    • 重要提示 #2:它甚至会 repeat bytes,这意味着您的 IV 正在泄漏关键信息。
    【解决方案2】:

    看起来您的主要问题是您使用 PHP 的 hash() 代替了 C# 端的 PasswordDeriveBytes() 步骤。这两种方法是不等价的。后者实现PBKDF1密码派生算法,而hash()只是一个哈希。它看起来像 PEAR might have a PBKDF1 implementation,但否则您可能需要自己编写。

    您还需要确保您的文本编码在双方都一致,如果您还没有这样做的话。

    最后,你应该考虑不做你正在做的事情,因为cryptography is harder than it looks。由于您使用的是 HTTP,因此您可以使用 SSL 协议来代替编写自己的协议。这将为您带来更好的安全性,并减少低级别细节的麻烦,例如保持增量 IV 同步等等。

    【讨论】:

    • 感谢您的回复。是的,我意识到主要问题在于 PasswordDeriveBytes()。您是说,使用 SSL 通过 HTTPS 传递明文密码比在一侧编码并在另一侧解码更安全?
    • @Derek Armstrong:是的! SSL 旨在保护诸如明文密码之类的内容,就像您的方案一样。主要区别在于 SSL 在过去 15 年中已经由地球上最好的密码学家审查和修订。我认为它也会更容易使用,尤其是因为您已经在通过 HTTP 进行通信了。
    • 我也在 Remus 的回答中添加了一条注释,PasswordDeriveBytes 是一个不一致的、未记录的proprietary scheme,它请求的所有字节都超过了 PBKDF1 的最大输出(20 个字节)。
    【解决方案3】:

    你有充分的理由不能只使用http://php.net/manual/en/function.mcrypt-module-open.php 并使用 rijndael-256 作为算法???

    【讨论】:

    • 那个模块比他已有的模块还要低级!
    • 从 PHP 7.1.0 开始,该函数已被弃用,并从 PHP 7.2.0 开始删除
    【解决方案4】:

    检查 PHP 中的 OpenSSL 例程,它们应该能够处理您需要做的事情。

    【讨论】:

      猜你喜欢
      • 2011-03-26
      • 2012-05-06
      • 2011-09-03
      • 1970-01-01
      • 2014-10-14
      • 2020-08-19
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多