Java 原始邏輯 (RFC 2104 HMAC-MD5)
public String cryptMd5(String source, String key) {
byte[] k_ipad = new byte[64];
byte[] k_opad = new byte[64];
byte[] keyb = key.getBytes("UTF-8");
byte[] value = source.getBytes("UTF-8");
Arrays.fill(k_ipad, keyb.length, 64, (byte) 0x36); // 54
Arrays.fill(k_opad, keyb.length, 64, (byte) 0x5C); // 92
for (int i = 0; i < keyb.length; i++) {
k_ipad[i] = (byte) (keyb[i] ^ 0x36);
k_opad[i] = (byte) (keyb[i] ^ 0x5C);
}
MessageDigest md = MessageDigest.getInstance("MD5");
md.update(k_ipad); md.update(value);
byte[] dg = md.digest(); md.reset();
md.update(k_opad); md.update(dg, 0, 16);
dg = md.digest();
return toHex(dg);
}
PHP 等效實作 (逐行對照)
function cryptMd5(string $source, string $key): string {
// 確保 UTF-8
if (!mb_check_encoding($source, 'UTF-8')) $source = mb_convert_encoding($source, 'UTF-8', 'auto');
if (!mb_check_encoding($key, 'UTF-8')) $key = mb_convert_encoding($key, 'UTF-8', 'auto');
// k_ipad / k_opad 初始化 (64 bytes)
$k_ipad = str_pad($key, 64, chr(0x36)); // 0x36 = 54
$k_opad = str_pad($key, 64, chr(0x5C)); // 0x5C = 92
// XOR 前 keyb.length 位元組
for ($i = 0; $i < strlen($key); $i++) {
$k_ipad[$i] = $k_ipad[$i] ^ chr(0x36);
$k_opad[$i] = $k_opad[$i] ^ chr(0x5C);
}
// MD5(k_ipad || source) -> 16 bytes raw binary
$dg = md5($k_ipad . $source, true);
// MD5(k_opad || dg[0..15]) -> 16 bytes raw binary
$dg = md5($k_opad . substr($dg, 0, 16), true);
// 輸出十六進制小寫
return bin2hex($dg);
}
驗證測試
// Java
System.out.println(cryptMd5("hello", "secret"));
// PHP
echo cryptMd5("hello", "secret");
// 兩者輸出應完全一致
關鍵細節對照表
| 步驟 | Java | PHP | 注意 |
| 編碼 | getBytes("UTF-8") | mb_convert_encoding(..., 'UTF-8') | 預設編碼不一致會導致結果不同 |
| 填充 | Arrays.fill(..., 0x36/0x5C) | str_pad(..., chr(0x36/0x5C)) | PHP str_pad 字串長度不足時填充 |
| XOR | keyb[i] ^ 0x36 | $k_ipad[$i] ^ chr(0x36) | PHP 字串按字元操作,需 chr() |
| MD5 更新 | md.update(k_ipad); md.update(value) | md5($k_ipad . $source, true) | PHP md5 直接拼接字串,true 回傳 raw binary |
| 第二輪 | md.update(k_opad); md.update(dg,0,16) | md5($k_opad . substr($dg,0,16), true) | 只取前 16 bytes (128 bits) |
| 輸出 | toHex(dg) | bin2hex($dg) | 皆為小寫十六進制 |
安全性提醒
- MD5 已被碰撞攻擊破解,**新系統請改用 HMAC-SHA256 或更強**。
- PHP 7.2+ 可用
hash_hmac('sha256', $source, $key) 一行實現,標準庫經過優化且恆定時間比對。
- 金鑰管理:使用隨機 32 位元組金鑰,定期輪換,儲存於 Vault/KMS 而非程式碼。