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

Java解密SJCL加密消息报错:CCM模式MAC校验失败求助

解决Java解密SJCL默认AES-CCM消息时的MAC校验失败问题

我之前在对接SJCL加密的客户端时,也踩过这个mac check in CCM failed的大坑!核心原因就是SJCL的默认参数和Java标准库的CCM配置很容易出现细微的不匹配,导致MAC校验通不过。下面是我总结的排查和解决步骤,你可以逐一验证:

1. 先对齐SJCL的默认核心参数

SJCL默认使用AES-CCM模式,参数如下,Java端必须完全匹配:

  • 密钥长度:128位(如果是密码派生的话,默认ks=128)
  • Nonce(IV)长度:12字节(96位,SJCL默认生成randomWords(3,0),3个4字节的Word就是12字节)
  • MAC(Tag)长度:64位(8字节,默认ts=64)
  • 密钥派生算法:PBKDF2,迭代次数1000次,哈希算法SHA-256,盐长度8字节

2. 处理SJCL的加密输出格式(关键!)

很多人容易忽略:SJCL默认调用sjcl.encrypt(password, data)输出的是JSON字符串,不是单纯的Base64密文。典型的输出结构如下:

{
  "v":1,
  "iter":1000,
  "ks":128,
  "ts":64,
  "mode":"ccm",
  "cipher":"aes",
  "salt":"[Base64编码的8字节盐]",
  "iv":"[Base64编码的12字节Nonce]",
  "ct":"[Base64编码的密文+MAC拼接数据]"
}

Java端必须先解析这个JSON,提取对应字段,不能直接把整个JSON当成密文处理!

3. Java端的具体实现步骤

步骤1:解析SJCL的JSON输出

用你熟悉的JSON库(比如Jackson、Gson)解析字符串,提取salt、iv、ct、iter、ts这些字段。

步骤2:用PBKDF2派生一致的密钥

如果客户端是用密码加密的,Java端必须用相同的参数派生密钥:

import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.PBEKeySpec;
import java.nio.charset.StandardCharsets;
import java.util.Base64;

// 从JSON提取的参数
String password = "客户端使用的密码";
String saltBase64 = "JSON里的salt字段";
int iter = 1000;
int keySize = 128;

// 解码盐
byte[] salt = Base64.getDecoder().decode(saltBase64);

// 派生密钥
PBEKeySpec keySpec = new PBEKeySpec(password.toCharArray(), salt, iter, keySize);
SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256");
SecretKey secretKey = factory.generateSecret(keySpec);
byte[] keyBytes = secretKey.getEncoded();

步骤3:初始化CCM模式的Cipher并解密

注意Nonce和MAC长度必须和SJCL完全一致:

import javax.crypto.Cipher;
import javax.crypto.spec.CCMParameterSpec;
import javax.crypto.spec.SecretKeySpec;

// 从JSON提取的参数
String ivBase64 = "JSON里的iv字段";
String ctBase64 = "JSON里的ct字段";
int tagLengthBits = 64; // 对应SJCL的ts=64

// 解码Nonce和密文+MAC
byte[] nonce = Base64.getDecoder().decode(ivBase64);
byte[] ciphertextWithTag = Base64.getDecoder().decode(ctBase64);

// 初始化CCM模式
Cipher cipher = Cipher.getInstance("AES/CCM/NoPadding");
CCMParameterSpec ccmSpec = new CCMParameterSpec(tagLengthBits, nonce);
cipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(keyBytes, "AES"), ccmSpec);

// 解密并自动校验MAC
byte[] plaintextBytes = cipher.doFinal(ciphertextWithTag);
String plaintext = new String(plaintextBytes, StandardCharsets.UTF_8);

4. 常见坑点排查

  • Base64解码错误:SJCL用的是标准Base64,不要用URL安全的Base64解码(除非客户端特意配置了)
  • 编码不一致:SJCL默认把明文转成UTF-8字节加密,Java解密后必须用UTF-8转字符串,别用平台默认编码
  • Nonce长度不对:如果客户端自定义了Nonce长度,必须同步修改Java的CCMParameterSpec里的Nonce字节数组长度
  • MAC长度不匹配:如果SJCL修改了ts参数,Java端要对应修改CCMParameterSpec的第一个参数(单位是比特)

内容的提问来源于stack exchange,提问作者Claudiu

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.20 07:01:45