簡體   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