用PHP重写Rijndael 256 C#加密代码
我有一个用C#编写的加密/解密算法——我需要能够用PHP生成相同的加密,这样我就可以通过HTTP发送加密文本,以便在C#端解密。 这是加密的C代码用PHP重写Rijndael 256 C#加密代码,c#,php,encryption,aes,rijndael,C#,Php,Encryption,Aes,Rijndael,我有一个用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_initVect
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#code在中的方法相同。这是为了执行PBKDF1密钥派生而记录的,如下所示:
此类使用
PKCS#5中定义的PBKDF1算法
v2.0标准,用于派生合适的字节
用于从一个
密码。该标准已记录在案
在IETF RRC 2898中
有一些PHP库实现了PBKDF1,但是基于RFC从头开始编写一个非常简单:
PBKDF1(p、S、c、dkLen)
选项:散列
底层散列函数
输入:p
密码,八位字节字符串
S salt,一个八位字节的字符串
c迭代计数,一个正整数
dkLen派生密钥的预期长度(以八位字节为单位),
一个正整数,MD2最多16
或
SHA-1的MD5和20
输出:DK导出
键,一个dkLen八位组字符串
步骤:
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.
现在让我们编写一个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;
}
此输出完全符合预期结果:
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而不是从密码短语和salt派生出来,这完全是错误的。请使用现成的加密方案,如SSL或S-MIME,不要重新发明自己的加密方案。您将得到错误的看起来您的主要问题是使用PHP的hash()
代替C端的PasswordDeriveBytes()
步骤。这两种方法并不等同。后者实现密码派生算法,而hash()
只是一个散列。看起来是这样的,但要不然你可能得自己写
如果还没有,您还需要确保文本编码在两侧都是一致的
最后,你应该考虑不要做你正在做的事情。因为您使用的是HTTP,所以可以使用SSL协议来代替编写自己的协议。这将为您带来更好的安全性,并减少对低级细节(如保持增量IVs同步等)的麻烦。是否有一个很好的理由让您不能只使用rijndael-256作为算法???检查PHP中的OpenSSL例程,他们应该能够处理您需要做的事情。该模块甚至比他已经拥有的模块更低级!从PHP 7.1.0开始,此函数已被弃用,并从PHP 7.2.0开始删除。感谢您的回复。是的,我意识到主要问题在于PasswordDeriveBytes()。你是说,通过HTTPS通过SSL传递明文密码比一边编码,另一边解码更安全吗?@Derek Armstrong:是的!SSL设计用于保护明文密码之类的东西,就像您的方案一样。主要区别在于,SSL在过去15年中已经被地球上最好的密码学家审查和修改。我认为它也会更容易使用,特别是因为你已经在通过HTTP进行交谈。我也在Remus的回答中添加了一个注释,PasswordDeriveBytes是一个不一致的,未记录的,所有从它请求的字节都超过了PBKDF1的最大输出(20字节)。谢谢你的回答,我想我明白你在这里想说什么了,但这还不足以实际投入使用。是否存在可能的完整编码示例?这是我为PBKDF1$HashPassPhrase=“passharse”提供的PHP函数$HashSalt=“saltvalue”$迭代次数=2$devkeylength=32$devkey=PBKDF1($HashPassPhrase、$Hashsalt、$HashIterations、$devkeylength);函数PBKDF1($pass,$salt,$count,$dklen){$t=sha1($pass.$salt);for($i=1;$i<$count;$i++){$t=sha1($t);}$t=substr($t,0,$dklen-1);return$t;}但我认为我没有得到正确的结果。//警告:不检查$dklen,必须是0..20函数PBKDF1($pass,$salt,$count,$dklen){$t=$pass.$salt;;for($i=0;$i<$count;$i++){$t=sha1($t,true);}返回substr($t,0,$dklen);}
重要注意事项:PasswordDeriveBytes仅与PBKDF1兼容,如果请求的长度小于等于20字节,则PBKDF1与SHA1的最大输出。PasswordDeriveBytes使用专有方案,这是不安全的,甚至不一致(例如,先请求32个字节,然后再请求16个字节会产生与请求48个字节不同的结果)。有关更多信息,请参阅。重要提示#2:甚至会发生,这意味着您的IV正在泄漏关键信息。
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