TypeScript 高级能力
TypeScript 高级能力
本章面向受限 JavaScript 宿主、协议诊断和需要显式维护状态的调用方。Node.js 18 与现代浏览器的常规业务接入不需要自定义 RNG、TextCodec 或 ZUCState。
可执行案例
// 1. 检查环境:记录 BigInt、文本 codec 和系统随机源是否可用。
const environment = getEnvReport();
assert.equal(environment.hasBigInt, true);
assert.equal(environment.hasTextEncoder, true);
assert.equal(environment.hasTextDecoder, true);
// 2. 注入测试随机源:确定性实现只用于测试,并在 finally 中立即清除。
configureRNG('strict');
setCustomRNG((length) => new Uint8Array(length).fill(0xa5));
try {
assert.equal(hasCustomRNG(), true);
assert.deepEqual(getRandomBytes(4), Uint8Array.of(0xa5, 0xa5, 0xa5, 0xa5));
} finally {
clearCustomRNG();
}
assert.equal(hasCustomRNG(), false);
// 3. 随机源失败断言:宿主返回的长度不正确时必须拒绝该结果。
setCustomRNG((length) => new Uint8Array(length - 1));
try {
assert.throws(() => getRandomBytes(16));
} finally {
clearCustomRNG();
}
// 4. 注入 UTF-8 codec:受限宿主实现必须保持 encode/decode 往返一致。
const encoder = new TextEncoder();
const decoder = new TextDecoder();
setTextCodec({
encode: (input) => encoder.encode(input),
decode: (bytes) => decoder.decode(bytes),
});
const plaintext = '订单 GMKIT-DEMO-0001';
assert.equal(bytesToString(stringToBytes(plaintext)), plaintext);
// 5. 转换 SM2 签名:64 字节 raw 的 r||s 转为 DER,再无损转回 raw。
const rawSignature = `${'01'.padStart(64, '0')}${'02'.padStart(64, '0')}`;
const derSignature = rawToDer(rawSignature);
assert.equal(derToRaw(derSignature), rawSignature);
// 6. DER 失败断言:截断的 DER 签名必须被拒绝。
assert.throws(() => derToRaw(derSignature.subarray(0, derSignature.length - 1)));
// 7. 复用增量摘要:digest() 自动重置,reset() 主动丢弃未完成消息。
const incremental = new SM3().update('ab');
incremental.reset().update('abc');
assert.equal(
incremental.digest(),
'66c7f0f462eeedd9d1f2d46bdc10e4e2'
+ '4167c4875cf2f7a2297da02b8f4ba8e0',
);
// 8. SM4-GCM 实例加密:实例保存 key/mode,当前消息仍要设置独立 nonce。
const sm4 = new SM4('0123456789abcdeffedcba9876543210', {
mode: CipherMode.GCM,
padding: PaddingMode.NONE,
iv: '000102030405060708090a0b',
});
const encrypted = sm4.encrypt('order=GMKIT-DEMO-0001&amount=88.00', {
aad: 'tenant=demo;schema=1',
outputFormat: OutputFormat.BASE64,
});
// 9. SM4-GCM 实例解密:使用相同 nonce 和 AAD 完成认证后恢复明文。
assert.equal(sm4.decrypt(encrypted, {
aad: 'tenant=demo;schema=1',
inputFormat: InputFormat.BASE64,
tagFormat: InputFormat.BASE64,
}), 'order=GMKIT-DEMO-0001&amount=88.00');
// 10. 推进 ZUC 低层状态:initialize 后每次 generateKeyword 都消费下一个 word。
const zucState = new ZUCState();
zucState.initialize(hexToBytes('00'.repeat(16)), hexToBytes('00'.repeat(16)));
assert.equal(zucState.generateKeyword(), 0x27bede74);
assert.equal(zucState.generateKeyword(), 0x018082da);环境报告
getEnvReport() 是即时探测,不修改配置:
在 ESM 中 hasNodeCrypto 可能为 false,但 hasWebCrypto 为 true,此时随机源仍可用。最终验证应调用 configureRNG('strict') 后执行一次 getRandomBytes,不能只看单一字段。
自定义随机源
setCustomRNG(fn) 的优先级高于系统随机源。适用场景是小程序、嵌入式 JS 宿主等已经拥有平台 CSPRNG、但没有暴露 Web Crypto 的环境。
传入函数必须:
- 接受正整数 byte 长度;
- 每次返回精确长度的
Uint8Array; - 使用平台密码学安全随机源;
- 不缓存或重复输出;
- 不把测试中的确定性实现带入生产构建。
库会校验返回类型和长度,但不能判断随机质量。测试结束用 clearCustomRNG() 清除;进程启动可用 hasCustomRNG() 防止测试 fixture 泄漏。
自定义 TextCodec
setTextCodec({ encode, decode }) 改变当前模块实例的字符串转换行为。它适用于没有 TextEncoder/TextDecoder 的受限宿主。
encode 必须返回 Uint8Array,decode 必须返回字符串,并与标准 UTF-8 对以下输入保持一致:
- 中文与 ASCII 混合文本;
- U+0000;
- 四字节 Unicode 字符;
- 孤立 surrogate 和无效 UTF-8 的替换行为。
应用启动阶段只配置一次。多个请求不能并发切换不同 codec;需要不同文本规则时,先在应用层转成 Uint8Array 再调用密码 API。
SM2 签名 DER 转换
asn1ToXml 与 signatureToXml 用于诊断展示,不是稳定的跨语言序列化格式。协议传输应保存 DER/PEM/明确字段,而不是 XML 调试文本。
实例状态与并发
“实例可复用”不等于“nonce/IV 可复用”。SM4-GCM、SM4-CCM 和 ZUC 的唯一性要求仍由应用协议保证。
SM2 密钥交换
双方角色、长期密钥、临时密钥、身份和确认标签缺一不可。可执行案例与参数矩阵见 TypeScript SM2 手册的密钥交换章节。
自定义曲线边界
SM2CurveParams 是已发布的兼容声明。0.10.1 固定使用标准 sm2p256v1;省略该对象即可。传入不同的 p/a/b/Gx/Gy/n 会抛错,不能借此启用其他椭圆曲线。
底层成员的完整签名见 TypeScript 通用 API、SM2 API、SM4 API 和 ZUC API。