Java转C#:APDU安全消息解密迁移遇MAC验证及900状态问题
APDU安全消息解密的C#迁移实现(修复填充与状态字问题)
问题分析
当前代码缺少ISO 9797-1方法2的去填充逻辑,且状态字解析错误,导致解密后返回异常的900状态。以下是对齐open-ecard Java原逻辑的完整C#实现。
完整修正代码
using System; using System.Security.Cryptography; using System.Linq; public class SecureMessaging { public byte[] DecryptSecureMessaging(byte[] encryptedData, byte[] sessionKey, byte[] iv) { if (encryptedData == null || encryptedData.Length == 0) throw new ArgumentNullException(nameof(encryptedData)); // 适配3DES(PACE默认),若用AES则修改密钥长度校验为16/24/32 if (sessionKey == null || sessionKey.Length != 24) throw new ArgumentException("Invalid 3DES session key length (must be 24 bytes)", nameof(sessionKey)); if (iv == null || iv.Length != 8) throw new ArgumentException("Invalid IV length for 3DES (must be 8 bytes)", nameof(iv)); byte[] decryptedRaw; // 对齐Java原代码的DESede/CBC/NoPadding配置 using (var des3 = TripleDES.Create()) { des3.Key = sessionKey; des3.IV = iv; des3.Mode = CipherMode.CBC; des3.Padding = PaddingMode.None; var decryptor = des3.CreateDecryptor(); decryptedRaw = decryptor.TransformFinalBlock(encryptedData, 0, encryptedData.Length); } // 移除ISO 9797-1方法2填充 var decryptedData = RemoveIso97971Padding(decryptedRaw); // 分离响应数据与状态字(APDU安全消息最后2字节为状态字) if (decryptedData.Length < 2) throw new InvalidOperationException("Decrypted data too short to contain status word"); var statusWord = decryptedData.Skip(decryptedData.Length - 2).Take(2).ToArray(); var responseData = decryptedData.Take(decryptedData.Length - 2).ToArray(); // 验证正常状态字0x9000(原问题中900是错误解析导致) if (statusWord[0] != 0x90 || statusWord[1] != 0x00) throw new InvalidOperationException($"Operation failed with status: 0x{statusWord[0]:X2}{statusWord[1]:X2}"); return responseData; } // 实现ISO 9797-1方法2去填充 private byte[] RemoveIso97971Padding(byte[] data) { if (data.Length == 0) return data; int padLength = data[data.Length - 1]; // 校验填充合法性:所有填充字节必须等于填充长度 for (int i = data.Length - padLength; i < data.Length; i++) { if (data[i] != padLength) throw new CryptographicException("Invalid ISO 9797-1 padding"); } return data.Take(data.Length - padLength).ToArray(); } public static void Main() { // 替换为实际测试数据 var encrypted = Convert.FromHexString("YOUR_ENCRYPTED_APDU_DATA"); var key = Convert.FromHexString("YOUR_24BYTE_3DES_KEY"); var iv = Convert.FromHexString("YOUR_8BYTE_IV"); var sm = new SecureMessaging(); try { var result = sm.DecryptSecureMessaging(encrypted, key, iv); Console.WriteLine($"Decrypted Data: {Convert.ToHexString(result)}"); } catch (Exception ex) { Console.WriteLine($"Error: {ex.Message}"); } } }
核心修正说明
- 算法对齐:严格匹配Java原代码的
DESede/CBC/NoPadding配置,C#中禁用自动填充,手动处理填充逻辑。 - 填充逻辑补全:实现了ISO 9797-1方法2的去填充,这是代码中缺失的核心部分,确保解密后数据还原为原始内容。
- 状态字修复:原问题中返回900是因为错误解析了状态字(正确的成功状态是
0x9000,即十进制36864),修正后从解密数据末尾提取2字节作为状态字并验证。 - 参数校验:增加密钥、IV长度校验,避免因参数不匹配导致的解密失败。
排查建议
- 若状态字仍异常,检查会话密钥、IV与Java端是否完全一致(字节顺序、长度)
- 若填充校验失败,确认加密端是否使用ISO 9797-1方法2填充,或调整去填充逻辑匹配实际规则
- 若使用AES而非3DES,替换
TripleDES为Aes类,并调整密钥(16/24/32字节)和IV(16字节)的长度校验
内容的提问来源于stack exchange,提问作者Bouls
相关产品推荐
相关产品推荐

