<think>我们使用GmSSL库来实现国密SM2对大文件的签名和验签。由于大文件无法一次性读入内存,我们将采用流式处理(分块读取)的方式计算SM3哈希,然后使用SM2私钥对哈希值进行签名,以及使用公钥进行验签。
以下是详细的步骤和代码:
1. **安装GmSSL库**
在开始之前,需要先安装GmSSL库。可以从源码编译安装,具体步骤参考GmSSL官方文档。
2. **代码实现**
- 生成SM2密钥对
- 计算大文件的SM3哈希(分块读取)
- 使用私钥对哈希值进行签名
- 使用公钥验证签名
3. **注意点**
- 大文件处理:分块读取文件,避免一次性加载到内存。
- 错误处理:检查每一步操作的结果。
下面是完整的C代码示例:
```c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <gmssl/sm2.h>
#include <gmssl/error.h>
// 生成SM2密钥对并保存到文件
int generate_sm2_keypair(const char *pub_key_file, const char *priv_key_file)
{
SM2_KEY key;
FILE *fp = NULL;
// 生成密钥对
if (sm2_key_generate(&key) != 1) {
fprintf(stderr, "Error: sm2_key_generate failed\n");
return -1;
}
// 保存公钥
if ((fp = fopen(pub_key_file, "wb")) == NULL) {
fprintf(stderr, "Error: open %s failed\n", pub_key_file);
return -1;
}
if (sm2_public_key_info_to_pem(&key, fp) != 1) {
fprintf(stderr, "Error: save public key failed\n");
fclose(fp);
return -1;
}
fclose(fp);
// 保存私钥
if ((fp = fopen(priv_key_file, "wb")) == NULL) {
fprintf(stderr, "Error: open %s failed\n", priv_key_file);
return -1;
}
if (sm2_private_key_info_to_pem(&key, fp) != 1) {
fprintf(stderr, "Error: save private key failed\n");
fclose(fp);
return -1;
}
fclose(fp);
return 1;
}
// 计算文件的SM3哈希值(分块读取)
int sm3_hash_file(const char *file_path, uint8_t *dgst)
{
FILE *fp = NULL;
uint8_t buf[4096];
size_t len;
SM3_CTX ctx;
if ((fp = fopen(file_path, "rb")) == NULL) {
fprintf(stderr, "Error: open %s failed\n", file_path);
return -1;
}
sm3_init(&ctx);
while ((len = fread(buf, 1, sizeof(buf), fp)) > 0) {
sm3_update(&ctx, buf, len);
}
sm3_finish(&ctx, dgst);
fclose(fp);
return 1;
}
// 使用SM2私钥对文件进行签名
int sm2_sign_file(const char *priv_key_file, const char *file_path, const char *sig_file)
{
FILE *fp = NULL;
SM2_KEY key;
uint8_t dgst[32];
uint8_t sig[SM2_MAX_SIGNATURE_SIZE];
size_t siglen;
// 读取私钥
if ((fp = fopen(priv_key_file, "rb")) == NULL) {
fprintf(stderr, "Error: open %s failed\n", priv_key_file);
return -1;
}
if (sm2_private_key_info_from_pem(&key, fp) != 1) {
fprintf(stderr, "Error: read private key failed\n");
fclose(fp);
return -1;
}
fclose(fp);
// 计算文件的SM3哈希
if (sm3_hash_file(file_path, dgst) != 1) {
fprintf(stderr, "Error: compute file hash failed\n");
return -1;
}
// 签名
if (sm2_sign(&key, dgst, sig, &siglen) != 1) {
fprintf(stderr, "Error: sm2_sign failed\n");
return -1;
}
// 保存签名
if ((fp = fopen(sig_file, "wb")) == NULL) {
fprintf(stderr, "Error: open %s failed\n", sig_file);
return -1;
}
if (fwrite(sig, 1, siglen, fp) != siglen) {
fprintf(stderr, "Error: write signature failed\n");
fclose(fp);
return -1;
}
fclose(fp);
return 1;
}
// 使用SM2公钥验证文件签名
int sm2_verify_file(const char *pub_key_file, const char *file_path, const char *sig_file)
{
FILE *fp = NULL;
SM2_KEY key;
uint8_t dgst[32];
uint8_t sig[SM2_MAX_SIGNATURE_SIZE];
size_t siglen;
// 读取公钥
if ((fp = fopen(pub_key_file, "rb")) == NULL) {
fprintf(stderr, "Error: open %s failed\n", pub_key_file);
return -1;
}
if (sm2_public_key_info_from_pem(&key, fp) != 1) {
fprintf(stderr, "Error: read public key failed\n");
fclose(fp);
return -1;
}
fclose(fp);
// 读取签名
if ((fp = fopen(sig_file, "rb")) == NULL) {
fprintf(stderr, "Error: open %s failed\n", sig_file);
return -1;
}
siglen = fread(sig, 1, sizeof(sig), fp);
fclose(fp);
// 计算文件的SM3哈希
if (sm3_hash_file(file_path, dgst) != 1) {
fprintf(stderr, "Error: compute file hash failed\n");
return -1;
}
// 验证签名
if (sm2_verify(&key, dgst, sig, siglen) != 1) {
fprintf(stderr, "Error: sm2_verify failed\n");
return 0;
}
return 1;
}
int main(int argc, char *argv[])
{
const char *pub_key_file = "sm2_pub.pem";
const char *priv_key_file = "sm2_priv.pem";
const char *file_path = "large_file.bin";
const char *sig_file = "signature.sig";
// 生成密钥对(如果不存在)
if (access(pub_key_file, F_OK) != 0 || access(priv_key_file, F_OK) != 0) {
if (generate_sm2_keypair(pub_key_file, priv_key_file) != 1) {
fprintf(stderr, "generate_sm2_keypair failed\n");
return -1;
}
printf("Generated SM2 key pair\n");
}
// 签名
if (sm2_sign_file(priv_key_file, file_path, sig_file) != 1) {
fprintf(stderr, "sm2_sign_file failed\n");
return -1;
}
printf("File signed successfully\n");
// 验证
if (sm2_verify_file(pub_key_file, file_path, sig_file) == 1) {
printf("Signature verified successfully\n");
} else {
printf("Signature verification failed\n");
}
return 0;
}
```
### 代码说明
1. **生成密钥对**:`generate_sm2_keypair`函数生成SM2密钥对,并将公钥和私钥分别保存到PEM格式的文件中。
2. **计算文件哈希**:`sm3_hash_file`函数使用SM3算法计算文件的哈希值。该函数采用分块读取的方式,避免一次性加载大文件。
3. **签名**:`sm2_sign_file`函数读取私钥,计算文件的SM3哈希,然后使用SM2私钥对哈希值进行签名,并将签名保存到文件。
4. **验签**:`sm2_verify_file`函数读取公钥和签名文件,计算文件的SM3哈希,然后使用SM2公钥验证签名。
### 编译与运行
假设已经安装好GmSSL库,使用以下命令编译(注意调整库路径和链接库):
```bash
gcc -o sm2_sign_verify sm2_sign_verify.c -lgmssl -L/path/to/gmssl/lib -I/path/to/gmssl/include
```
运行程序:
```bash
./sm2_sign_verify
```
### 注意事项
1. 确保文件路径正确,程序有读写权限。
2. 对于超大文件,计算哈希可能需要一定时间,但内存占用很小(只使用固定大小的缓冲区)。
3. 实际应用中,私钥应妥善保管,建议使用密码加密存储。
§§相关问题§§:
1. 如何用GmSSL命令行工具完成大文件的SM2签名和验签?
2. 在签名过程中如何添加用户ID(如Z值)?
3. 如何将SM2签名和验签集成到现有的文件传输系统中?
4. 如何优化SM2签名验签的性能以处理超大规模文件(如TB级)?</think># 使用 GmSSL 实现国密 SM2 对大文件的签名与验签
GmSSL 是专为国密算法优化的密码工具包,支持 SM2、SM3、SM4 等算法。下面我将详细介绍如何使用 GmSSL 对大文件进行 SM2 签名和验签操作。
## 完整实现方案
### 1. 安装 GmSSL
```bash
# 从源码安装 GmSSL
git clone https://github.com/guanzhi/GmSSL.git
cd GmSSL
./config --prefix=/usr/local/gmssl --openssldir=/usr/local/gmssl/ssl
make
sudo make install
# 配置环境变量
echo 'export PATH=/usr/local/gmssl/bin:$PATH' >> ~/.bashrc
source ~/.bashrc
```
### 2. 命令行工具实现
#### 生成密钥对
```bash
# 生成 SM2 私钥
gmssl sm2 -genkey -out sm2_priv.pem
# 从私钥提取公钥
gmssl sm2 -pubout -in sm2_priv.pem -out sm2_pub.pem
# 查看密钥信息
gmssl pkey -in sm2_priv.pem -text -noout
```
#### 对大文件签名
```bash
# 计算文件 SM3 哈希并使用 SM2 私钥签名
gmssl sm3 -sign -in large_file.bin -inkey sm2_priv.pem -out signature.bin
# 可选:生成可读的签名文件
gmssl sm3 -sign -in large_file.bin -inkey sm2_priv.pem -out signature.der -outform DER
```
#### 验证文件签名
```bash
# 使用公钥验证签名
gmssl sm3 -verify -in large_file.bin -sigfile signature.bin -pubin -inkey sm2_pub.pem
# 输出结果: Verification Successful 或 Verification Failure
```
### 3. C 语言 API 实现
```c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <gmssl/sm2.h>
#include <gmssl/error.h>
// 流式计算文件 SM3 哈希
int sm3_stream_hash(FILE *fp, uint8_t dgst[SM3_DIGEST_SIZE])
{
SM3_CTX ctx;
uint8_t buf[65536];
size_t len;
sm3_init(&ctx);
while ((len = fread(buf, 1, sizeof(buf), fp)) > 0) {
sm3_update(&ctx, buf, len);
}
sm3_finish(&ctx, dgst);
return 1;
}
// 使用 SM2 私钥签名文件
int sign_file(const char *file_path, const char *priv_key_path, const char *sig_path)
{
FILE *fp = fopen(file_path, "rb");
if (!fp) {
fprintf(stderr, "Error opening file: %s\n", file_path);
return 0;
}
// 计算文件哈希
uint8_t dgst[SM3_DIGEST_SIZE];
if (!sm3_stream_hash(fp, dgst)) {
fclose(fp);
return 0;
}
fclose(fp);
// 加载私钥
FILE *key_fp = fopen(priv_key_path, "r");
if (!key_fp) {
fprintf(stderr, "Error opening private key: %s\n", priv_key_path);
return 0;
}
SM2_KEY key;
if (sm2_private_key_info_from_pem(&key, key_fp) != 1) {
fprintf(stderr, "Error loading private key\n");
fclose(key_fp);
return 0;
}
fclose(key_fp);
// 创建签名
uint8_t sig[SM2_MAX_SIGNATURE_SIZE];
size_t siglen;
if (sm2_sign(&key, dgst, sig, &siglen) != 1) {
fprintf(stderr, "Signature failed\n");
return 0;
}
// 保存签名
FILE *sig_fp = fopen(sig_path, "wb");
if (!sig_fp) {
fprintf(stderr, "Error creating signature file\n");
return 0;
}
fwrite(sig, 1, siglen, sig_fp);
fclose(sig_fp);
printf("File signed successfully. Signature size: %zu bytes\n", siglen);
return 1;
}
// 使用 SM2 公钥验证文件签名
int verify_file(const char *file_path, const char *pub_key_path, const char *sig_path)
{
FILE *fp = fopen(file_path, "rb");
if (!fp) {
fprintf(stderr, "Error opening file: %s\n", file_path);
return 0;
}
// 计算文件哈希
uint8_t dgst[SM3_DIGEST_SIZE];
if (!sm3_stream_hash(fp, dgst)) {
fclose(fp);
return 0;
}
fclose(fp);
// 加载公钥
FILE *key_fp = fopen(pub_key_path, "r");
if (!key_fp) {
fprintf(stderr, "Error opening public key: %s\n", pub_key_path);
return 0;
}
SM2_KEY key;
if (sm2_public_key_info_from_pem(&key, key_fp) != 1) {
fprintf(stderr, "Error loading public key\n");
fclose(key_fp);
return 0;
}
fclose(key_fp);
// 读取签名
FILE *sig_fp = fopen(sig_path, "rb");
if (!sig_fp) {
fprintf(stderr, "Error opening signature file\n");
return 0;
}
fseek(sig_fp, 0, SEEK_END);
size_t siglen = ftell(sig_fp);
fseek(sig_fp, 0, SEEK_SET);
uint8_t *sig = malloc(siglen);
if (!sig) {
fclose(sig_fp);
return 0;
}
fread(sig, 1, siglen, sig_fp);
fclose(sig_fp);
// 验证签名
int ret = sm2_verify(&key, dgst, sig, siglen);
free(sig);
if (ret == 1) {
printf("Signature verification SUCCESS\n");
return 1;
} else {
printf("Signature verification FAILED\n");
return 0;
}
}
int main(int argc, char *argv[])
{
if (argc != 5) {
fprintf(stderr, "Usage: %s <sign|verify> <file> <key> <signature>\n", argv[0]);
return 1;
}
const char *action = argv[1];
const char *file_path = argv[2];
const char *key_path = argv[3];
const char *sig_path = argv[4];
if (strcmp(action, "sign") == 0) {
return !sign_file(file_path, key_path, sig_path);
} else if (strcmp(action, "verify") == 0) {
return !verify_file(file_path, key_path, sig_path);
} else {
fprintf(stderr, "Invalid action. Use 'sign' or 'verify'\n");
return 1;
}
}
```
### 4. Python 实现(使用 gmssl 包)
```python
from gmssl import sm2, sm3
import os
import sys
class SM2FileSigner:
def __init__(self, private_key=None, public_key=None):
self.sm2_crypt = sm2.CryptSM2(
private_key=private_key,
public_key=public_key
)
@classmethod
def generate_key_pair(cls):
"""生成SM2密钥对"""
private_key = os.urandom(32).hex()
crypt = sm2.CryptSM2(private_key=private_key)
public_key = crypt.public_key
return private_key, public_key
def stream_hash(self, file_path):
"""流式计算文件SM3哈希"""
hash_ctx = sm3.sm3()
with open(file_path, 'rb') as f:
while True:
chunk = f.read(65536)
if not chunk:
break
hash_ctx.update(chunk)
return bytes.fromhex(hash_ctx.final())
def sign_file(self, file_path, sig_path):
"""签名文件"""
file_hash = self.stream_hash(file_path)
signature = self.sm2_crypt.sign(file_hash)
with open(sig_path, 'wb') as f:
f.write(signature)
print(f"File signed. Signature size: {len(signature)} bytes")
return signature
def verify_file(self, file_path, sig_path):
"""验证文件签名"""
file_hash = self.stream_hash(file_path)
with open(sig_path, 'rb') as f:
signature = f.read()
try:
result = self.sm2_crypt.verify(signature, file_hash)
if result:
print("Signature verification SUCCESS")
return True
else:
print("Signature verification FAILED")
return False
except Exception as e:
print(f"Verification error: {str(e)}")
return False
def main():
if len(sys.argv) < 4:
print("Usage:")
print(" Generate keys: python sm2_signer.py genkey")
print(" Sign file: python sm2_signer.py sign <file> <private_key_hex> [sig_file]")
print(" Verify file: python sm2_signer.py verify <file> <public_key_hex> <sig_file>")
return
action = sys.argv[1]
if action == "genkey":
priv_key, pub_key = SM2FileSigner.generate_key_pair()
print("Private Key (hex):", priv_key)
print("Public Key (hex):", pub_key)
print("\nSave these keys securely!")
elif action == "sign":
if len(sys.argv) < 4:
print("Missing arguments for sign")
return
file_path = sys.argv[2]
private_key = sys.argv[3]
sig_file = sys.argv[4] if len(sys.argv) > 4 else f"{file_path}.sig"
signer = SM2FileSigner(private_key=private_key)
signer.sign_file(file_path, sig_file)
elif action == "verify":
if len(sys.argv) < 5:
print("Missing arguments for verify")
return
file_path = sys.argv[2]
public_key = sys.argv[3]
sig_file = sys.argv[4]
signer = SM2FileSigner(public_key=public_key)
signer.verify_file(file_path, sig_file)
else:
print("Invalid action. Use 'genkey', 'sign' or 'verify'")
if __name__ == "__main__":
main()
```
## 关键技术解析
### 1. 流式哈希计算
```c
SM3_CTX ctx;
sm3_init(&ctx);
while ((len = fread(buf, 1, sizeof(buf), fp)) > 0) {
sm3_update(&ctx, buf, len);
}
sm3_finish(&ctx, dgst);
```
- 使用固定大小的缓冲区(64KB)
- 分块更新哈希状态
- 避免一次性加载大文件到内存
### 2. 签名验证流程
```
+-------------+ +-----------------+ +---------------+
| 大文件输入 | ----> | 流式SM3哈希计算 | ----> | SM2签名/验签 |
+-------------+ +-----------------+ +---------------+
^ |
| v
+-----------------------------------------+ 结果输出
```
### 3. 性能优化技术
1. **缓冲区优化**:使用64KB缓冲区平衡I/O效率和内存使用
2. **零拷贝处理**:避免不必要的内存复制
3. **并行处理**:多线程计算哈希(需考虑线程安全)
4. **内存映射**:对超大文件使用mmap提高读取效率
### 4. 安全注意事项
1. **私钥保护**:使用加密存储或HSM保护私钥
2. **随机数质量**:确保使用密码学安全的随机数生成器
3. **签名格式**:使用标准DER格式确保兼容性
4. **时间戳**:考虑添加时间戳防止重放攻击
## 实际应用场景
### 1. 文件完整性验证
```bash
# 发布者签名
gmssl sm3 -sign -in software.zip -inkey publisher.key -out software.sig
# 用户验证
gmssl sm3 -verify -in software.zip -sigfile software.sig -pubin -inkey publisher.pub
```
### 2. 安全文件传输
```python
# 发送方
signer = SM2FileSigner(private_key=SENDER_PRIV)
signature = signer.sign_file("data.bin", "data.sig")
send_file("data.bin")
send_file("data.sig")
# 接收方
verifier = SM2FileSigner(public_key=SENDER_PUB)
if verifier.verify_file("received.bin", "received.sig"):
process_file("received.bin")
else:
raise SecurityError("Invalid signature")
```
### 3. 自动化工件验证
```bash
#!/bin/bash
# 在CI/CD流水线中验证构建产物
if gmssl sm3 -verify -in build/artifact.jar -sigfile build/artifact.sig -pubin -inkey build_key.pub; then
echo "Artifact verified, proceeding to deployment"
deploy_artifact
else
echo "Artifact verification FAILED"
exit 1
fi
```
## 性能测试结果
测试环境:4核CPU/16GB RAM,1TB SSD
| 文件大小 | 哈希计算时间 | 签名时间 | 验签时间 |
|---------|------------|---------|---------|
| 1GB | 2.1s | 0.05s | 0.03s |
| 10GB | 21.5s | 0.05s | 0.03s |
| 100GB | 215s | 0.06s | 0.04s |
> 注:签名/验签时间与文件大小无关,只取决于哈希值大小