用PHP重写Rijndael 256 C#加密代码

时间:2021-09-22 18:31:13

I have an encryption/decryption algorithm written in C# - I need to be able to produce the same encryption in PHP so I can send the encrypted text over HTTP to be decrypted on the C# side. Here is the C# code for the encryption.

我有一个用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;
}

I have some similar PHP code that may help. It doesn't do exactly as needed, but I think it may be a good place to start.

我有一些类似的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);

?>

4 个解决方案

#1


17  

You need to derive the key from the pass phrase the same way as the C# code does in the PasswordDeriveBytes. This is documented to do PBKDF1 key derivation, as per RFC2898:

您需要从密码导出密钥的方式与密码导入字母中的C#代码相同。据记录,这可以根据RFC2898进行PBKDF1密钥派生:

This class uses an extension of the PBKDF1 algorithm defined in the PKCS#5 v2.0 standard to derive bytes suitable for use as key material from a password. The standard is documented in IETF RRC 2898.

此类使用PKCS#5 v2.0标准中定义的PBKDF1算法的扩展来派生适合用作密码的密钥材料的字节。该标准记录在IETF RRC 2898中。

there are PHP libraries that implement PBKDF1 out there, but is really simple to write one from scratch based ont he RFC:

那里有实现PBKDF1的PHP库,但基于RFC可以很容易地从头开始编写:

PBKDF1 (P, S, c, dkLen)

PBKDF1(P,S,c,dkLen)

Options: Hash
underlying hash function

选项:哈希底层哈希函数

Input: P
password, an octet string S salt, an eight-octet string c iteration count, a positive integer dkLen intended length in octets of derived key, a positive integer, at most 16 for MD2 or MD5 and 20 for SHA-1

输入:P密码,八位字节串S盐,八位八位字符串c迭代计数,正整数dkLen预期长度,以八位字节为单位的导出密钥,一个正整数,最多16为MD2或MD5,20为SHA-1

Output: DK derived key, a dkLen-octet string

输出:DK派生密钥,dkLen-octet字符串

Steps:

  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.

Updated

When you find youself in this situation, you usually search for an example implementaiton that shows the values at every step. for instance the one at http://www.di-mgt.com.au/cryptoKDFs.html#examplespbkdf:

当您在这种情况下发现自己时,通常会搜索一个示例实现,以显示每一步的值。例如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

So now lets write a PHP function that does this:

所以现在让我们编写一个执行此操作的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;      
}

Now you can see the errs of your ways: you did not specify the all important raw=true parameter to sha1. Lets see what is our function output:

现在你可以看到你的方式的错误:你没有为sha1指定所有重要的raw = true参数。让我们看看我们的功能输出是什么:

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

this output exactly the expected result:

这个输出恰好是预期的结果:

Key:dc19847e05c64d2f
IV:af10ebfb4a3d2a20
Expected: DC19847E05C64D2FAF10EBFB4A3D2A20

Next, we can validate that the C# function does the same:

接下来,我们可以验证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();

this produces the very same output:

这产生了相同的输出:

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

QED

bonus explanation

Please don't do crypto if you don't know exactly what you're doing. Even after you get the PHP implementaiton correct, your posted C# code has some serious problems. You are mixing byte arrays with stirng representing hex dumps, you use a hard coded IV instead of deriving it from the passphrase and salt, is just overall plain wrong. Please use an off-the shelf encryption scheme, like SSL or S-MIME and do not re-invent your own. You will get it wrong.

如果您不确切知道自己在做什么,请不要加密。即使你的PHP实现正确,你发布的C#代码也有一些严重的问题。你正在使用代表十六进制转储的stirng混合字节数组,你使用硬编码的IV而不是从密码和盐中导出它,这只是整体上的错误。请使用现成的加密方案,如SSL或S-MIME,不要重新创建自己的加密方案。你会弄错的。

#2


4  

It looks like your main problem is that you're using PHP's hash() in place of the PasswordDeriveBytes() step on the C# side. Those two methods are not equivalent. The latter implements the PBKDF1 password derivation algorithm, while hash() is just a hash. It looks like PEAR might have a PBKDF1 implementation, but otherwise you might have to write it yourself.

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

You also need to make sure your text encoding is consistent on both sides, if you haven't already.

如果还没有,还需要确保双方的文本编码一致。

Finally, you should consider not doing what you're doing because cryptography is harder than it looks. Since you're using HTTP, you can make use of the SSL protocol in lieu of writing your own. This will net you far better security and less hassle on low-level details like keeping incremental IVs in sync and whatnot.

最后,你应该考虑不要做你正在做的事情因为加密比看起来更难。由于您使用的是HTTP,因此您可以使用SSL协议代替编写自己的SSL协议。这将为您提供更好的安全性,减少对低级细节的麻烦,例如保持增量IV同步等等。

#3


0  

Is there a good reason why you can't just use http://php.net/manual/en/function.mcrypt-module-open.php and use rijndael-256 as the algorithm????

有没有一个很好的理由为什么你不能只使用http://php.net/manual/en/function.mcrypt-module-open.php并使用rijndael-256作为算法????

#4


0  

Check OpenSSL routines in PHP, they should be able to handle what you need to do.

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

#1


17  

You need to derive the key from the pass phrase the same way as the C# code does in the PasswordDeriveBytes. This is documented to do PBKDF1 key derivation, as per RFC2898:

您需要从密码导出密钥的方式与密码导入字母中的C#代码相同。据记录,这可以根据RFC2898进行PBKDF1密钥派生:

This class uses an extension of the PBKDF1 algorithm defined in the PKCS#5 v2.0 standard to derive bytes suitable for use as key material from a password. The standard is documented in IETF RRC 2898.

此类使用PKCS#5 v2.0标准中定义的PBKDF1算法的扩展来派生适合用作密码的密钥材料的字节。该标准记录在IETF RRC 2898中。

there are PHP libraries that implement PBKDF1 out there, but is really simple to write one from scratch based ont he RFC:

那里有实现PBKDF1的PHP库,但基于RFC可以很容易地从头开始编写:

PBKDF1 (P, S, c, dkLen)

PBKDF1(P,S,c,dkLen)

Options: Hash
underlying hash function

选项:哈希底层哈希函数

Input: P
password, an octet string S salt, an eight-octet string c iteration count, a positive integer dkLen intended length in octets of derived key, a positive integer, at most 16 for MD2 or MD5 and 20 for SHA-1

输入:P密码,八位字节串S盐,八位八位字符串c迭代计数,正整数dkLen预期长度,以八位字节为单位的导出密钥,一个正整数,最多16为MD2或MD5,20为SHA-1

Output: DK derived key, a dkLen-octet string

输出:DK派生密钥,dkLen-octet字符串

Steps:

  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.

Updated

When you find youself in this situation, you usually search for an example implementaiton that shows the values at every step. for instance the one at http://www.di-mgt.com.au/cryptoKDFs.html#examplespbkdf:

当您在这种情况下发现自己时,通常会搜索一个示例实现,以显示每一步的值。例如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

So now lets write a PHP function that does this:

所以现在让我们编写一个执行此操作的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;      
}

Now you can see the errs of your ways: you did not specify the all important raw=true parameter to sha1. Lets see what is our function output:

现在你可以看到你的方式的错误:你没有为sha1指定所有重要的raw = true参数。让我们看看我们的功能输出是什么:

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

this output exactly the expected result:

这个输出恰好是预期的结果:

Key:dc19847e05c64d2f
IV:af10ebfb4a3d2a20
Expected: DC19847E05C64D2FAF10EBFB4A3D2A20

Next, we can validate that the C# function does the same:

接下来,我们可以验证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();

this produces the very same output:

这产生了相同的输出:

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

QED

bonus explanation

Please don't do crypto if you don't know exactly what you're doing. Even after you get the PHP implementaiton correct, your posted C# code has some serious problems. You are mixing byte arrays with stirng representing hex dumps, you use a hard coded IV instead of deriving it from the passphrase and salt, is just overall plain wrong. Please use an off-the shelf encryption scheme, like SSL or S-MIME and do not re-invent your own. You will get it wrong.

如果您不确切知道自己在做什么,请不要加密。即使你的PHP实现正确,你发布的C#代码也有一些严重的问题。你正在使用代表十六进制转储的stirng混合字节数组,你使用硬编码的IV而不是从密码和盐中导出它,这只是整体上的错误。请使用现成的加密方案,如SSL或S-MIME,不要重新创建自己的加密方案。你会弄错的。

#2


4  

It looks like your main problem is that you're using PHP's hash() in place of the PasswordDeriveBytes() step on the C# side. Those two methods are not equivalent. The latter implements the PBKDF1 password derivation algorithm, while hash() is just a hash. It looks like PEAR might have a PBKDF1 implementation, but otherwise you might have to write it yourself.

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

You also need to make sure your text encoding is consistent on both sides, if you haven't already.

如果还没有,还需要确保双方的文本编码一致。

Finally, you should consider not doing what you're doing because cryptography is harder than it looks. Since you're using HTTP, you can make use of the SSL protocol in lieu of writing your own. This will net you far better security and less hassle on low-level details like keeping incremental IVs in sync and whatnot.

最后,你应该考虑不要做你正在做的事情因为加密比看起来更难。由于您使用的是HTTP,因此您可以使用SSL协议代替编写自己的SSL协议。这将为您提供更好的安全性,减少对低级细节的麻烦,例如保持增量IV同步等等。

#3


0  

Is there a good reason why you can't just use http://php.net/manual/en/function.mcrypt-module-open.php and use rijndael-256 as the algorithm????

有没有一个很好的理由为什么你不能只使用http://php.net/manual/en/function.mcrypt-module-open.php并使用rijndael-256作为算法????

#4


0  

Check OpenSSL routines in PHP, they should be able to handle what you need to do.

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