繁体   English   中英

需要PHP 8版本的C#/.NET加密代码

[英]Need PHP 8 version of C#/.NET encryption code

一个已经使用多年的加密C#密码现在需要转换为PHP 8。
我接近了,还有一个问题如下所述:

例如,下面的密钥长度超过 71 个字符并且未正确加密:

secret = "id=jsmith12&timestamp=2022-07-06t11:10:43&expiration=2022-07-06t11:15:43"; //71 chars-long

但是,这些秘密将被正确加密,因为它们的长度少于 71 个字符:

secret = "id=jsmith&timestamp=2022-07-06t11:10:43&expiration=2022-07-06t11:15:43";  // 69 chars-long
    
secret = "id=jsmith1&timestamp=2022-07-06t11:10:43&expiration=2022-07-06t11:15:43"; // 70 chars-long

有一个在线页面,您可以在其中测试生成的令牌是否正确: https://www.mybudgetpak.com/SSOTest/

您可以通过提供生成的令牌、密钥和加密方法(Rijndael 或 Triple DES)来评估令牌。

如果评估(令牌解密)成功,测试页面将显示秘密中使用的 ID、时间戳和过期值。

C# 代码:

  1. 秘密,一个串联的查询字符串值,需要加密的内容:

     string secret = "id=jsmith123&timestamp=2022-07-06t11:10:43&expiration=2022-07-06t11:15:43";
  2. 钥匙:

     string key = "C000000000000000"; //16 character-long
  3. ASCII 编码的秘密和密钥转换为字节数组:

     System.Text.ASCIIEncoding encoding = new System.Text.ASCIIEncoding(); byte[] encodedSecret = encoding.GetBytes(secret); byte[] encodedKey = encoding.GetBytes(key);
  4. 选项 1:Rijndael

     // Call the generate token method: string token = GenerateRijndaelSecureToken(encodedSecret, encodedKey);
     private string GenerateRijndaelSecureToken(byte[] encodedSecret, byte[] encodedKey) { Rijndael rijndael = Rijndael.Create(); // the encodedKey must be a valid length so we pad it until it is (it checks // number of bits) while (encodedKey.Length * 8 < rijndael.KeySize) { byte[] tmp = new byte[encodedKey.Length + 1]; encodedKey.CopyTo(tmp, 0); tmp[tmp.Length - 1] = (byte)'\0'; encodedKey = tmp; } rijndael.Key = encodedKey; rijndael.Mode = CipherMode.ECB; rijndael.Padding = PaddingMode.Zeros; ICryptoTransform ict = rijndael.CreateEncryptor(); byte[] result = ict.TransformFinalBlock(encodedSecret, 0, encodedSecret.Length); // convert the encodedSecret to a Base64 string to return return Convert.ToBase64String(result); }
  5. 选项 2:三重 DES

     // Call the generate token method: string token = GenerateSecureTripleDesToken(encodedSecret, encodedKey);
     private string generateSecureTripleDesToken(byte[] encodedSecret, byte[] encodedKey) { // Generate the secure token (this implementation uses 3DES) TripleDESCryptoServiceProvider tdes = new TripleDESCryptoServiceProvider(); // the encodedKey must be a valid length so we pad it until it is (it checks // number of bits) while (encodedKey.Length * 8 < tdes.KeySize) { byte[] tmp = new byte[encodedKey.Length + 1]; encodedKey.CopyTo(tmp, 0); tmp[tmp.Length - 1] = (byte) '\0'; encodedKey = tmp; } tdes.Key = encodedKey; tdes.Mode = CipherMode.ECB; tdes.Padding = PaddingMode.Zeros; ICryptoTransform ict = tdes.CreateEncryptor(); byte[] result = ict.TransformFinalBlock(encodedSecret, 0, encodedSecret.Length); // convert the encodedSecret to a Base64 string to return return Convert.ToBase64String(result); }

PHP 8码:

public $cipher_method = "AES-256-ECB";
// Will not work:
//$secret = "id=jsmith12&timestamp=2022-07-06t11:10:43&expiration=2022-07-06t11:15:43";
    
// Will work:
//$secret = "id=jsmith&timestamp=2022-07-06t11:10:43&expiration=2022-07-06t11:15:43";
    
$key = "C000000000000000";
    
$token = openssl_encrypt($secret, $cipher_method, $key);

有两件事需要注意:

  • C# 代码用 0x00 值填充密钥到所需长度,即 AES-256 为 256 位,3DES 为 192 位。 由于 PHP/OpenSSL 会自动用 0x00 值填充太短的键,因此不需要在 PHP 代码中显式实现(尽管它会更透明)。
  • C# 代码使用零填充。 另一方面,PHP/OpenSSL 应用 PKCS#7 填充。 由于 PHP/OpenSSL 不支持零填充,因此必须使用OPENSSL_ZERO_PADDING禁用默认的 PKCS#7 填充(注意:这不会启用零填充,标志的名称选择不当)并且必须显式实现零填充,例如使用:
function zeropad($data, $bs) {
    $length = ($bs - strlen($data) % $bs) % $bs;
    return $data . str_repeat("\0", $length);
}

这里$bs是块大小(AES 为 16 字节,DES/3DES 为 8 字节)。

不需要进一步的改变:一个可能的实现是:

$cipher_method = "aes-256-ecb"; // for AES (32 bytes key)
//$cipher_method = "des-ede3";  // for 3DES (24 bytes key)

// Zero pad plaintext (explicitly)
$bs = 16;  // for AES
//$bs = 8; // for 3DES
$secret = zeropad($secret, $bs);
    
// Zero pad key (implicitly)
$key = "C000000000000000";
    
$token = openssl_encrypt($secret, $cipher_method, $key, OPENSSL_ZERO_PADDING); // disable PKCS#7 default padding, Base64 encode (implicitly)
print($token . PHP_EOL); 

以这种方式生成的密文可以使用链接的网站进行解密(无论其长度如何)。


错误的填充会导致 web 站点上的解密失败(至少不能可靠地成功)。 但是,只有当明文大于 71 字节(即使只考虑 65 到 79 字节之间的范围)时,解密才会失败的逻辑是不正确的。 例如,解密也因 66 字节而失败。 页面源提供了比 GUI 更多的信息:

Could not read \u0027expiration\u0027 as a date: 2022-07-06t11:15:43\u000e\u000e\u000e\u000e\u000e\u000e\u000e\u000e\u000e\u000e\u000e\u000e\u000e\u000e

问题是(如预期的那样)末尾的 PKCS#7 填充字节:66 字节的 14 个 0x0e 值。
只有知道 web 站点的解密逻辑,才能可靠地回答为什么解密对某些填充字节有效而对其他填充字节无效。 然而,最终,确切的原因并不重要。


请注意,应用的密钥扩展是不安全的。 此外,ECB 不安全,3DES 已过时,零填充不可靠。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM