GM/T 0019-2023 通用密码服务接口规范:国密应用开发的统一语言
在国密改造中,开发者最常遇到的问题之一:同样的 SM2 签名操作,在不同厂商的密码设备(USB Key、服务器密码机、硬件安全模块 HSM)上调用方式完全不同。甲厂商用 sm2Sign(data, keyRef),乙厂商用 cryptSign(keyHandle, data, digestAlg),丙厂商用 signWithKey(keyId, message, options)。这种"万国接口"的局面导致每个项目都要重新适配底层设备,移植成本极高。
GM/T 0019-2023《通用密码服务接口规范》 正是为了解决这个问题而生。它定义了统一的密码服务接口(CSP, Cryptographic Service Provider),让上层应用无需关心底层设备细节,即可调用国密算法服务。
标准背景与定位
为什么需要通用接口?
国密算法的应用场景已从传统的 PKI/CA 扩展到:政务系统、金融交易、物联网设备、移动端应用、云计算服务等。不同场景使用的密码设备形态各异:
| 设备类型 | 典型厂商 | 接口差异 |
|---|---|---|
| USB Key(智能密码钥匙) | 三未信安、江南信安、格尔软件 | 各自私有 SDK |
| 服务器密码机 | 三未信安、吉大正元、卫士通 | 网络协议不同 |
| 硬件安全模块(HSM) | IBM、Thales、众安科技 | API 风格迥异 |
| 云密码服务 | 阿里云、腾讯云、华为云 | REST API vs SDK |
标准关系图
┌─────────────────────────────────────────────────────────┐
│ 应用层(业务代码) │
│ 调用 GM/T 0019 统一接口 │
└──────────────────────────┬──────────────────────────────┘
│ 接口调用
┌──────────────────────────▼──────────────────────────────┐
│ GM/T 0019-2023 通用密码服务接口 │
│ (CSP - Cryptographic Service Provider) │
└──────────────────────────┬──────────────────────────────┘
│ 实现适配
┌──────────────────────┼──────────────────────┐
│ │ │
▼ ▼ ▼
┌─────────┐ ┌─────────┐ ┌─────────┐
│ USB Key │ │ Server │ │ Cloud │
│ 驱动 │ │ Crypto │ │ Service │
│ 适配层 │ │ 适配层 │ │ 适配层 │
└─────────┘ └─────────┘ └─────────┘
│ │ │
▼ ▼ ▼
┌─────────┐ ┌─────────┘ ┌─────────┐
│ 硬件 │ │ 硬件 │ 虚拟 │
│设备 │ │ 密码机 │ 密码服务 │
└─────────┘ └─────────┘ └─────────┘与 GM/T 0018 的关系
GM/T 0019 与 GM/T 0018-2023《密码设备应用接口规范》是配套的姊妹标准:
| 维度 | GM/T 0018-2023 | GM/T 0019-2023 |
|---|---|---|
| 定位 | 密码设备产品规范 | 密码服务接口规范 |
| 对象 | 设备厂商 | 应用开发者 |
| 内容 | 设备应提供的功能 | 接口如何调用 |
| 关系 | 设备实现 0018 | 应用调用 0019 |
| 类比 | 硬件规格书 | 软件 API 文档 |
核心接口框架
接口分层架构
GM/T 0019-2023 将密码服务接口分为四个层次:
┌─────────────────────────────────────────────┐
│ Layer 4: 应用接口层 (Application API) │
│ - 高级封装:证书操作、签名验签、加解密 │
│ - 适用:业务开发人员 │
├─────────────────────────────────────────────┤
│ Layer 3: 密码算法层 (Crypto Algorithm API) │
│ - 基础算法:SM2/SM3/SM4/SM9/ZUC │
│ - 适用:密码算法开发者 │
├─────────────────────────────────────────────┤
│ Layer 2: 密钥管理层 (Key Management API) │
│ - 密钥生成、导入、导出、销毁 │
│ - 适用:密钥管理人员 │
├─────────────────────────────────────────────┤
│ Layer 1: 设备管理层 (Device Management API) │
│ - 设备连接、会话管理、状态查询 │
│ - 适用:系统运维人员 │
└─────────────────────────────────────────────┘Layer 1:设备管理层接口
设备管理层是最底层的接口,负责与密码设备建立连接和管理会话。
#### 设备初始化与连接
// 设备管理接口示例(C 语言风格伪代码)
typedef struct {
unsigned int version; // 接口版本号
char device_name[64]; // 设备名称
unsigned int max_sessions; // 最大并发会话数
} CSP_DEVICE_INFO;
// 初始化密码服务环境
int CSP_Initialize(void);
// 枚举已连接的密码设备
int CSP_EnumDevices(CSP_DEVICE_INFO **devices, unsigned int *count);
// 打开设备会话
int CSP_OpenSession(
const char *device_id,
unsigned int flags, // CSP_SESSION_READ / CSP_SESSION_WRITE
void **session_handle
);
// 关闭会话
int CSP_CloseSession(void *session_handle);关键参数说明:
| 参数 | 类型 | 说明 |
|---|---|---|
device_id | string | 设备唯一标识,由 CSP_EnumDevices 返回 |
flags | enum | 会话访问模式,可读可写需同时设置 |
session_handle | pointer | 输出参数,后续操作使用此句柄 |
| 错误码 | 含义 | 排查建议 |
|---|---|---|
CSP_ERR_DEVICE_NOT_FOUND | 设备未连接 | 检查 USB Key 是否插入,服务是否启动 |
CSP_ERR_SESSION_FULL | 会话数超限 | 检查是否有未关闭的会话泄漏 |
CSP_ERR_INVALID_HANDLE | 句柄无效 | 检查会话是否已关闭 |
// 查询设备状态
typedef struct {
unsigned int is_online; // 1=在线,0=离线
unsigned int firmware_version; // 固件版本
unsigned int free_memory; // 可用存储空间(字节)
unsigned int error_code; // 最后一次错误码
} CSP_DEVICE_STATUS;
int CSP_GetDeviceStatus(
const char *device_id,
CSP_DEVICE_STATUS *status
);Layer 2:密钥管理层接口
密钥管理是密码服务中最敏感的部分,GM/T 0019-2023 对密钥的生成、导入、导出、存储、销毁都做了严格规范。
#### 密钥生成
// 密钥属性定义
typedef struct {
CSP_KEY_TYPE key_type; // CSP_KEY_SM2 / CSP_KEY_SM4 / ...
unsigned int key_length; // 密钥长度(bit)
unsigned int usage_flags; // 使用权限:签名/加密/解密/密钥协商
char label[32]; // 密钥标签(便于识别)
} CSP_KEY_ATTR;
// 生成密钥对
int CSP_GenerateKeyPair(
void *session_handle,
const CSP_KEY_ATTR *attr,
char *public_key_ref, // 输出:公钥引用标识
char *private_key_ref // 输出:私钥引用标识
);
// 生成对称密钥
int CSP_GenerateSymmetricKey(
void *session_handle,
const CSP_KEY_ATTR *attr,
char *key_ref // 输出:密钥引用标识
);密钥生成示例(SM2 密钥对):
CSP_KEY_ATTR attr = {0};
attr.key_type = CSP_KEY_SM2;
attr.key_length = 256; // SM2 固定 256 位
attr.usage_flags = CSP_KEY_USAGE_SIGN | CSP_KEY_USAGE_DERIVE;
strcpy(attr.label, "server_sign_key");
char pub_ref[64] = {0};
char priv_ref[64] = {0};
int ret = CSP_GenerateKeyPair(session, &attr, pub_ref, priv_ref);
if (ret != CSP_SUCCESS) {
// 处理生成失败
return ret;
}
// 后续操作使用 pub_ref 和 priv_ref 引用密钥#### 密钥导入与导出
// 导入外部密钥
int CSP_ImportKey(
void *session_handle,
const CSP_KEY_ATTR *attr,
const unsigned char *key_data,
unsigned int key_data_len,
char *key_ref
);
// 导出公钥
int CSP_ExportPublicKey(
void *session_handle,
const char *key_ref,
unsigned char *pub_key_buf,
unsigned int *buf_len
);
// 注意:私钥原则上不允许导出
// 仅特殊情况(如密钥备份)可通过加密方式导出
int CSP_ExportEncryptedKey(
void *session_handle,
const char *key_ref,
const char *wrap_key_ref,
unsigned char *wrapped_key_buf,
unsigned int *buf_len
);安全约束:
- 私钥导入必须有身份认证(PIN 码或生物特征)
- 私钥导出必须是加密形式,且仅限授权人员操作
- 所有密钥操作记录审计日志
// 安全销毁密钥(覆写存储区域)
int CSP_DestroyKey(
void *session_handle,
const char *key_ref
);
// 销毁设备中所有密钥(工厂重置)
int CSP_ClearAllKeys(
void *session_handle,
const char *admin_pin
);Layer 3:密码算法层接口
密码算法层是应用最频繁的部分,提供 SM2/SM3/SM4/SM9/ZUC 等算法的直接调用。
#### SM2 签名接口
// SM2 签名
int CSP_SM2_Sign(
void *session_handle,
const char *private_key_ref,
const unsigned char *message,
unsigned int message_len,
unsigned char *signature, // 输出
unsigned int *sig_len
);
// SM2 验签
int CSP_SM2_Verify(
void *session_handle,
const char *public_key_ref,
const unsigned char *message,
unsigned int message_len,
const unsigned char *signature,
unsigned int sig_len
);签名数据格式:
GM/T 0019-2023 规定 SM2 签名采用 r || s 格式(GB/T 32918.2-2016),长度为 64 字节(32 字节 r + 32 字节 s)。部分厂商可能返回 DER 编码格式,接口层应统一转换为标准格式。
#### SM3 杂凑接口
// SM3 杂凑计算
int CSP_SM3_Hash(
const unsigned char *message,
unsigned int message_len,
unsigned char *digest, // 输出,32 字节
unsigned int *digest_len
);
// SM3 增量计算(大数据场景)
int CSP_SM3_Init(void **context);
int CSP_SM3_Update(void *context, const unsigned char *data, unsigned int len);
int CSP_SM3_Final(void *context, unsigned char *digest, unsigned int *digest_len);#### SM4 加密接口
// SM4 加密(ECB 模式)
int CSP_SM4_Encrypt_ECB(
void *session_handle,
const char *key_ref,
const unsigned char *plaintext,
unsigned int plaintext_len,
unsigned char *ciphertext, // 输出
unsigned int *ciphertext_len
);
// SM4 解密(ECB 模式)
int CSP_SM4_Decrypt_ECB(
void *session_handle,
const char *key_ref,
const unsigned char *ciphertext,
unsigned int ciphertext_len,
unsigned char *plaintext, // 输出
unsigned int *plaintext_len
);
// SM4 加密(CBC 模式,含 IV)
int CSP_SM4_Encrypt_CBC(
void *session_handle,
const char *key_ref,
const unsigned char *iv, // 16 字节 IV
const unsigned char *plaintext,
unsigned int plaintext_len,
unsigned char *ciphertext,
unsigned int *ciphertext_len
);
// SM4 解密(CBC 模式)
int CSP_SM4_Decrypt_CBC(
void *session_handle,
const char *key_ref,
const unsigned char *iv,
const unsigned char *ciphertext,
unsigned int ciphertext_len,
unsigned char *plaintext,
unsigned int *plaintext_len
);填充模式说明:
| 模式 | 填充方式 | 适用场景 |
|---|---|---|
| ECB | PKCS7 padding | 固定长度数据加密 |
| CBC | PKCS7 padding | 一般数据加密 |
| CTR | 无需填充 | 流式数据加密 |
| GCM | 无需填充(AEAD) | 需要认证的加密场景 |
// SM9 密钥生成(基于身份)
int CSP_SM9_KeyGen(
void *session_handle,
const char *master_priv_key_ref,
const unsigned char *identity, // 身份标识字符串
unsigned int identity_len,
char *user_priv_key_ref // 输出:用户私钥引用
);
// SM9 加密
int CSP_SM9_Encrypt(
void *session_handle,
const char *master_pub_key_ref,
const unsigned char *identity,
unsigned int identity_len,
const unsigned char *plaintext,
unsigned int plaintext_len,
unsigned char *ciphertext,
unsigned int *ciphertext_len
);
// SM9 解密
int CSP_SM9_Decrypt(
void *session_handle,
const char *user_priv_key_ref,
const unsigned char *ciphertext,
unsigned int ciphertext_len,
unsigned char *plaintext,
unsigned int *plaintext_len
);Layer 4:应用接口层
应用接口层提供高级封装,适合业务开发人员直接使用。
#### 证书操作接口
// 读取证书
int CSP_ReadCertificate(
void *session_handle,
const char *cert_ref,
unsigned char *cert_buf,
unsigned int *buf_len
);
// 证书链验证
int CSP_VerifyCertChain(
void *session_handle,
const char *leaf_cert_ref,
const char **intermediate_certs[],
unsigned int intermediate_count,
int *result // 1=有效,0=无效
);
// 证书撤销状态查询(OCSP/CRL)
int CSP_CheckCertRevocation(
void *session_handle,
const char *cert_ref,
CSP_REV_CHECK_METHOD method, // CSP_OCSP / CSP_CRL
int *revoked, // 1=已吊销,0=未吊销
char *revocation_time, // 吊销时间(可选)
unsigned int time_buf_len
);#### 数字信封接口
// 数字信封创建(SM2 加密 SM4 密钥 + SM4 加密数据)
int CSP_CreateDigitalEnvelope(
void *session_handle,
const char *recipient_pub_key_ref,
const unsigned char *plaintext,
unsigned int plaintext_len,
unsigned char *envelope_buf, // 输出:信封数据
unsigned int *envelope_len
);
// 数字信封打开
int CSP_OpenDigitalEnvelope(
void *session_handle,
const char *recipient_priv_key_ref,
const unsigned char *envelope_buf,
unsigned int envelope_len,
unsigned char *plaintext,
unsigned int *plaintext_len
);错误码规范
GM/T 0019-2023 统一定义了密码服务的错误码,避免各厂商自定义错误码导致的兼容问题。
错误码分类
| 类别 | 范围 | 说明 |
|---|---|---|
| 成功 | 0x00000000 | 操作成功 |
| 通用错误 | 0x80000000-0x800000FF | 通用性错误 |
| 设备错误 | 0x80010000-0x8001FFFF | 设备相关错误 |
| 会话错误 | 0x80020000-0x8002FFFF | 会话管理错误 |
| 密钥错误 | 0x80030000-0x8003FFFF | 密钥操作错误 |
| 算法错误 | 0x80040000-0x8004FFFF | 算法调用错误 |
| 证书错误 | 0x80050000-0x8005FFFF | 证书操作错误 |
| 内存错误 | 0x80060000-0x8006FFFF | 内存分配错误 |
常用错误码
| 错误码 | 宏定义 | 含义 |
|---|---|---|
0x80000001 | CSP_ERR_GENERAL | 通用错误 |
0x80000002 | CSP_ERR_INVALID_PARAM | 参数无效 |
0x80010001 | CSP_ERR_DEVICE_NOT_FOUND | 设备未找到 |
0x80010002 | CSP_ERR_DEVICE_BUSY | 设备忙 |
0x80020001 | CSP_ERR_INVALID_SESSION | 会话无效 |
0x80020002 | CSP_ERR_SESSION_EXISTS | 会话已存在 |
0x80030001 | CSP_ERR_KEY_NOT_FOUND | 密钥未找到 |
0x80030002 | CSP_ERR_KEY_INVALID | 密钥无效 |
0x80030003 | CSP_ERR_KEY_UNEXPORTABLE | 密钥不可导出 |
0x80040001 | CSP_ERR_ALG_UNSUPPORTED | 算法不支持 |
0x80040002 | CSP_ERR_ALG_INVALID_PARAM | 算法参数无效 |
0x80050001 | CSP_ERR_CERT_INVALID | 证书无效 |
0x80050002 | CSP_ERR_CERT_EXPIRED | 证书已过期 |
0x80050003 | CSP_ERR_CERT_REVOKED | 证书已吊销 |
与国密生态的集成
密码设备驱动适配
设备厂商需要按照 GM/T 0019-2023 实现适配层,将设备私有 API 转换为标准接口:
┌────────────────────────────────────────────┐
│ 设备厂商私有 SDK │
│ (如:UsbKeySDK.dll, CryptoMachine.lib) │
└────────────────────┬───────────────────────┘
│ 厂商实现
┌────────────────────▼───────────────────────┐
│ GM/T 0019 标准适配层 │
│ (CSP_Driver_Adapter.so / .dll) │
└────────────────────┬───────────────────────┘
│ 标准接口
┌────────────────────▼───────────────────────┐
│ 应用代码(调用 CSP_* 函数) │
└────────────────────────────────────────────┘国密 TLS 库集成
国密 TLS 库(如 Tongsuo、BabaSSL、GmSSL)可通过 GM/T 0019 接口调用硬件密码服务:
// Tongsuo 中使用 GM/T 0019 接口作为 ENGINE
ENGINE *e = ENGINE_by_id("gmt0019");
if (e == NULL) {
// 初始化失败
}
// 设置默认密码设备
ENGINE_set_default_RSA(e);
ENGINE_set_default_EC(e);
ENGINE_set_default_DH(e);
ENGINE_set_default_DIGEST(e);
ENGINE_set_default_CIPHER(e);
// 后续 TLS 握手自动使用硬件加速国密应用开发框架
主流国密开发框架已内置 GM/T 0019 支持:
| 框架 | 语言 | GM/T 0019 支持 |
|---|---|---|
| Tongsuo | C | 原生支持 |
| BabaSSL | C | 原生支持 |
| GmSSL | C/Python | 通过插件支持 |
| go-gmssl | Go | 通过 CGO 调用 |
| Java-GM | Java | 通过 JNI 调用 |
开发实战注意事项
坑 1:会话泄漏
现象:应用运行一段时间后,新无法打开密码设备会话,报错 CSP_ERR_SESSION_FULL。
原因:每次调用 CSP_OpenSession 后未调用 CSP_CloseSession,导致会话资源泄漏。
解决方案:
void *session = NULL;
int ret = CSP_OpenSession(device_id, flags, &session);
if (ret != CSP_SUCCESS) {
// 错误处理
return ret;
}
// 确保无论何种路径都关闭会话
// 推荐:使用 RAII 风格封装
CSP_SessionGuard guard(session); // 构造时打开,析构时关闭
// ... 使用 session ...
// guard 离开作用域时自动调用 CSP_CloseSession坑 2:密钥引用标识符生命周期
现象:密钥生成成功后,再次调用 CSP_SM2_Sign 时返回 CSP_ERR_KEY_NOT_FOUND。
原因:密钥引用标识符(key_ref)仅在会话有效期内有效,会话关闭后引用失效。
解决方案:
- 方案 A:保持会话长期存活,密钥引用持续有效
- 方案 B:密钥导入设备后,使用持久化标识(如密钥指纹)替代引用
// 方案 B:获取密钥指纹作为持久化标识
unsigned char key_fingerprint[32] = {0};
unsigned int fingerprint_len = sizeof(key_fingerprint);
CSP_GetKeyFingerprint(session, key_ref, key_fingerprint, &fingerprint_len);
// 后续使用指纹查找密钥(无需保持会话)
CSP_LookupKeyByFingerprint(device_id, key_fingerprint, &key_ref);坑 3:SM2 签名格式混淆
现象:验签返回失败,错误码 CSP_ERR_ALG_INVALID_PARAM。
原因:签名数据格式不一致。GM/T 0019-2023 规定使用 r || s 格式(64 字节),但部分设备默认返回 DER 编码格式。
解决方案:
unsigned char signature[64] = {0};
unsigned int sig_len = sizeof(signature);
int ret = CSP_SM2_Sign(session, priv_ref, message, msg_len, signature, &sig_len);
if (ret != CSP_SUCCESS) {
return ret;
}
// 验证签名长度,确保是 64 字节(r || s 格式)
if (sig_len != 64) {
// 可能是 DER 格式,需要转换
signature_der_to_rs(signature, &sig_len);
}坑 4:SM4 CBC 模式 IV 管理
现象:解密返回乱码或错误。
原因:CBC 模式的 IV 管理不当,加密和解密使用不同的 IV。
解决方案:
// 加密侧:生成随机 IV,随密文一起传输
unsigned char iv[16] = {0};
CSP_GenerateRandom(session, iv, sizeof(iv)); // 生成随机 IV
unsigned char ciphertext[128] = {0};
unsigned int ciphertext_len = 0;
CSP_SM4_Encrypt_CBC(session, key_ref, iv, plaintext, plaintext_len,
ciphertext, &ciphertext_len);
// 传输:iv || ciphertext(IV 不需要保密,但必须一致)
send(socket, iv, sizeof(iv), 0);
send(socket, ciphertext, ciphertext_len, 0);
// 解密侧:先接收 IV,再用 IV 解密
unsigned char recv_iv[16] = {0};
recv(socket, recv_iv, sizeof(recv_iv), 0);
unsigned char decrypted[128] = {0};
unsigned int decrypted_len = 0;
CSP_SM4_Decrypt_CBC(session, key_ref, recv_iv, ciphertext, ciphertext_len,
decrypted, &decrypted_len);坑 5:SM9 身份编码一致性
现象:SM9 加密成功,解密返回 CSP_ERR_ALG_INVALID_PARAM。
原因:加密和解密使用的身份标识(identity)编码不一致。SM9 标准规定身份编码为 UTF-8 字节串,长度不超过 64 字节。
解决方案:
// 统一使用 UTF-8 编码身份标识
const char *identity_str = "user@example.com";
unsigned char identity[65] = {0}; // 64 字节 + null terminator
strncpy((char*)identity, identity_str, 64);
unsigned int identity_len = strlen(identity_str);
// 加密和解密使用相同的 identity_len 和 identity 数据
CSP_SM9_Encrypt(session, master_pub_ref, identity, identity_len,
plaintext, plaintext_len, ciphertext, &ciphertext_len);
CSP_SM9_Decrypt(session, user_priv_ref, ciphertext, ciphertext_len,
plaintext, &plaintext_len);合规实施建议
密评要求
根据 GB/T 39786-2021《信息系统密码应用基本要求》,密码服务接口应符合以下要求:
| 密码级别 | 接口要求 | 审计要求 |
|---|---|---|
| 第一级 | 使用符合 GM/T 0019 的接口 | 基本操作日志 |
| 第二级 | 使用符合 GM/T 0019 的接口 | 完整操作日志 |
| 第三级 | 使用符合 GM/T 0019 的接口,私钥不出设备 | 完整日志 + 访问控制 |
| 第四级 | 使用符合 GM/T 0019 的接口,HSM 托管 | 完整日志 + 双因素认证 |
实施检查清单
开发团队应逐项检查:
- [ ] 所有密码操作通过 GM/T 0019 标准接口调用
- [ ] 私钥不导出密码设备(除非必要且加密导出)
- [ ] 密钥使用完成后及时销毁引用
- [ ] 会话使用后及时关闭
- [ ] 所有密码操作记录审计日志
- [ ] 错误处理符合标准错误码规范
- [ ] SM2 签名格式统一为
r || s(64 字节) - [ ] SM4 CBC 模式 IV 正确管理
- [ ] SM9 身份编码统一为 UTF-8
总结
GM/T 0019-2023 是国密应用开发的"普通话",它让不同厂商的密码设备能够被同一套代码调用,大幅降低了国密改造的适配成本。对于开发者而言,掌握这个标准意味着:
- 移植更容易:更换密码设备只需更换驱动适配层,业务代码无需修改
- 维护更简单:统一接口减少代码量,降低维护成本
- 合规更便捷:符合密评要求,减少合规风险
参考资料:
相关实践: