前言
跨語言系統整合(ASP/ASP.NET、PHP、Java、Python、Go)常面臨加密演算法相容性問題。本文提供 C# 實作的 MD5(16/32位)、AES(ECB/CBC 模式)標準代碼,確保與其他語言互通。
⚠️ 安全性警示
MD5 已被破解,不可用於密碼儲存、簽名驗證、安全關鍵場景。
僅適用於:非安全檢查和、快取鍵、資料完整性非惡意竄改檢查。
DES 已淘汰(56-bit 金鑰可暴力破解),請改用 AES-256。
本文保留 DES 代碼僅供舊系統相容維護。
MD5 相容性實作
using System;
using System.Security.Cryptography;
using System.Text;
public static class Md5Compat
{
///
/// 相容舊版 ASP/動網/DVBBS 的 16/32 位 MD5
/// 核心差異:取 hash 陣列的不同區段
///
public static string Compute(string input, int length = 32)
{
if (string.IsNullOrEmpty(input)) return string.Empty;
byte[] bytes = Encoding.ASCII.GetBytes(input); // 關鍵:ASCII 編碼
byte[] hash = MD5.Create().ComputeHash(bytes);
var sb = new StringBuilder();
switch (length)
{
case 16: // 取中間 8 bytes (索引 4-11) → 16 字元
for (int i = 4; i < 12; i++)
sb.Append(hash[i].ToString("x2"));
break;
case 32: // 完整 16 bytes → 32 字元
for (int i = 0; i < 16; i++)
sb.Append(hash[i].ToString("x2"));
break;
default: // 標準完整輸出
for (int i = 0; i < hash.Length; i++)
sb.Append(hash[i].ToString("x2"));
break;
}
return sb.ToString();
}
// 標準 MD5 (32 位小寫)
public static string Standard(string input) => Compute(input, 32);
}
跨語言驗證對照表
| 語言/實作 | 輸入 | 16位輸出 | 32位輸出 |
| C# Md5Compat.Compute("test", 16) | test | 098f6bcd4621d373 | 098f6bcd4621d373cade4e832627b4f6 |
| PHP md5("test") | test | - | 098f6bcd4621d373cade4e832627b4f6 |
| Python hashlib.md5(b"test").hexdigest() | test | - | 098f6bcd4621d373cade4e832627b4f6 |
| Java MessageDigest.getInstance("MD5") | test | - | 098f6bcd4621d373cade4e832627b4f6 |
| ASP 動網 16位 | test | 098f6bcd4621d373 | - |
AES 加密 (取代 DES) - 現代標準
using System;
using System.IO;
using System.Security.Cryptography;
using System.Text;
public static class AesEncryption
{
// 金鑰必須 16/24/32 bytes (AES-128/192/256)
// IV 必須 16 bytes (AES 區塊大小)
private static readonly byte[] DefaultKey = Encoding.UTF8.GetBytes("0123456789abcdef0123456789abcdef"); // 32 bytes = AES-256
private static readonly byte[] DefaultIV = Encoding.UTF8.GetBytes("abcdef0123456789"); // 16 bytes
///
/// AES-CBC 加密 (推薦模式),輸出 Base64
///
public static string Encrypt(string plainText, byte[] key = null, byte[] iv = null)
{
key ??= DefaultKey;
iv ??= DefaultIV;
using (Aes aes = Aes.Create())
{
aes.Key = key;
aes.IV = iv;
aes.Mode = CipherMode.CBC;
aes.Padding = PaddingMode.PKCS7; // 標準填充,跨語言相容
using (MemoryStream ms = new MemoryStream())
using (CryptoStream cs = new CryptoStream(ms, aes.CreateEncryptor(), CryptoStreamMode.Write))
using (StreamWriter sw = new StreamWriter(cs, Encoding.UTF8))
{
sw.Write(plainText);
}
return Convert.ToBase64String(ms.ToArray());
}
}
///
/// AES-CBC 解密
///
public static string Decrypt(string cipherTextBase64, byte[] key = null, byte[] iv = null)
{
key ??= DefaultKey;
iv ??= DefaultIV;
byte[] cipherBytes = Convert.FromBase64String(cipherTextBase64);
using (Aes aes = Aes.Create())
{
aes.Key = key;
aes.IV = iv;
aes.Mode = CipherMode.CBC;
aes.Padding = PaddingMode.PKCS7;
using (MemoryStream ms = new MemoryStream(cipherBytes))
using (CryptoStream cs = new CryptoStream(ms, aes.CreateDecryptor(), CryptoStreamMode.Read))
using (StreamReader sr = new StreamReader(cs, Encoding.UTF8))
{
return sr.ReadToEnd();
}
}
}
}
舊版 DES 相容代碼 (僅供維護)
public static class DesCompat
{
// 關鍵:ECB 模式 + ZeroPadding 才能與 Perl/PHP/Java 老版 DES 相容
private static readonly byte[] DesKey = new byte[]
{
0x82, 0xBC, 0xA1, 0x6A, 0xF5, 0x87, 0x3B, 0xE6
}; // 8 bytes = 64 bits (含 8 位 parity 實際 56 bits)
public static string Encrypt(string plainText)
{
using (var des = DES.Create())
{
des.Key = DesKey;
des.Mode = CipherMode.ECB; // 關鍵:ECB 模式
des.Padding = PaddingMode.Zeros; // 關鍵:ZeroPadding
using (var ms = new MemoryStream())
using (var cs = new CryptoStream(ms, des.CreateEncryptor(), CryptoStreamMode.Write))
{
byte[] input = Encoding.Unicode.GetBytes(plainText); // 關鍵:Unicode 編碼
cs.Write(input, 0, input.Length);
cs.FlushFinalBlock();
return Convert.ToBase64String(ms.ToArray());
}
}
}
public static string Decrypt(string cipherText)
{
using (var des = DES.Create())
{
des.Key = DesKey;
des.Mode = CipherMode.ECB;
des.Padding = PaddingMode.Zeros;
byte[] input = Convert.FromBase64String(cipherText);
using (var ms = new MemoryStream(input))
using (var cs = new CryptoStream(ms, des.CreateDecryptor(), CryptoStreamMode.Read))
using (var sr = new StreamReader(cs, Encoding.Unicode))
{
return sr.ReadToEnd();
}
}
}
}
跨語言 AES 參數對照表 (CBC + PKCS7 + Base64)
| 參數 | C# | Java | Python | PHP | Node.js |
| 演算法 | AES/CBC/PKCS7Padding | AES/CBC/PKCS5Padding | AES.MODE_CBC | aes-256-cbc | aes-256-cbc |
| 金鑰長度 | 32 bytes | 32 bytes | 32 bytes | 32 bytes | 32 bytes |
| IV 長度 | 16 bytes | 16 bytes | 16 bytes | 16 bytes | 16 bytes |
| 編碼 | UTF-8 → Base64 | UTF-8 → Base64 | UTF-8 → Base64 | UTF-8 → Base64 | UTF-8 → Base64 |
| 填充 | PKCS7 | PKCS5 (同 PKCS7) | 自動 | 自動 | 自動 |
密碼儲存正確做法:PBKDF2 / Argon2 / Bcrypt
using System.Security.Cryptography;
public static class PasswordHash
{
// 驗證密碼
public static bool Verify(string password, string hashBase64)
{
byte[] hashBytes = Convert.FromBase64String(hashBase64);
// 格式: [salt(16)][iterations(4)][hash(32)]
byte[] salt = hashBytes[0..16];
int iterations = BitConverter.ToInt32(hashBytes[16..20]);
byte[] expectedHash = hashBytes[20..52];
using (var pbkdf2 = new Rfc2898DeriveBytes(password, salt, iterations, HashAlgorithmName.SHA256))
{
byte[] actualHash = pbkdf2.GetBytes(32);
return CryptographicOperations.FixedTimeEquals(actualHash, expectedHash);
}
}
// 雜湊密碼
public static string Hash(string password, int iterations = 100000)
{
byte[] salt = RandomNumberGenerator.GetBytes(16);
using (var pbkdf2 = new Rfc2898DeriveBytes(password, salt, iterations, HashAlgorithmName.SHA256))
{
byte[] hash = pbkdf2.GetBytes(32);
var result = new byte[52];
Buffer.BlockCopy(salt, 0, result, 0, 16);
Buffer.BlockCopy(BitConverter.GetBytes(iterations), 0, result, 16, 4);
Buffer.BlockCopy(hash, 0, result, 20, 32);
return Convert.ToBase64String(result);
}
}
}
總結
- MD5:僅用於非安全場景相容,16位取 hash[4..11],32位取完整 hash
- DES:已淘汰,相容需 ECB + ZeroPadding + Unicode 編碼 + Base64
- AES:現代標準,CBC + PKCS7 + Base64,金鑰 32 bytes,IV 16 bytes
- 密碼:嚴禁 MD5/SHA 直接雜湊,必須用 PBKDF2/Argon2/Bcrypt + 高迭代 + 隨機鹽
- 金鑰管理:勿硬編碼,使用 Key Vault、環境變數、HSM