Comp.SM2Helper 使用指南
依赖单元:
CnSM2,CnECC,CnBase64,CnNative
适用环境:Delphi 5.0+ / VCL / FMX / 控制台
一、概述
TSM2Helper 是 SM2 国密算法的业务逻辑封装层,将 CnSM2/CnECC 底层 API 封装为独立、易用的辅助类。不依赖任何 UI 单元,可在任意 Delphi 环境中直接使用。
类型定义
type
TSM2CryptMode = (cmC1C3C2, cmC1C2C3); // 加密模式
TSM2SigFormat = (sfHex, sfAsn1Hex, sfBase64, sfAsn1Base64); // 签名格式
TSM2KeyFormat = (kfHex, kfBase64); // 密钥输入格式
API 速查表
| 分类 | 方法 | 说明 |
|---|---|---|
| 密钥 | GenerateKeyPair |
生成 SM2 密钥对 → HEX |
| 密钥 | ValidateKeyPair |
校验公私钥是否匹配 |
| 密钥 | CalcPubKeyFromPrivKey |
从私钥计算公钥 |
| 校验 | IsValidPublicKeyHex |
公钥 HEX 格式校验(纯函数) |
| 校验 | IsValidPrivateKeyHex |
私钥 HEX 格式校验(纯函数) |
| 转换 | NormalizePublicKey |
Base64→HEX 公钥标准化 |
| 转换 | NormalizePrivateKey |
Base64→HEX 私钥标准化 |
| 转换 | PublicKeyHexToBase64 |
HEX→Base64 公钥 |
| 转换 | PrivateKeyHexToBase64 |
HEX→Base64 私钥 |
| PEM | ImportKeyPairFromPEM |
PEM 文件 → 密钥对 |
| PEM | ImportPublicKeyFromPEM |
PEM 文件 → 公钥 |
| PEM | ExportKeyPairToPEM |
密钥对 → PEM 文件 |
| PEM | ExportPublicKeyToPEM |
公钥 → PEM 文件 |
| 加解密 | EncryptBytes / DecryptBytes |
字节数组加解密 |
| 加解密 | EncryptString / DecryptString |
字符串加解密(HEX出入) |
| 加解密 | EncryptFile / DecryptFile |
文件加解密 |
| 签名 | SignBytes / VerifyBytes |
字节数组签名验签 |
| 签名 | SignFile / VerifyFile |
文件签名验签 |
| ASN1 | CryptToAsn1 / CryptFromAsn1 |
密文 ASN1 格式转换 |
二、加解密
2.1 字符串加解密
uses Comp.SM2Helper;
var
Helper: TSM2Helper;
PubKeyHex, PrivKeyHex: string;
CipherHex, PlainText: string;
begin
Helper := TSM2Helper.Create;
try
PubKeyHex := '04450C442ADA3727DA5C61BED92AF9190B0C76F87473909DD573B7C46727CB88'
+ '066630371AE32EF6CDE503E9AFD0EA9CD762B543E9F1A1733A0EA1D66C970C721E';
PrivKeyHex := 'F0D75B33B825F32B39A0AA3E143E4AE0210CB23BBFAADB006211C5053E2399A0';
// --- 加密:字符串 → HEX 密文 ---
// C1C3C2 模式,带 $04 前缀字节(国标默认)
CipherHex := Helper.EncryptString('Hello SM2', PubKeyHex);
// C1C3C2 模式,不带 $04 前缀
CipherHex := Helper.EncryptString('Hello SM2', PubKeyHex, cmC1C3C2, False);
// C1C2C3 模式(兼容旧版/特定系统)
CipherHex := Helper.EncryptString('Hello SM2', PubKeyHex, cmC1C2C3);
// --- 解密:HEX 密文 → 明文字符串 ---
PlainText := Helper.DecryptString(CipherHex, PrivKeyHex);
// 指定 C1C2C3 模式解密
PlainText := Helper.DecryptString(CipherHex, PrivKeyHex, cmC1C2C3);
finally
Helper.Free;
end;
end;
2.2 字节数组加解密
var
SourceBytes, EncBytes, DecBytes: TBytes;
begin
SourceBytes := BytesOf('Binary Data');
// 加密
EncBytes := Helper.EncryptBytes(SourceBytes, PubKeyHex);
// 解密
DecBytes := Helper.DecryptBytes(EncBytes, PrivKeyHex);
end;
2.3 文件加解密
begin
// 加密文件
if Helper.EncryptFile('D:\plain.dat', 'D:\encrypted.sm2', PubKeyHex) then
WriteLn('文件加密成功');
// 解密文件
if Helper.DecryptFile('D:\encrypted.sm2', 'D:\recovered.dat', PrivKeyHex) then
WriteLn('文件解密成功');
// 指定 C1C2C3 模式
Helper.EncryptFile('D:\plain.dat', 'D:\enc.sm2', PubKeyHex, cmC1C2C3);
Helper.DecryptFile('D:\enc.sm2', 'D:\rec.dat', PrivKeyHex, cmC1C2C3);
end;
2.4 ASN1 密文格式转换(对接 Java / OpenSSL)
// 标准 SM2 密文通过 ASN1 封装后可在不同平台间互通
var
RawHex, Asn1Hex, RestoredHex: string;
begin
// 原始密文 → ASN1 格式
Asn1Hex := Helper.CryptToAsn1(RawHex);
// ASN1 格式 → 原始密文
RestoredHex := Helper.CryptFromAsn1(Asn1Hex);
end;
三、密钥管理
3.1 生成密钥对
var
PubKeyHex, PrivKeyHex: string;
begin
if Helper.GenerateKeyPair(PubKeyHex, PrivKeyHex) then
begin
WriteLn('公钥: ', PubKeyHex); // 130 个 HEX 字符 = '04' + X(64) + Y(64)
WriteLn('私钥: ', PrivKeyHex); // 64 个 HEX 字符 = 32 字节
end;
end;
3.2 验证密钥对
if Helper.ValidateKeyPair(PubKeyHex, PrivKeyHex) then
WriteLn('密钥对有效')
else
WriteLn('密钥对不匹配');
3.3 从私钥计算公钥
PubKeyHex := Helper.CalcPubKeyFromPrivKey(PrivKeyHex);
// 返回带 '04' 前缀的完整公钥 HEX
3.4 密钥格式校验(纯函数,无副作用)
// 只校验不修改,返回 Boolean
if Helper.IsValidPublicKeyHex(Input) then
WriteLn('公钥 HEX 格式正确(130 字符,04 开头)');
if Helper.IsValidPrivateKeyHex(Input) then
WriteLn('私钥 HEX 格式正确(64 字符)');
3.5 密钥格式转换
// --- HEX → Base64 ---
var PubB64: string := Helper.PublicKeyHexToBase64(PubKeyHex);
var PrivB64: string := Helper.PrivateKeyHexToBase64(PrivKeyHex);
// --- Base64 → HEX(标准化)---
// 返回空字符串表示格式无效
PubKeyHex := Helper.NormalizePublicKey(PubB64, kfBase64);
PrivKeyHex := Helper.NormalizePrivateKey(PrivB64, kfBase64);
// --- HEX 输入标准化(去空格、统一大写)---
PubKeyHex := Helper.NormalizePublicKey(Input, kfHex);
四、PEM 密钥文件
4.1 导出 PEM
// 导出密钥对(无密码)
Helper.ExportKeyPairToPEM('D:\keys\sm2_keypair.pem', PubKeyHex, PrivKeyHex);
// 导出密钥对(带密码保护,使用 AES-128 加密)
Helper.ExportKeyPairToPEM('D:\keys\sm2_encrypted.pem', PubKeyHex, PrivKeyHex, 'MyP@ssw0rd');
// 仅导出公钥
Helper.ExportPublicKeyToPEM('D:\keys\sm2_pub.pem', PubKeyHex);
4.2 导入 PEM
var LoadedPub, LoadedPriv: string;
// 导入密钥对(无密码)
if Helper.ImportKeyPairFromPEM('D:\keys\sm2_keypair.pem', LoadedPub, LoadedPriv) then
WriteLn('导入成功');
// 导入密钥对(带密码)
if Helper.ImportKeyPairFromPEM('D:\keys\sm2_encrypted.pem', LoadedPub, LoadedPriv, 'MyP@ssw0rd') then
WriteLn('密码正确,导入成功');
// 仅导入公钥
if Helper.ImportPublicKeyFromPEM('D:\keys\sm2_pub.pem', LoadedPub) then
WriteLn('公钥导入成功');
// 注意:PEM 导入后密钥自动转为 HEX 格式
五、签名验签
5.1 签名
var
Signature: string;
UserID: string; // 签名方身份标识(国标要求)
Data: TBytes;
begin
UserID := 'ALICE123@YAHOO.COM';
Data := BytesOf('消息摘要内容');
// HEX 格式签名
Signature := Helper.SignBytes(UserID, Data, PrivKeyHex, PubKeyHex, sfHex);
// Base64 格式(适合 Web API 传输)
Signature := Helper.SignBytes(UserID, Data, PrivKeyHex, '', sfBase64);
// ASN1-HEX 格式
Signature := Helper.SignBytes(UserID, Data, PrivKeyHex, PubKeyHex, sfAsn1Hex);
// ASN1-Base64 格式(标准互通格式)
Signature := Helper.SignBytes(UserID, Data, PrivKeyHex, PubKeyHex, sfAsn1Base64);
end;
5.2 验签
if Helper.VerifyBytes(UserID, Data, Signature, PubKeyHex, sfHex) then
WriteLn('验签通过')
else
WriteLn('验签失败(签名无效/数据被篡改/公钥不匹配)');
5.3 文件签名验签
// 文件签名
Signature := Helper.SignFile('CnPack Team', 'D:\contract.pdf', PrivKeyHex, sfBase64);
// 文件验签
if Helper.VerifyFile('CnPack Team', 'D:\contract.pdf', Signature, PubKeyHex, sfBase64) then
WriteLn('文件验签通过');
六、VCL Form 集成示例
6.1 改造前的代码(问题版本)
procedure TFormSM2.btnSM2EncryptClick(Sender: TObject);
var
T: AnsiString;
SM2: TCnSM2;
PublicKey: TCnSM2PublicKey;
EnStream: TMemoryStream;
ST: TCnSM2CryptSequenceType;
begin
SM2 := TCnSM2.Create(ctSM2); // 问题:过早创建
PublicKey := TCnSM2PublicKey.Create; // 问题:Exit 时泄漏
EnStream := TMemoryStream.Create; // 问题:Exit 时泄漏
if not CheckPublicKeyStr(edtSM2PublicKey) then Exit; // 泄漏!
if Length(edtSM2Text.Text) = 0 then Exit; // 泄漏!
PublicKey.SetHex(edtSM2PublicKey.Text);
T := AnsiString(edtSM2Text.Text);
if rbC1C3C2.Checked then // 重复逻辑 #1
ST := cstC1C3C2
else
ST := cstC1C2C3;
// ... 40 行加解密代码 ...
end;
6.2 改造后的代码(使用 Helper)
procedure TFormSM2.btnSM2EncryptClick(Sender: TObject);
var
Helper: TSM2Helper;
Mode: TSM2CryptMode;
PubKey: string;
begin
Helper := TSM2Helper.Create;
try
// 先校验输入
PubKey := Helper.NormalizePublicKey(edtSM2PublicKey.Text, kfHex);
if PubKey = '' then
begin
ShowMessage('公钥 HEX 无效,需130字符,04开头');
Exit;
end;
if Length(edtSM2Text.Text) = 0 then
begin
ShowMessage('请输入要加密的文本');
Exit;
end;
// 统一获取加密模式
if rbC1C3C2.Checked then
Mode := cmC1C3C2
else
Mode := cmC1C2C3;
// 一行完成加密
mmoSM2Result.Lines.Text := Helper.EncryptString(
edtSM2Text.Text, PubKey, Mode, chkPrefixByte.Checked);
if mmoSM2Result.Lines.Text <> '' then
ShowMessage('加密成功');
finally
Helper.Free; // 无需管理任何其他对象
end;
end;
6.3 完整 Form 改造建议
将所有 Tab 页的按钮事件按以下模式统一改造:
// 1. 创建 Helper(通过 try/finally 保证释放)
// 2. 校验输入(通过 Normalize*Key / IsValid* 纯函数)
// 3. 获取参数(Mode / Format)
// 4. 调用 Helper 方法,结果直接赋值到 UI
| 原方法 | 行数 | 改用 Helper 后 |
|---|---|---|
btnSM2EncryptClick |
53行 | ~20行 |
btnSM2DecryptClick |
56行 | ~20行 |
btnSM2SignFileClick |
55行 | ~15行 |
btnSM2VerifyClick |
55行 | ~20行 |
btnSM2EncryptFileClick |
26行 | ~10行 |
btnSM2DecryptFileClick |
24行 | ~10行 |
七、属性说明
SM2 属性:高级用法
// Helper.SM2 返回底层 TCnSM2 实例(延迟创建)
// 可直接访问其属性用于高级场景
var
BytesPerCoordinate: Integer;
begin
BytesPerCoordinate := Helper.SM2.BytesCount; // = 32
// 也可以直接操作 Generator、Order 等底层对象
end;
八、错误处理模式
所有方法遵循"返回空值表示失败"的约定,不抛异常:
| 返回类型 | 成功 | 失败 |
|---|---|---|
string |
非空字符串 | '' 空字符串 |
Boolean |
True |
False |
TBytes |
长度 > 0 | SetLength(Result, 0) |
// 推荐的调用模式
var
Result: string;
begin
Result := Helper.DecryptString(CipherHex, PrivKeyHex);
if Result = '' then
begin
// 解密失败:密钥不对、密文格式错误、模式不匹配
Log('解密失败,请检查私钥和密文格式');
Exit;
end;
// 使用 Result...
end;
九、注意事项
- 密钥格式:内部统一使用 HEX 字符串,公钥必须带
04前缀(130字符),私钥为64字符 - 加密模式:默认
cmC1C3C2符合国标 GM/T 0003.4-2012,只有在对接使用 C1C2C3 的旧系统时才切换 - 签名 UserID:必须与签名时一致,空字符串和
'1234567812345678'有等效含义(国标默认值) - 密码保护 PEM:使用 AES-128-CBC 加密,密码不能为空字符串
- 文件操作:大文件会全部读入内存,超大文件(>1GB)慎用
- 线程安全:
TSM2Helper实例不是线程安全的,每个线程应创建自己的实例
中年大叔学Delphi
浙公网安备 33010602011771号