Flutter使用pointycastle解密Modulr安全令牌时MAC错误求助
解决Flutter中Modulr安全令牌解密的MAC验证错误问题
Modulr安全令牌解密流程(官方文档翻译)
- 生成RSA密钥对,将公钥提供给Modulr平台;
- Modulr使用该公钥,通过RSA ECB-OAEP-SHA256算法加密对称密钥,同时生成初始化向量(IV),再用该对称密钥通过AES-GCM无填充算法加密敏感卡数据(即待解密的token);
- API返回三个核心字段:
encryptedSymmetricKey(加密后的对称密钥)、initialisationVector(IV)、token(加密后的敏感数据);- 本地解密流程:
- 用RSA私钥解密
encryptedSymmetricKey得到原始对称密钥;- 结合对称密钥、IV,用AES-GCM算法解密
token并验证MAC。
MAC验证错误的核心原因及解决步骤
出现SecretBox has wrong message authentication code (MAC)错误,本质是AES-GCM解密时计算的MAC和返回的MAC不匹配,根源通常出在RSA解密对称密钥错误、AES-GCM参数处理不当这两个环节,以下是具体排查和修复方案:
1. 确保RSA解密对称密钥的正确性
Modulr用的是RSA ECB-OAEP-SHA256加密对称密钥,pointycastle默认OAEP可能用SHA-1哈希,必须显式指定SHA-256,否则解密出的对称密钥会完全错误,直接导致后续MAC验证失败。
正确的RSA解密代码:
import 'dart:convert'; import 'dart:typed_data'; import 'package:pointycastle/api.dart'; import 'package:pointycastle/asymmetric/oaep.dart'; import 'package:pointycastle/digests/sha256.dart'; import 'package:pointycastle/asymmetric/rsa.dart'; // 加载PKCS#8格式的RSA私钥(若为PEM格式,需先去除头部尾部注释) RSAPrivateKey loadPrivateKey(String privateKeyPem) { final pemBytes = utf8.encode(privateKeyPem .replaceAll('-----BEGIN PRIVATE KEY-----', '') .replaceAll('-----END PRIVATE KEY-----', '') .replaceAll('\n', '')); final privateKey = RSAKeyParser().parse(base64.decode(pemBytes)) as RSAPrivateKey; return privateKey; } // 解密encryptedSymmetricKey Uint8List decryptSymmetricKey(String encryptedKeyBase64, RSAPrivateKey privateKey) { final encryptedBytes = base64.decode(encryptedKeyBase64); // 显式指定OAEP用SHA-256哈希 final cipher = OAEPEncoding(RSAEngine()) ..init(false, PrivateKeyParameter<RSAPrivateKey>(privateKey, parameters: OAEPParameters(SHA256Digest(), null, null))); return cipher.process(encryptedBytes); }
2. 修正AES-GCM解密的参数处理
AES-GCM的MAC验证失败,常见错误点包括:IV长度错误、密文与MAC拆分错误、编码不一致。
正确的AES-GCM解密代码:
import 'dart:convert'; import 'dart:typed_data'; import 'package:pointycastle/api.dart'; import 'package:pointycastle/block/aes_fast.dart'; import 'package:pointycastle/macs/gcm.dart'; import 'package:collection/equality.dart'; String decryptSecureToken(String tokenBase64, Uint8List symmetricKey, String ivBase64) { final iv = base64.decode(ivBase64); // 验证IV长度:GCM推荐12字节,Modulr返回的IV需符合此要求 if (iv.length != 12) { throw Exception("Invalid IV length: must be 12 bytes"); } final tokenBytes = base64.decode(tokenBase64); // Modulr返回的token是「密文+MAC」组合,MAC固定16字节,需拆分 final ciphertext = tokenBytes.sublist(0, tokenBytes.length - 16); final receivedMac = tokenBytes.sublist(tokenBytes.length - 16); // 初始化AES-GCM cipher final gcm = GCMBlockCipher(AESFastEngine()) ..init(false, AEADParameters( KeyParameter(symmetricKey), 128, // MAC长度(128位=16字节) iv, null, // 若Modulr要求附加数据(AAD),需传入对应字节,否则传null )); // 解密密文 final decryptedBytes = gcm.process(ciphertext); // 验证MAC final calculatedMac = gcm.mac; if (!const ListEquality().equals(receivedMac, calculatedMac)) { throw Exception("SecretBox has wrong message authentication code (MAC)"); } return utf8.decode(decryptedBytes); }
3. 额外排查点
- 私钥格式:确保私钥是PKCS#8格式,若为PKCS#1格式(PEM头部为
-----BEGIN RSA PRIVATE KEY-----),需转换为PKCS#8后再加载; - Base64编码:确认所有解码用标准Base64,若API返回的是URL-safe Base64,需用
base64UrlDecode替代base64.decode; - 附加数据(AAD):若Modulr文档中指定需传入AAD(比如请求的
requestId等参数),需将AAD转换为字节后传入AEADParameters的第4个参数; - 对称密钥长度:AES密钥长度需和Modulr使用的一致(通常256位=32字节),解密后可打印对称密钥长度验证。
内容的提问来源于stack exchange,提问作者Adrien Audouard
相关产品推荐
相关产品推荐

