HarmonyOS 7 ECIES 示例能解密,连续发两条消息却有风险?AES-GCM 的 IV 不能复用

HarmonyOS 7 ECIES 示例能解密,连续发两条消息却有风险?AES-GCM 的 IV 不能复用
华为在 API 26.0.0 的 ECIES 开发指导里给出了一次完整的演示:ECC 密钥协商,X963KDF 导出材料,再用 AES-GCM 加密和解密一条消息。演示一次能够解密,不等于把相同参数原封不动放进消息循环也安全。
我在把示例改成“同一会话连续加密多条消息”时,最先检查的不是加密函数是否返回成功,而是每次调用的 key + IV 是否重复。NIST SP 800-38D 对 GCM 明确要求:同一密钥下的 IV 必须满足唯一性约束。本文不把官网的一次性演示说成错误;风险发生在我们把示例中的派生 IV 固定下来,又在同一派生密钥下反复加密时。
适用边界:HarmonyOS 的 ECIES 指导标注从 API 26.0.0 开始。本文在本机运行了 Node.js AES-GCM 实验,验证的是 GCM 的通用密码学约束,不是声称在 API 26 设备上编译或跑通了
CryptoArchitectureKit。生产密钥生命周期和 IV 分配必须再经过安全评审。
先看官方示例里这两个值从哪里来
官方流程可读成四段:双方生成 ECC 密钥对,通过 createKeyAgreement('ECC256') 取得相同的共享材料;createKdf('X963KDF|SHA256') 将材料导成 32 字节;示例取前 16 字节作 GCM IV,后 16 字节作 AES-128 密钥;加密后把 GCM 认证标签交给解密端。
对于一对新生成密钥只加密这一条消息的演示,流程能够展示协商、导出、加密和认证的连接关系。但如果应用保持同一对参与协商的密钥,KDF 的输入与 info 也不变,再次从同一结果取 IV 和 AES 密钥,第二条消息将得到相同的 key + IV。GCM 不能这样用。实际工程里要把“密钥协商/派生”和“每条消息的 IV 分配”分开设计。

案例一:为什么两条消息不能共用 IV
下面的最小实验只使用 Node.js 标准库,方便任何开发机复现。它故意在同一 AES-128 密钥下复用一个 12 字节 IV,分别加密两段等长文本。注意:这段 badIv 仅用于演示禁用模式,不是可用的生产代码。
import assert from 'node:assert/strict';
import { createCipheriv, createDecipheriv, randomBytes } from 'node:crypto';
function seal(key, iv, plain) {
const cipher = createCipheriv('aes-128-gcm', key, iv);
const ciphertext = Buffer.concat([cipher.update(plain), cipher.final()]);
return { ciphertext, tag: cipher.getAuthTag(), iv };
}
function open(key, packet) {
const decipher = createDecipheriv('aes-128-gcm', key, packet.iv);
decipher.setAuthTag(packet.tag);
return Buffer.concat([decipher.update(packet.ciphertext), decipher.final()]);
}
function xor(a, b) {
assert.equal(a.length, b.length);
return Buffer.from(a.map((byte, i) => byte ^ b[i]));
}
const key = randomBytes(16);
const badIv = randomBytes(12);
const a = Buffer.from('order=000001;paid=Y');
const b = Buffer.from('order=000002;paid=N');
const badA = seal(key, badIv, a);
const badB = seal(key, badIv, b);
assert.deepEqual(xor(badA.ciphertext, badB.ciphertext), xor(a, b));
console.log('case 1: reused key+IV exposes plaintext XOR relationship');
const goodA = seal(key, randomBytes(12), a);
const goodB = seal(key, randomBytes(12), b);
assert.notDeepEqual(goodA.iv, goodB.iv);
assert.deepEqual(open(key, goodA), a);
assert.deepEqual(open(key, goodB), b);
console.log('case 2: distinct IVs decrypt independently');
const tampered = { ...goodA, ciphertext: Buffer.from(goodA.ciphertext) };
tampered.ciphertext[0] ^= 1;
assert.throws(() => open(key, tampered));
console.log('case 3: ciphertext tampering fails authentication');
保存为 gcm-iv-check.mjs,运行 node gcm-iv-check.mjs。第一行说明两段密文暴露了对应明文的异或关系;它不意味着仅凭这一步就恢复了全部明文,但已足够证明这组参数不可重复使用。后两行验证每条消息用不同 IV 可以分别解密,以及篡改密文会在认证阶段失败。
案例二:把一次性演示改成多消息格式
和 HarmonyOS API 对齐时,先保留官方的 ECC 协商与 X963KDF 派生步骤,再单独为每次 AES-GCM 加密调用分配 IV。下面只写参数管理的设计草图,避免复制整篇官方示例。它没有在 API 26 设备上编译验证,不应直接复制进生产工程:
import { cryptoFramework } from '@kit.CryptoArchitectureKit';
interface EncryptedPacket {
iv: cryptoFramework.DataBlob;
aad: cryptoFramework.DataBlob;
ciphertext: cryptoFramework.DataBlob;
tag: cryptoFramework.DataBlob;
}
async function encryptOne(
symKey: cryptoFramework.SymKey,
plain: cryptoFramework.DataBlob
): Promise<EncryptedPacket> {
const random: cryptoFramework.Random = cryptoFramework.createRandom();
const params: cryptoFramework.GcmParamsSpec = {
algName: 'GcmParamsSpec',
iv: random.generateRandomSync(12),
aad: { data: new Uint8Array(0) },
authTag: { data: new Uint8Array(16) }
};
const cipher = cryptoFramework.createCipher('AES128|GCM');
await cipher.init(cryptoFramework.CryptoMode.ENCRYPT_MODE, symKey, params);
const ciphertext: cryptoFramework.DataBlob = await cipher.update(plain);
params.authTag = await cipher.doFinal(null);
return { iv: params.iv, aad: params.aad, ciphertext, tag: params.authTag };
}
每条消息的 iv、aad、ciphertext、tag 都是这条消息自己的;解密方要用相同的 IV/AAD/tag 做认证解密,而不能重新随机生成或拿上一条消息的值。还需在协议中提供让接收方重建相同共享密钥的公钥信息。上述代码只演示每条消息的 GCM 参数管理,不包含完整密钥交换、网络传输和持久化协议,也没有处理 update/doFinal 在不同分段加密场景下的输出拼接;真正接入时要依据目标 API 26 SDK 的类型检查和端到端用例补齐。
这里选 12 字节 IV 是 GCM 常见的 96 位长度,但“调用随机函数”并不自动等于在无限条消息上有绝对唯一性。要明确同一密钥的消息数量上限、并发分配和重启后的策略;做不到时应缩短密钥生命周期或设计可证明不重复的 IV 分配方式。不要把 IV 当秘密;需要保护的是密钥,IV 需要的是同一密钥下不复用。
解密失败该查什么
把 GCM 解密失败粗暴归因于“密钥不一致”会走弯路。我的排查顺序是:
- 先对比双方协商曲线、KDF 算法、
info与导出长度是否一致。 - 再对比该条消息的 IV、AAD、密文和认证标签是否被完整传输;标签不是重新生成的随机数。
- 若仍失败,检查是否在编码、分片、持久化或跨设备同步时改变了字节序列。
- 不要在认证失败后向业务层交付“已经解出的部分明文”;直接拒绝该消息并保留非敏感排障信息。
本机三个实验验证了 GCM 行为;API 26 的 cryptoFramework 实际编译、12 字节 IV 支持情况、不同设备互通和异常码需要在具备对应 SDK 的工程中继续验收。没有这些结果前,这段接口连接只能作为设计草图,不能贴上“量产可用”的标签。
记住这一条边界
HarmonyOS 7 的 ECIES 指导很适合学习“ECC 协商 + X963KDF + AES-GCM”如何接起来;一旦从一次性演示变成多条消息,最先补的是每次加密调用的 key/IV 唯一性管理与可传输的数据封装。能解密只是功能验收,重复消息、并发、重启和篡改测试才是安全验收的起点。
参考资料(核对日期:2026-09-18):
更多推荐



所有评论(0)