You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Flutter使用pointycastle解密Modulr安全令牌时MAC错误求助

解决Flutter中Modulr安全令牌解密的MAC验证错误问题

Modulr安全令牌解密流程(官方文档翻译)

  1. 生成RSA密钥对,将公钥提供给Modulr平台;
  2. Modulr使用该公钥,通过RSA ECB-OAEP-SHA256算法加密对称密钥,同时生成初始化向量(IV),再用该对称密钥通过AES-GCM无填充算法加密敏感卡数据(即待解密的token);
  3. API返回三个核心字段:encryptedSymmetricKey(加密后的对称密钥)、initialisationVector(IV)、token(加密后的敏感数据);
  4. 本地解密流程:
    • 用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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.17 08:40:34