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;

九、注意事项

  1. 密钥格式:内部统一使用 HEX 字符串,公钥必须带 04 前缀(130字符),私钥为64字符
  2. 加密模式:默认 cmC1C3C2 符合国标 GM/T 0003.4-2012,只有在对接使用 C1C2C3 的旧系统时才切换
  3. 签名 UserID:必须与签名时一致,空字符串和 '1234567812345678' 有等效含义(国标默认值)
  4. 密码保护 PEM:使用 AES-128-CBC 加密,密码不能为空字符串
  5. 文件操作:大文件会全部读入内存,超大文件(>1GB)慎用
  6. 线程安全TSM2Helper 实例不是线程安全的,每个线程应创建自己的实例
posted on 2026-05-22 11:58  redhat588  阅读(22)  评论(0)    收藏  举报