文章目录
- 0.前言
- 1\\. 什么是Key Wrap
-
- 1.1 什么是 Key Wrap
- 1.2 为什么需要 Key Wrap?
- 1.3 Key Wrap 的工作原理
- 1.4 对比RFC 3394与RFC 5649两个标准
- 2\\. 通过实验理解Key Wrap
-
- 2.1 查询openssl 支持key wrap算法列表
- 2.2 准备测试数据
-
- 2.2.1 准备一个 16 字节 AES 密钥(作为待包装的密钥)
- 2.2.2 准备一个 32 字节 KEK(AES-256)
- 2.3 Key Wrap(RFC 3394)
-
- 2.3.1 Wrap
- 2.3.2 Unwrap
- 2.4 使用Key Wrap with Padding(RFC 5649)
-
- 2.4.1 Key Wrap之case1(与RFC 3394对比)
-
- 2.4.1.1 Wrap
- 2.4.1.2 Unwrap
- 2.4.2 Key Wrap之case2(加密非8bytes倍数数据)
-
- 2.4.2.1 Wrap
- 2.4.2.2 UnWrap
0.前言
最近在阅读HSM安全存储相关材料时,遇到key wrap/ Unwrap的概念。 这个概念之前的偶尔听过,但一直一来只是大概了解其为了包装密钥。 概念出处?设计目的是什么?与我们熟悉的加密的有什么不同或关联?有哪些特性呢? 这些问题一直萦绕在脑海中,挥之不去。希望通过层层分析,拨开心中迷雾。 如果你和我一样,接下来的旅程也许会有一些启发。如若如此,则倍感荣幸。
1. 什么是Key Wrap
1.1 什么是 Key Wrap
Key Wrap(密钥包装)是一种专门用于保护密码密钥的机制。它使用一个密钥加密密钥(KEK)对另一个密钥进行包装,生成可安全存储、传输或导入导出的 Wrapped Key。与普通数据加密不同,Key Wrap 不仅提供密钥的机密性,还提供完整性保护和安全恢复能力,因此被广泛应用于 HSM、PKCS#11、TEE、云 KMS 等密钥管理系统中。目前最常用的标准是 RFC 3394(AES-KW)和 RFC 5649(AES-KWP),并由 NIST SP 800-38F 统一纳入密钥包装推荐规范。
1.2 为什么需要 Key Wrap?
假设系统中已经有一个 AES-256 主密钥(KEK),现在需要把一个 AES-128 会话密钥导出到另一台设备。 如果直接传输
AES-128 Key
│
▼
网络传输
任何人截获数据,都可以获得这个密钥。 如果使用普通 AES-CBC 加密:
AES-128 Key
│
▼
AES-CBC 加密
│
▼
Ciphertext
虽然提供了保密性,但仍然存在一些问题:
- 密钥是否被篡改?
- 解密出来的数据是否仍然是合法密钥?
- 是否能够安全地导入到 HSM 或 TEE 中? 因此需要一种专门针对密钥管理设计的算法,这就是 Key Wrap。
1.3 Key Wrap 的工作原理
Key Wrap 使用一个 KEK(Key Encryption Key) 去保护另一个密钥。
KEK(Wrap Key)
│
▼
Key Wrap Algorithm
│
▼
Target Key(待包装密钥)
│
▼
Wrapped Key Blob
接收方使用同一个 KEK 执行 Unwrap
Wrapped Key Blob
│
▼
Key Unwrap
│
▼
Original Key
整个过程中:
- 原始密钥不会明文传输;
- Wrapped Key 可以安全存储或传输;
- 解包时会进行完整性校验。
1.4 对比RFC 3394与RFC 5649两个标准

| 发布时间 | 2002 | 2009 | RFC5649 是 RFC3394 的扩展版本 |
| 标准名称 | AES Key Wrap Algorithm | AES Key Wrap with Padding Algorithm | KWP = Key Wrap with Padding |
| 设计目标 | 包装(Wrap)密码密钥 | 包装(Wrap)密码密钥 | 相同 |
| 底层算法 | AES | AES | 相同 |
| KEK(Key Encryption Key) | 使用 KEK 包装其他密钥 | 使用 KEK 包装其他密钥 | 相同 |
| 主要用途 | 密钥存储、导入导出、密钥传输 | 密钥存储、导入导出、密钥传输 | 相同 |
| 提供机密性(Confidentiality) | ✔ | ✔ | 相同 |
| 提供完整性保护(Integrity) | ✔ | ✔ | 相同 |
| 支持 Unwrap 恢复 | ✔ | ✔ | 相同 |
| AES 核心运算 | 相同的 AES Key Wrap 核心算法 | 复用 RFC3394 的核心算法 | 相同 |
| 数据长度要求 | 必须是 8 字节整数倍 | 支持任意长度 | 不同 |
| Padding | 不支持 | 支持自动 Padding | 不同 |
| 初始值(IV/AIV) | 默认 IV:A6A6A6A6A6A6A6A6 | AIV:A65959A6 || MLI | 不同 |
| 是否包含长度信息 | 否 | 是,包含 MLI(Message Length Indicator) | 不同 |
| 是否适用于非 8 字节整数倍数据 | ✘ | ✔ | 不同 |
| 输出结果是否与 RFC3394 相同 | —— | 否,即使输入数据长度是 16/32 字节也不同 | 不同 |
| 解包时完整性校验 | 校验默认 IV 是否恢复正确 | 校验 AIV(常量 + MLI)是否恢复正确 | 不同 |
| OpenSSL Cipher | id-aes128-wrapid-aes192-wrapid-aes256-wrap | id-aes128-wrap-padid-aes192-wrap-padid-aes256-wrap-pad | 不同 |
| PKCS#11 Mechanism | CKM_AES_KEY_WRAP | CKM_AES_KEY_WRAP_PAD | 不同 |
| 典型应用 | AES-128、AES-192、AES-256、3DES 等固定长度密钥 | 任意长度密钥材料、ECC 密钥材料、密钥派生结果等 | 不同 |
- 可以更加简化去理解
| AES-128(16B)、AES-256(32B)等固定长度密钥 | AES_KEY_WRAP | RFC 3394 |
| 长度可能不是 8 字节整数倍的密钥材料 | AES_KEY_WRAP_PAD | RFC 5649 |
2. 通过实验理解Key Wrap
2.1 查询openssl 支持key wrap算法列表
openssl list -cipher-algorithms | grep WRAP
{ 2.16.840.1.101.3.4.1.5, AES-128-WRAP, AES128-WRAP, id-aes128-wrap } @ default
{ 1.2.840.113549.1.9.16.3.6, DES3-WRAP, id-smime-alg-CMS3DESwrap } @ default
{ 2.16.840.1.101.3.4.1.45, AES-256-WRAP, AES256-WRAP, id-aes256-wrap } @ default
{ 2.16.840.1.101.3.4.1.8, AES-128-WRAP-PAD, AES128-WRAP-PAD, id-aes128-wrap-pad } @ default
{ 2.16.840.1.101.3.4.1.48, AES-256-WRAP-PAD, AES256-WRAP-PAD, id-aes256-wrap-pad } @ default
{ 2.16.840.1.101.3.4.1.25, AES-192-WRAP, AES192-WRAP, id-aes192-wrap } @ default
{ 2.16.840.1.101.3.4.1.28, AES-192-WRAP-PAD, AES192-WRAP-PAD, id-aes192-wrap-pad } @ default
{ AES-256-WRAP-INV, AES256-WRAP-INV } @ default
{ AES-192-WRAP-INV, AES192-WRAP-INV } @ default
{ AES-128-WRAP-INV, AES128-WRAP-INV } @ default
{ AES-256-WRAP-PAD-INV, AES256-WRAP-PAD-INV } @ default
{ AES-192-WRAP-PAD-INV, AES192-WRAP-PAD-INV } @ default
{ AES-128-WRAP-PAD-INV, AES128-WRAP-PAD-INV } @ default
****
- 内容实际上可以分为 四类
- 本文主要关注前两类,分别对应的标准是RFC 3394,RFC 5649
| AES-xxx-WRAP | RFC 3394 | AES Key Wrap,不支持任意长度 |
| AES-xxx-WRAP-PAD | RFC 5649 | AES Key Wrap with Padding,支持任意长度 |
| DES3-WRAP | CMS(S/MIME) | 3DES Key Wrap,用于CMS |
| xxx-INV | OpenSSL内部算法 | 反向(Unwrap)实现,不建议用户直接使用 |
2.2 准备测试数据
2.2.1 准备一个 16 字节 AES 密钥(作为待包装的密钥)
- 16byte密钥生成
printf '\\x00\\x11\\x22\\x33\\x44\\x55\\x66\\x77\\x88\\x99\\xaa\\xbb\\xcc\\xdd\\xee\\xff' > key.bin
- 查看key.bin内容
xxd key.bin
00000000: 0011 2233 4455 6677 8899 aabb ccdd eeff .."3DUfw……..
2.2.2 准备一个 32 字节 KEK(AES-256)
- 32byte KEK准备
printf '\\x00\\x01\\x02\\x03\\x04\\x05\\x06\\x07\\x08\\x09\\x0a\\x0b\\x0c\\x0d\\x0e\\x0f\\x10\\x11\\x12\\x13\\x14\\x15\\x16\\x17\\x18\\x19\\x1a\\x1b\\x1c\\x1d\\x1e\\x1f' > kek.bin
- 查看kek.bin
xxd kek.bin
00000000: 0001 0203 0405 0607 0809 0a0b 0c0d 0e0f …………….
00000010: 1011 1213 1415 1617 1819 1a1b 1c1d 1e1f …………….
2.3 Key Wrap(RFC 3394)
2.3.1 Wrap
- 注意,需要输入iv
openssl enc \\
-id-aes256-wrap \\
-e \\
-K 000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F \\
-iv A6A6A6A6A6A6A6A6 \\
-in key.bin \\
-out wrapped_01.bin
| -id-aes256-wrap | AES-256 Key Wrap(RFC 3394) |
| -e | Encrypt(Wrap) |
| -K | KEK(十六进制) |
| -iv | iv(十六进制) |
| -in | 待包装密钥 |
| -out | Wrapped Key |
- 查看Wrap结果
xxd wrapped_01.bin
00000000: 64e8 c3f9 ce0f 5ba2 63e9 7779 0581 8a2a d…..[.c.wy…*
00000010: 93c8 191e 7d6e 8ae7
- 与 RFC 3394 test例子对比 origin_url=%3A%2Fe81d81fde2954729a45494e3d54151cd&pos_id=img-0aWxo5u6-1784501027539)

2.3.2 Unwrap
- Unwrap过程, 恢复密钥
openssl enc \\
-id-aes256-wrap \\
-d \\
-K 000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F \\
-iv A6A6A6A6A6A6A6A6 \\
-in wrapped_01.bin \\
-out unwrap_01.bin
| -id-aes256-wrap | AES-256 Key Wrap(RFC 3394) |
| -d | Decrypt(Unwrap) |
| -K | KEK(十六进制) |
| -iv | iv(十六进制) |
| -in | Wrapped Key |
| -out | 恢复后密钥 |
- 查看恢复密钥内容
xxd unwrap_01.bin
00000000: 0011 2233 4455 6677 8899 aabb ccdd eeff .."3DUfw……..
2.4 使用Key Wrap with Padding(RFC 5649)
2.4.1 Key Wrap之case1(与RFC 3394对比)
– 敲黑板:这个实验使用参数除了pad区别外,其他都一样,请观察结果 -** 参数相同的情况,结果依然不同,使用上不能混淆**
2.4.1.1 Wrap
注意: 加密的时候给了一个提示,hex string is too long, ignoring excess,这里指的IV太长了
openssl enc \\
-id-aes256-wrap-pad \\
-e \\
-K 000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F \\
-iv A6A6A6A6A6A6A6A6 \\
-in key.bin \\
-out wrapped_02.bin
hex string is too long, ignoring excess
- 查看查看Wrap结果
- 发现,相同的参数输入,与RFC 3394计算的结果不同
- 证明:RFC 3394 与 RFC 5649不是相互替代,有不能混用
xxd wrapped_02.bin
00000000: bdd6 3552 e627 e7bd 8ba7 dabd 855e 29ac ..5R.'…….^).
00000010: 1174 db41 4855 9917
2.4.1.2 Unwrap
- 解密
openssl enc \\
-id-aes256-wrap-pad \\
-d \\
-K 000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F \\
-iv A6A6A6A6A6A6A6A6 \\
-in wrapped_02.bin \\
-out unwrap_02.bin
hex string is too long, ignoring excess
- 查看还原结果,一致
xxd unwrap_02.bin
00000000: 0011 2233 4455 6677 8899 aabb ccdd eeff .."3DUfw……..
2.4.2 Key Wrap之case2(加密非8bytes倍数数据)
2.4.2.1 Wrap
- 准备被加密数据
echo "Hello PKCS11" > data.bin
- 数据为13bytes,
xxd data.bin
00000000: 4865 6c6c 6f20 504b 4353 3131 0a Hello PKCS11.
- 加密命令
openssl enc \\
-id-aes256-wrap-pad \\
-e \\
-K 000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F \\
-in data.bin \\
-iv A65959A6 \\
-out wrapped.data.bin
- 加密后数据
xxd wrapped.data.bin
00000000: 9491 edf3 84a2 db5f 345d 6bf8 1ab7 fbae ……._4]k…..
00000010: 3846 f77f e4f7 20a7
2.4.2.2 UnWrap
- 恢复命令
openssl enc \\
-id-aes256-wrap-pad \\
-d \\
-K 000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F \\
-iv A65959A6 \\
-in wrapped.data.bin \\
-out unwrap.data.bin
- 查看恢复结果
xxd unwrap.data.bin
00000000: 4865 6c6c 6f20 504b 4353 3131 0a Hello PKCS11.



