在最广义的定义中, 密钥包装(Key Wrapping) 是指使用一个密钥(称为包装密钥)对另一个重要密钥进行加密的过程。
如第4.1节所述,密钥封装(Key Encapsulation) 通常被视为一种特定的密钥协商方式,它使用非对称加密技术:公钥用于加密对称密钥,私钥则用于解密该对称密钥以便在数据交换中使用。因此我们区分了密钥包装和密钥封装这两个概念。(注意:有些作者并不区分密钥包装——按我们的定义——与密钥封装。对这些作者而言,密钥封装只是密钥包装的另一种形式。)
密钥包装和密钥封装是常见的保护手段,既用于在非安全通道上传输密钥,也常用于密钥存储(例如存入数据库)。由于应用广泛,相关标准和最佳实践自然应运而生。美国国家标准与技术研究院(NIST)制定了使用AES等对称算法(参见《NIST特别出版物800-38F》)和 RSA 非对称密钥对进行密钥包装的标准——尽管在RSA场景中,NIST使用的术语将其描述为与密钥传输相关的包装功能。(实际上,提供各类密码学标准的NIST将密钥封装细节归类在密钥传输主题下——参见《NIST特别出版物800-56B修订版2》。)
关于密钥包装的核心安全原则是:绝不要用安全强度不足的包装密钥来保护高安全强度的密钥。虽然使用更高安全强度的包装密钥显然更理想,但这并非总能实现。 BouncyCastle .NET API遵循的标准主要来自上述NIST文档以及 RFC 3394 和 RFC 5649(后者允许采用与 RFC 3394 不同的填充方案进行对称密钥包装)。
理解密钥包装的典型应用场景:假设需要通过某种通道(无论是否安全)传输密钥。包装密钥会存储在硬件安全模块(HSM)中,而密钥的包装和解包操作都在HSM内完成。这体现了密钥管理的范例——虽然这超出了本文档的讨论范围。但关键要强调的是:包装密钥的安全强度必须不低于被包装密钥,且包装密钥本身也需要严密保护。
最常用的对称密钥标准是 AES-KW 系列,即使用 AES 作为分组密码(密钥长度为 128、192 或 256 位),输出以64位为单位的加密块。以下示例基于RFC 3394标准,采用默认初始化向量,展示最简单的无填充场景。
输入参数包括:AES 包装密钥(即示例中的 keyParam )、待包装数据(或密钥,即示例中的 unwrappedData,以 64 位块为单位);输出则是加密数据(或包装后的密钥)。解包过程类似,需要提供原始包装密钥和已包装数据。
import BouncyCastle;
// RFC3394标准密钥包装(默认IV)
wrapKeyRFC3394 = function(keyParam, unwrappedData){
// 创建AES引擎
var symmetricBlockCipher = BouncyCastle.Crypto.Engines.AesEngine();
// 创建RFC3394包装引擎
var wrapEngine = BouncyCastle.Crypto.Engines.Rfc3394WrapEngine(symmetricBlockCipher);
// 初始化包装模式(true表示加密)
wrapEngine.Init(true, keyParam);
// 执行密钥包装
return wrapEngine.Wrap(unwrappedData, 0, #unwrappedData);
}
// RFC3394标准密钥解包
unwrapKeyRFC3394 = function(keyParam, wrappedData){
// 创建AES引擎
var symmetricBlockCipher = BouncyCastle.Crypto.Engines.AesEngine();
// 创建RFC3394包装引擎
var wrapEngine = BouncyCastle.Crypto.Engines.Rfc3394WrapEngine(symmetricBlockCipher);
// 初始化解包模式(false表示解密)
wrapEngine.Init(false, keyParam);
// 执行密钥解包
return wrapEngine.Unwrap(wrappedData, 0, #wrappedData);
}
// 生成随机密钥
var keyParam = BouncyCastle.Crypto.Parameters.KeyParameter(raw.buffer(32)); // 256位AES密钥
// 生成随机数据
var unwrappedBuffer1 = raw.buffer(string.random(64)); // 64字节随机数据
// 包装密钥
var wrappedData = wrapKeyRFC3394(keyParam, unwrappedBuffer1);
// 解包密钥
var unwrappedBuffer2 = unwrapKeyRFC3394(keyParam, wrappedData);
print(unwrappedBuffer1)
print(unwrappedBuffer2)
RFC 3394标准还描述了可自定义IV的变体方案,以下示例展示具体实现方式:
import BouncyCastle;
// RFC3394 密钥包装(带 IV 参数)
wrapKeyRFC3394WithIV = function(keyParam, IV, unwrappedData){
// 组合密钥与IV参数
var keyParamWithIV = BouncyCastle.Crypto.Parameters.ParametersWithIV(keyParam, IV);
// 创建AES引擎
var symmetricBlockCipher = BouncyCastle.Crypto.Engines.AesEngine();
// 创建RFC3394包装引擎
var wrapEngine = BouncyCastle.Crypto.Engines.Rfc3394WrapEngine(symmetricBlockCipher);
// 初始化包装模式
wrapEngine.Init(true, keyParamWithIV);
// 执行密钥包装
return wrapEngine.Wrap(unwrappedData, 0, #unwrappedData);
}
// RFC3394 密钥解包(带 IV 参数)
unwrapKeyRFC3394WithIV = function(keyParam, IV, wrappedData){
// 组合密钥与 IV 参数
var keyParamWithIV = BouncyCastle.Crypto.Parameters.ParametersWithIV(keyParam, IV);
// 创建 AES 引擎
var symmetricBlockCipher = BouncyCastle.Crypto.Engines.AesEngine();
// 创建 RFC3394 包装引擎
var wrapEngine = BouncyCastle.Crypto.Engines.Rfc3394WrapEngine(symmetricBlockCipher);
// 初始化解包模式
wrapEngine.Init(false, keyParamWithIV);
// 执行密钥解包
return wrapEngine.Unwrap(wrappedData, 0, #wrappedData);
}
// 示例数据
var keyParam = BouncyCastle.Crypto.Parameters.KeyParameter(raw.buffer("0123456789ABCDEF")); // 16 字节密钥
var IV = raw.buffer("12345678"); // 8 字节 IV
var unwrappedData = raw.buffer(16,"Hello, World!"); // 16 字节待包装数据
// 包装密钥
var wrappedData = wrapKeyRFC3394WithIV(keyParam, IV, unwrappedData);
print("包装后的数据:", wrappedData);
// 解包密钥
var unwrappedResult = unwrapKeyRFC3394WithIV(keyParam, IV, wrappedData);
print("解包后的数据:", unwrappedResult);
关键说明:
- aardio中使用
#操作符获取buffer对象的字节长度- 包装密钥(keyParam)应为
KeyParameter类型对象,可通过BouncyCastle.Crypto.Parameters.KeyParameter()创建- IV 参数需要是 8 字节长度的 buffer 对象(RFC3394 标准要求)
- 被包装数据(unwrappedData) 的长度必须是 8 字节的整数倍