dchest/libxaes256gcm
GitHub: dchest/libxaes256gcm
使用 C 语言实现的 XAES-256-GCM 扩展 nonce 认证加密算法库,支持密钥承诺变体并兼容多种平台原生加密后端。
Stars: 0 | Forks: 0
# C 语言实现 XAES-256-GCM
[XAES-256-GCM][1] 的 C 语言实现,这是一种使用扩展 nonce 的关联数据认证加密(AEAD)算法,以及它的密钥承诺变体 [KC-XAES-256-GCM][2]。
这两种变体均兼容 NIST 标准,并可视为使用标准基于 CMAC 的 KDF 进行密钥派生与 AES-256-GCM 的组合。
此实现仅使用公共平台 API,通过以下其中一个后端来执行加密操作。
## 后端
* CryptoKit + CommonCrypto (macOS, iOS)
* OpenSSL (Linux, FreeBSD)
* LibreSSL (OpenBSD)
* BoringSSL
## 构建
每个后端都有各自的 Makefile._backend_。默认的 `Makefile` 会尝试自动检测平台并使用正确的 Makefile._backend_,因此在大多数情况下,您只需运行 `make` 即可。
**macOS, Linux, OpenBSD, FreeBSD:**
```
make
```
(请注意,macOS/iOS 版本的 `Makefile.apple` 需要 Swift 编译器,因为它需要为 AES-GCM 构建 CryptoKit shim。)
这会创建一个 `dist` 目录,包含:
- `include/xaes256gcm.h` -- 公共 API
- `include/xaes256gcm_platform.h` -- 平台支持文件,从 `xaes256gcm.h` 中包含
- `libxaes256gcm.a` -- 静态库
- `test` -- 测试可执行文件
- `benchmark` -- benchmark 可执行文件
或者,只需将所需的 .c, .h 文件复制到您的项目中(将合适的 `xaes256gcm_backend.h` 重命名为 `xaes256gcm_platform.h`)。
**BoringSSL:**
在您选择的路径中获取并构建 BoringSSL:
```
# 在 boringssl 目录内
mkdir build
cd build
cmake ..
make
```
```
# 在 xaes256gcm 目录内
make -f Makefile.boringssl BORINGSSL_DIR=/path/to/boringssl
```
**LibreSSL Portable:**
在 macOS 上,使用 Homebrew 安装 LibreSSL:
```
brew install libressl
LDFLAGS="-L/opt/homebrew/opt/libressl/lib" \
CFLAGS="-I/opt/homebrew/opt/libressl/include" \
make -f Makefile.libressl
```
## 性能
XAES-GCM 仅给 AES-GCM 增加了一点开销:每条消息增加两次额外的块加密和一次密钥调度。密钥承诺变体每条消息额外增加三次块加密。
在 M1 MacBook Air 上,BoringSSL 后端加密 1 MB 消息的速度为 6.3 GB/s,Apple 后端为 4.7 GB/s,而 OpenSSL 后端在 Linux 和 FreeBSD 虚拟机中为 5.5 GB/s。(LibreSSL 目前尚不支持 arm64 的 AES 指令集)。
在 Linux 虚拟机中的 AMD EPYC-Genoa 上,OpenSSL 后端加密 1 MB 消息的速度为 11 GB/s。
## 安全性
- Nonce 重用会破坏机密性和真实性。每一对密钥和 nonce 组合必须是唯一的。
- 192 位的 nonce 使得随机 nonce 生成是安全的。
- 密钥承诺变体将每个密文绑定到一个特定的密钥,从而防止攻击者生成一个能够在多个密钥下成功解密为不同明文的单一密文攻击。
## API
所有函数(`xaes256gcm_ctx_destroy` 除外)成功时返回 0,出错时返回非零值。
必须检查它们的返回结果。
### 常量
- `XAES256GCM_KEY_SIZE` (32) -- 密钥长度(以字节为单位)
- `XAES256GCM_NONCE_SIZE` (24) -- nonce 长度(以字节为单位)
- `XAES256GCM_OVERHEAD` (16) -- 密文开销(认证标签)
- `XAES256GCM_KC_OVERHEAD` (48) -- KC 变体的密文开销(标签 + 32 字节的密钥承诺)
- `XAES256GCM_AAD_MAX` -- 关联数据的最大长度(以字节为单位)
(2^61 - 1 和 SIZE_MAX 中的较小值)。
- `XAES256GCM_PLAINTEXT_MAX` -- 明文的最大长度(以字节为单位)
(2^36 - 32 和 SIZE_MAX - 48 中的较小值)。
### Context
用法:
```
// allocate context (e.g. on stack)
struct xaes256gcm_ctx c;
// initialize context
if (xaes256gcm_ctx_init(&c, key) != 0) {
// handle error
}
// use c for seal/open...
// destroy context
xaes256gcm_ctx_destroy(&c);
```
每次封装(seal)和打开(open)操作在语义上都是独立的。
然而,Context 并非线程安全的:每个线程都应创建自己的 Context。
如果您不想手动管理 Context,并且不需要通过为多个操作缓存密钥来获得轻微的性能提升,请使用稍后描述的一次性函数。
#### `xaes256gcm_ctx_init`
```
int xaes256gcm_ctx_init(struct xaes256gcm_ctx *c, const uint8_t key[32]);
```
使用 32 字节的密钥初始化 Context。
每个由 `xaes256gcm_ctx_init()` 初始化的 Context 都必须使用 `xaes256gcm_ctx_destroy()` 进行销毁。根据后端的不同,初始化过程可能会分配内存,这些内存将由析构函数释放。
成功返回 0,出错返回非零值。
返回错误表示底层加密后端初始化失败。
#### `xaes256gcm_ctx_destroy`
```
void xaes256gcm_ctx_destroy(struct xaes256gcm_ctx *c);
```
销毁 Context。
### 加密与解密
#### `xaes256gcm_ctx_seal`
```
int xaes256gcm_ctx_seal(
struct xaes256gcm_ctx *c,
const uint8_t nonce[24],
const uint8_t *plaintext, size_t plaintext_len,
const uint8_t *aad, size_t aad_len,
uint8_t *ciphertext, size_t ciphertext_max_len
);
```
加密并认证明文,对可选的附加数据进行认证,并将结果写入密文。`ciphertext_max_len` 表示可用于存储结果的空间,该值必须至少为 `plaintext_len + XAES256GCM_OVERHEAD`。
此密钥的 nonce 必须是唯一的。它不需要是随机或不可预测的,但允许使用随机 nonce。
对同一个密钥重用 nonce 会破坏机密性和真实性。
成功返回 0,出错返回非零值。
返回错误表示底层加密后端加密失败,或者未满足输入边界要求。出错时,生成的密文状态是未定义的。
#### `xaes256gcm_ctx_open`
```
int xaes256gcm_ctx_open(
struct xaes256gcm_ctx *c,
const uint8_t nonce[24],
const uint8_t *ciphertext, size_t ciphertext_len,
const uint8_t *aad, size_t aad_len,
uint8_t *plaintext, size_t plaintext_max_len
);
```
验证密文中的认证标签并解密密文。
`plaintext_max_len` 表示可用于存储结果的空间,该值必须至少为 `ciphertext_len - XAES256GCM_OVERHEAD`。
nonce 和附加数据必须与传递给封装(seal)函数的值相同。
成功返回 0,出错返回非零值。
返回错误表示底层加密后端解密失败,未满足输入边界要求,或者密文认证失败。出错时,生成的明文是未定义的,并且可能会被零覆盖;但如果认证失败,明文中绝对不会包含部分解密的数据。
#### `xaes256gcm_ctx_seal_kc`
```
int xaes256gcm_ctx_seal_kc(
struct xaes256gcm_ctx *c,
const uint8_t nonce[24],
const uint8_t *plaintext, size_t plaintext_len,
const uint8_t *aad, size_t aad_len,
uint8_t *ciphertext, size_t ciphertext_max_len
);
```
与 `xaes256gcm_ctx_seal` 相同,但会额外计算一个 32 字节的密钥承诺标签,并将其附加在认证标签之后。输出长度为 `plaintext_len + XAES256GCM_KC_OVERHEAD`(明文 + 48 字节)。
#### `xaes256gcm_ctx_open_kc`
```
int xaes256gcm_ctx_open_kc(
struct xaes256gcm_ctx *c,
const uint8_t nonce[24],
const uint8_t *ciphertext, size_t ciphertext_len,
const uint8_t *aad, size_t aad_len,
uint8_t *plaintext, size_t plaintext_max_len
);
```
与 `xaes256gcm_ctx_open` 相同,但同时会验证密钥承诺。
`plaintext_max_len` 必须至少为 `ciphertext_len - XAES256GCM_KC_OVERHEAD`。
### 简单的一次性函数
这些函数在一次调用中结合了 `init` + `seal`/`open` + `destroy`。
```
int xaes256gcm_seal(
const uint8_t key[32], const uint8_t nonce[24],
const uint8_t *plaintext, size_t plaintext_len,
const uint8_t *aad, size_t aad_len,
uint8_t *ciphertext, size_t ciphertext_max_len
);
int xaes256gcm_open(
const uint8_t key[32], const uint8_t nonce[24],
const uint8_t *ciphertext, size_t ciphertext_len,
const uint8_t *aad, size_t aad_len,
uint8_t *plaintext, size_t plaintext_max_len
);
int xaes256gcm_seal_kc(
const uint8_t key[32], const uint8_t nonce[24],
const uint8_t *plaintext, size_t plaintext_len,
const uint8_t *aad, size_t aad_len,
uint8_t *ciphertext, size_t ciphertext_max_len
);
int xaes256gcm_open_kc(
const uint8_t key[32], const uint8_t nonce[24],
const uint8_t *ciphertext, size_t ciphertext_len,
const uint8_t *aad, size_t aad_len,
uint8_t *plaintext, size_t plaintext_max_len
);
```
### Nonce 生成
#### `xaes256gcm_rand_nonce`
```
int xaes256gcm_rand_nonce(uint8_t nonce[24]);
```
使用后端的 CSPRNG 生成一个随机的 24 字节 nonce。
成功返回 0,出错返回非零值。
返回错误表示底层加密后端生成随机字节失败,并且 nonce 的内容是未定义的。
## 示例
```
#include
#include
#include
int main(void)
{
uint8_t key[32] = {0}; // fill with an actual secret key
uint8_t nonce[24];
uint8_t plaintext[] = "Hello, world!"; // includes NUL
uint8_t ciphertext[sizeof(plaintext) - 1 + XAES256GCM_OVERHEAD];
uint8_t decrypted[sizeof(plaintext)] = {0};
uint8_t aad[] = "metadata"; // includes NUL
struct xaes256gcm_ctx c;
// Initialize context.
if (xaes256gcm_ctx_init(&c, key) != 0) {
fprintf(stderr, "init failed\n");
return 1;
}
// Generate a random nonce.
if (xaes256gcm_rand_nonce(nonce) != 0) {
fprintf(stderr, "nonce generation failed\n");
xaes256gcm_ctx_destroy(&c);
return 1;
}
// Encrypt.
if (xaes256gcm_ctx_seal(&c, nonce,
plaintext, sizeof(plaintext) - 1,
aad, sizeof(aad) - 1,
ciphertext, sizeof(ciphertext)) != 0) {
fprintf(stderr, "encryption failed\n");
xaes256gcm_ctx_destroy(&c);
return 1;
}
// Decrypt.
if (xaes256gcm_ctx_open(&c, nonce,
ciphertext, sizeof(ciphertext),
aad, sizeof(aad) - 1,
decrypted, sizeof(decrypted)) != 0) {
fprintf(stderr, "decryption failed\n");
xaes256gcm_ctx_destroy(&c);
return 2;
}
printf("%s\n", decrypted); // "Hello, world!"
xaes256gcm_ctx_destroy(&c);
return 0;
}
```
要构建并运行它,请将 `example.c` 放入 `dist` 目录中,然后运行:
```
cc example.c -o example -Iinclude -L. -lxaes256gcm && ./example
```
请注意,与这个简单的示例不同,要加密文本,您首先应该将其适当地编码为 UTF-8 字节。
## 参考文献
- [XAES-256-GCM 规范][1]
- [Filippo Valsorda: XAES-256-GCM][3]
- [基于分组密码的 Nonce 派生方案的密钥承诺][2]
## 许可证
ISC 许可证。详见 [LICENSE](LICENSE)。
标签:AEAD, AES-GCM, 加密算法, 安全测试工具, 客户端加密, 密码学, 开发库, 手动系统调用