如何在PHP中基于32位Hex密钥生成ISO/IEC 9797-1 MAC?
PHP实现符合ISO/IEC 9797-1标准的MAC计算
问题背景
需要基于XML内容和32位十六进制密钥生成ISO/IEC 9797-1标准MAC,要求与指定在线工具计算结果一致(填充方法1),但现有代码无法得到正确输出。
现有代码的问题
- 密钥处理错误:直接对十六进制字符串做子串截取,未先转换为二进制密钥,导致实际使用的是ASCII字符而非正确的加密密钥。
- 填充逻辑错误:仅补0字节,未按照填充方法1的要求先添加
0x80再补0,不符合标准规范。 - CBC-MAC实现错误:直接用ECB模式加密整个消息,未遵循CBC-MAC逐块异或前一次加密结果的核心逻辑。
- 最终变换错误:错误地对消息最后一块做3DES加密,而非对CBC-MAC的中间结果执行密钥解密再加密的标准变换。
正确实现代码
function calculateISO9797MAC(string $messageHex, string $keyHex): string { // 将十六进制消息和密钥转换为二进制 $message = hex2bin($messageHex); $keyBin = hex2bin($keyHex); // 拆分密钥:K1(前8字节)、K2(后8字节) $k1 = substr($keyBin, 0, 8); $k2 = substr($keyBin, 8, 8); // 应用填充方法1:先加0x80,再补0至8字节的整数倍 $message .= chr(0x80); $paddingLength = 8 - (strlen($message) % 8); if ($paddingLength !== 8) { $message .= str_repeat(chr(0x00), $paddingLength); } // 计算CBC-MAC(初始向量为全0) $mac = str_repeat(chr(0x00), 8); // 初始IV $blockCount = strlen($message) / 8; for ($i = 0; $i < $blockCount; $i++) { $block = substr($message, $i * 8, 8); // 当前块与前一次MAC结果异或 $xorBlock = ''; for ($j = 0; $j < 8; $j++) { $xorBlock .= chr(ord($block[$j]) ^ ord($mac[$j])); } // 用K1加密异或后的块 $mac = openssl_encrypt($xorBlock, 'DES-ECB', $k1, OPENSSL_RAW_DATA); } // 执行最终变换:K2解密 -> K1加密(ISO/IEC 9797-1算法3) $decrypted = openssl_decrypt($mac, 'DES-ECB', $k2, OPENSSL_RAW_DATA); $finalMac = openssl_encrypt($decrypted, 'DES-ECB', $k1, OPENSSL_RAW_DATA); // 转换为十六进制大写,取前8个字符(对应4字节,匹配工具输出) return strtoupper(substr(bin2hex($finalMac), 0, 8)); } // 使用示例 $xmlContent = '<your-xml-content-here>'; $messageHex = bin2hex($xmlContent); // 将XML转换为十六进制 $keyHex = 'your-32-hex-characters-key'; // 32位十六进制密钥 $calculatedMAC = calculateISO9797MAC($messageHex, $keyHex); echo $calculatedMAC;
关键说明
- 密钥转换:必须将十六进制密钥转换为二进制,否则无法正确用于DES加密。
- 填充方法1:严格遵循标准,无论原消息长度是否为8字节倍数,都需先添加
0x80,再补0至块大小。 - CBC-MAC逻辑:初始向量为全0,每块需与前一次加密结果异或后再加密,确保符合标准计算流程。
- 最终变换:通过K2解密中间MAC结果,再用K1加密,对应ISO/IEC 9797-1的算法3,是匹配工具结果的核心步骤。
内容的提问来源于stack exchange,提问作者Mario de M. Barros Neto
相关产品推荐
相关产品推荐

