[Delphi]一个功能完备的国密SM4类(TSM4)

Posted BlueStorm

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了[Delphi]一个功能完备的国密SM4类(TSM4)相关的知识,希望对你有一定的参考价值。

本软件使用Delphi 10.3.3编写和测试, 源码中用到了System.NetEncoding和Generics.Collections两个单元, 因此本程序仅支持Delphi XE及更新的版本.

支持6种加密模式: ECB, CBC, CFB, OFB, PCBC, CTR; 默认为ECB;

支持7种填充模式(ZERO, PKCS5, PKCS7, ISO10126, ANSIX923, OneAndZero); 默认为PKCS7;

SM4要求密码长度的长度为16个字节(128bit), 如果长度不足, 程序就填充0x00来补足

SM4要求初始向量的长度为16个字节(128bit), 如果长度不足, 程序就填充0x00来补足

SrcBuffer, KeyBuffer, IVBuffer, DestBuffer: TBuffer 说明:

SrcBuffer存放待加密/解密的数据,

KeyBuffer存放密码数据,

IVBuffer存放初始向量数据,

DestBuffer存放已加密/解密的数据

TBuffer本身带有各种数据转换函数, 数据转换非常方便,不需要另外再写代码,例如:

function ToString(Encoding: TEncoding): String;

function ToHexString: String;

function ToDelimitedHexString(Prefix: String; Delimitor: String): String;

function ToBase64String: String;

procedure ToBytes(var OutBytes: TBytes; ByteLen: Integer);

procedure FromString(const Str: String);

procedure FromString(const Str: String; Encoding: TEncoding);

procedure FromHexString(const Str: String);

procedure FromDelimitedHexString(HexStr: String; Prefix: String; Delimitor: String');

procedure FromBase64String(const Str: String);

procedure FromBytes(const InBytes: TBytes; ByteLen: Integer);

最简单的TSM4使用示范代码:

//最简单的使用示范
procedure TMainForm.Test;
var
  S1, S2: String;
begin
  with TSM4.Create(cmCBC, pmPKCS5) do
  begin
    SrcBuffer.FromString('先学着让自己值钱');
    KeyBuffer.FromBase64String('MTIzNDU2Nzg5MDEyMzQ1Ng=='); //1234567890123456
//或KeyBuffer.FromString('1234567890123456');
    IVBuffer.FromHexString('6162636465666768696A6B6C6D6E6F70'); //abcdefghijklmnop
    Encrypt;  //加密
    S1 := DestBuffer.ToHexString; //6C19FEC30147DBDE9539DC1DF0F3ACF09B6E6210F0F220D3D923F200DC5A44C4
    
    SrcBuffer.FromHexString(S1);
    Decrypt;  //解密
    S2 := DestBuffer.ToString; //'先学着让自己值钱'
    Free;
  end;
end;

完整的TSM4代码(包括测试代码)

unit uMain;

interface

uses
$IF CompilerVersion <= 22
  Windows, Messages, Generics.Collections, SysUtils,
  Variants, Classes, Graphics, Controls, Forms,
  Dialogs, NetEncoding, StdCtrls, uSM4;
$ELSE
  Winapi.Windows, Winapi.Messages, Generics.Collections, System.SysUtils,
  System.Variants, System.Classes, Vcl.Graphics, Vcl.Controls, Vcl.Forms,
  Vcl.Dialogs, System.NetEncoding, Vcl.StdCtrls, uSM4;
$ENDIF

type
  TMainForm = class(TForm)
    LabelBlockMode: TLabel;
    LabelPaddingmode: TLabel;
    ComboBoxCipherMode: TComboBox;
    ComboBoxPaddingmode: TComboBox;
    LabelStr: TLabel;
    LabelIV: TLabel;
    LabelEnc: TLabel;
    LabelDec: TLabel;
    EditKey: TEdit;
    EditIV : TEdit;
    EditDec: TEdit;
    EditEnc: TEdit;
    EditStr: TEdit;
    LabelKey: TLabel;
    ButtonEncrypt: TButton;
    ButtonDecrypt: TButton;
    ComboBoxStr: TComboBox;
    ComboBoxKey: TComboBox;
    ComboBoxIV: TComboBox;
    ComboBoxEnc: TComboBox;
    ComboBoxDec: TComboBox;
    LabelStrCnt: TLabel;
    LabelKeyCnt: TLabel;
    LabelIVCnt : TLabel;
    LabelEncCnt: TLabel;
    LabelDecCnt: TLabel;
    EditSrc: TEdit;
    ButtonToHex: TButton;
    ButtonToBase64: TButton;
    EditDest: TEdit;
    RzEdit1: TEdit;
    Label1: TLabel;
    ButtonFromHex: TButton;
    ButtonFramBase64: TButton;
    Label2: TLabel;
    Label3: TLabel;
    procedure FormCreate(Sender: TObject);
    procedure EditChange(Sender: TObject);
    procedure ButtonClick(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
    procedure ButtonConvertClick(Sender: TObject);
    procedure ButtonFromClick(Sender: TObject);
  private type
    TProc = procedure(const S: String) of object;
    Tfunc = function(): String of object;
    TFromDict = TDictionary<String, TProc>;
    TToDict   = TDictionary<String, TFunc>;
    TCountDict = TDictionary<TEdit, TLabel>;
  private
    SM4: TSM4;
    DIctCm: TDictionary<String, TCipherMode>;
    DIctPm: TDictionary<String, TPaddingMode>;
    DictFromStr, DictFromKey, DictFromIV, DictFromEnc: TFromDict;
    DictToEnc, DictToDec: TToDict;
    DictCount: TCountDict;
    procedure FillDicts;
  public
     Public declarations 
  end;

var
  MainForm: TMainForm;

implementation

$R *.dfm

uses uBuffer;

procedure TMainForm.FormCreate(Sender: TObject);
begin
  ReportMemoryLeaksOnShutDown := True;
  SM4 := TSM4.Create;

  DIctCm := TDictionary<String, TCipherMode>.Create;
  DIctPm := TDictionary<String, TPaddingMode>.Create;

  DictFromStr := TFromDict.Create;
  DictFromKey := TFromDict.Create;
  DictFromIV  := TFromDict.Create;
  DictFromEnc := TFromDict.Create;

  DictToEnc := TToDict.Create;
  DictToDec := TToDict.Create;

  DictCount := TCountDict.Create;

  FillDicts;
  EditChange(nil);
end;

procedure TMainForm.FormDestroy(Sender: TObject);
begin
  DIctCm.Free;
  DIctPm.Free;

  DictFromStr.Free;
  DictFromKey.Free;
  DictFromIV.Free;
  DictFromEnc.Free;

  DictToEnc.Free;
  DictToDec.Free;

  DictCount.Free;

  SM4.Free;
end;

procedure TMainForm.FillDicts;
var
  SrcBuffer, KeyBuffer, IVBuffer, DestBuffer: TBuffer;
begin
  SrcBuffer := SM4.SrcBuffer;
  KeyBuffer := SM4.KeyBuffer;
  IVBuffer  := SM4.IVBuffer;
  DestBuffer:= SM4.DestBuffer;

  with DIctCm do
  begin
    Add('ECB' , cmECB);
    Add('CBC' , cmCBC);
    Add('CFB' , cmCFB);
    Add('OFB' , cmOFB);
    Add('PCBC', cmPCBC);
    Add('CTR' , cmCTR);
  end;

  with DIctPm do
  begin
    Add('ZERO'      , pmZERO);
    Add('PKCS5'     , pmPKCS5);
    Add('PKCS7'     , pmPKCS7);
    Add('ISO10126'  , pmISO10126);
    Add('ANSIX923'  , pmANSIX923);
    Add('OneAndZero', pmOneAndZero);
  end;

  with DictFromStr do
  begin
    Add('String', SrcBuffer.FromString);
    Add('Hex'   , SrcBuffer.FromHexString);
    Add('Base64', SrcBuffer.FromBase64String);
  end;

  with DictFromKey do
  begin
    Add('String', KeyBuffer.FromString);
    Add('Hex'   , KeyBuffer.FromHexString);
    Add('Base64', KeyBuffer.FromBase64String);
  end;

  with DictFromIV do
  begin
    Add('String', IVBuffer.FromString);
    Add('Hex'   , IVBuffer.FromHexString);
    Add('Base64', IVBuffer.FromBase64String);
  end;

  with DictFromEnc do
  begin
    Add('Hex'   , SrcBuffer.FromHexString);
    Add('Base64', SrcBuffer.FromBase64String);
  end;

  with DictToEnc do
  begin
    Add('Hex'   , DestBuffer.ToHexString);
    Add('Base64', DestBuffer.ToBase64String);
  end;

  with DictToDec do
  begin
    Add('String', DestBuffer.ToString);
    Add('Hex'   , DestBuffer.ToHexString);
    Add('Base64', DestBuffer.ToBase64String);
  end;

  with DictCount do
  begin
    Add(EditStr, LabelStrCnt);
    Add(EditKey, LabelKeyCnt);
    Add(EditIV , LabelIVCnt );
    Add(EditEnc, LabelEncCnt);
    Add(EditDec, LabelDecCnt);
  end;
end;

procedure TMainForm.ButtonConvertClick(Sender: TObject);
begin
  with TBuffer.Create do
  begin
    FromString(EditSrc.Text);
    if Sender = ButtonToHex then
      EditDest.Text := ToHexString
    else
      EditDest.Text := ToBase64String;
    Free;
  end;
end;

procedure TMainForm.ButtonFromClick(Sender: TObject);
begin
  with TBuffer.Create do
  begin
    if Sender = ButtonFromHex then
      FromHexString(EditDest.Text)
    else
      FromBase64String(EditDest.Text);
    EditSrc.Text := ToString;
    Free;
  end;
end;

procedure TMainForm.ButtonClick(Sender: TObject);
begin
  SM4.CipherMode  := DIctCm.Items[ComboBoxCipherMode.Text];
  SM4.PaddingMode := DIctPm.Items[ComboBoxPaddingMode.Text];

  DictFromKey.Items[ComboBoxKey.Text](EditKey.Text);
  DictFromIV.Items[ComboBoxIV.Text](EditIV.Text);
  if Sender = ButtonEncrypt then
  begin
    DictFromStr.Items[ComboBoxStr.Text](EditStr.Text);
    SM4.Encrypt;
    EditEnc.Text := DictToEnc.Items[ComboBoxEnc.Text]();
  end
  else
  begin
    DictFromEnc.Items[ComboBoxEnc.Text](EditEnc.Text);
    SM4.Decrypt;
    EditDec.Text := DictToDec.Items[ComboBoxDec.Text]();
  end;
end;

procedure TMainForm.EditChange(Sender: TObject);
var
  Edit: TEdit;
begin
  if Sender <> nil then
  begin
    Edit := TEdit(Sender);
    DictCount.Items[Edit].Caption := Length(Edit.Text).ToString;
  end
  else
  begin
    for Edit in DictCount.Keys do
    begin
      DictCount.Items[Edit].Caption := Length(Edit.Text).ToString;
    end;
  end;
end;

end.
object MainForm: TMainForm
  Left = 0
  Top = 0
  Caption = 'SM4'#21152#23494#35299#23494#27979#35797
  ClientHeight = 452
  ClientWidth = 788
  Color = clBtnFace
  Font.Charset = DEFAULT_CHARSET
  Font.Color = clWindowText
  Font.Height = -11
  Font.Name = 'Tahoma'
  Font.Style = []
  OldCreateOrder = False
  Position = poScreenCenter
  OnCreate = FormCreate
  OnDestroy = FormDestroy
  DesignSize = (
    788
    452)
  PixelsPerInch = 96
  TextHeight = 13
  object LabelBlockMode: TLabel
    Left = 5
    Top = 108
    Width = 48
    Height = 13
    Caption = #21152#23494#27169#24335
  end
  object LabelPaddingmode: TLabel
    Left = 132
    Top = 108
    Width = 48
    Height = 13
    Caption = #22635#20805#27169#24335
  end
  object LabelStr: TLabel
    Left = 28
    Top = 149
    Width = 24
    Height = 13
    Caption = #26126#25991
  end
  object LabelIV: TLabel
    Left = 4
    Top = 234
    Width = 48
    Height = 13
    Caption = #21021#22987#21521#37327
  end
  object LabelEnc: TLabel
    Left = 26
    Top = 314
    Width = 24
    Height = 13
    Caption = #23494#25991
  end
  object LabelDec: TLabel
    Left = 26
    Top = 402
    Width = 24
    Height = 13
    Caption = #26126#25991
  end
  object LabelKey: TLabel
    Left = 26
    Top = 193
    Width = 24
    Height = 13
    Caption = #23494#30721
  end
  object LabelStrCnt: TLabel
    Left = 758
    Top = 150
    Width = 24
    Height = 13
    Anchors = [akTop, akRight]
    Caption = #23383#25968
  end
  object LabelKeyCnt: TLabel
    Left = 758
    Top = 192
    Width = 24
    Height = 13
    Anchors = [akTop, akRight]
    Caption = #23383#25968
  end
  object LabelIVCnt: TLabel
    Left = 758
    Top = 234
    Width = 24
    Height = 13
    Anchors = [akTop, akRight]
    Caption = #23383#25968
  end
  object LabelEncCnt: TLabel
    Left = 758
    Top = 314
    Width = 24
    Height = 13
    Anchors = [akTop, akRight]
    Caption = #23383#25968
  end
  object LabelDecCnt: TLabel
    Left = 758
    Top = 400
    Width = 24
    Height = 13
    Anchors = [akTop, akRight]
    Caption = #23383#25968
  end
  object Label1: TLabel
    Left = 5
    Top = 35
    Width = 48
    Height = 13
    Caption = #23545#27604#32467#26524
  end
  object Label2: TLabel
    Left = 512
    Top = 1
    Width = 28
    Height = 13
    Caption = 'String'
  end
  object Label3: TLabel
    Left = 494
    Top = 64
    Width = 89
    Height = 13
    Caption = 'Hex/Base64 [utf8]'
  end
  object ComboBoxCipherMode: TComboBox
    Left = 59
    Top = 105
    Width = 54
    Height = 21
    TabOrder = 0
    Text = 'ECB'
    Items.Strings = (
      'ECB'
      'CBC'
      'PCBC'
      'CFB'
      'OFB'
      'CTR')
  end
  object ComboBoxPaddingmode: TComboBox
    Left = 185
    Top = 105
    Width = 68
    Height = 21
    TabOrder = 1
    Text = 'PKCS7'
    Items.Strings = (
      'PKCS5'
      'PKCS7'
      'ANSIX923'
      'OneAndZero'
      'ISO10126'
      'ZERO'
      '')
  end
  object EditKey: TEdit
    Left = 119
    Top = 188
    Width = 633
    Height = 21
    Anchors = [akLeft, akTop, akRight]
    TabOrder = 2
    Text = '1234567890123456'
    OnChange = EditChange
  end
  object EditIV: TEdit
    Left = 119
    Top = 230
    Width = 633
    Height = 21
    Anchors = [akLeft, akTop, akRight]
    TabOrder = 3
    Text = 'abcdefghijklmnop'
    OnChange = EditChange
  end
  object EditDec: TEdit
    Left = 119
    Top = 396
    Width = 633
    Height = 21
    Anchors = [akLeft, akTop, akRight]
    TabOrder = 4
    OnChange = EditChange
  end
  object EditEnc: TEdit
    Left = 119
    Top = 310
    Width = 633
    Height = 21
    Anchors = [akLeft, akTop, akRight]
    TabOrder = 5
    OnChange = EditChange
  end
  object EditStr: TEdit
    Left = 119
    Top = 146
    Width = 633
    Height = 21
    Anchors = [akLeft, akTop, akRight]
    TabOrder = 6
    Text = #20808#23398#30528#35753#33258#24049#20540#38065
    OnChange = EditChange
  end
  object ButtonEncrypt: TButton
    Left = 329
    Top = 268
    Width = 137
    Height = 24
    Caption = #21152#23494
    TabOrder = 7
    OnClick = ButtonClick
  end
  object ButtonDecrypt: TButton
    Left = 329
    Top = 351
    Width = 137
    Height = 24
    Caption = #35299#23494
    TabOrder = 8
    OnClick = ButtonClick
  end
  object ComboBoxStr: TComboBox
    Left = 57
    Top = 146
    Width = 58
    Height = 21
    TabOrder = 9
    Text = 'String'
    Items.Strings = (
      'String'
      'Hex'
      'Base64')
  end
  object ComboBoxKey: TComboBox
    Left = 57
    Top = 188
    Width = 58
    Height = 21
    TabOrder = 10
    Text = 'String'
    Items.Strings = (
      'String'
      'Hex'
      'Base64')
  end
  object ComboBoxIV: TComboBox
    Left = 57
    Top = 230
    Width = 58
    Height = 21
    TabOrder = 11
    Text = 'String'
    Items.Strings = (
      'String'
      'Hex'
      'Base64')
  end
  object ComboBoxEnc: TComboBox
    Left = 57
    Top = 310
    Width = 58
    Height = 21
    TabOrder = 12
    Text = 'Hex'
    Items.Strings = (
      'Hex'
      'Base64')
  end
  object ComboBoxDec: TComboBox
    Left = 57
    Top = 396
    Width = 58
    Height = 21
    TabOrder = 13
    Text = 'String'
    Items.Strings = (
      'String'
      'Hex'
      'Base64')
  end
  object EditSrc: TEdit
    Left = 361
    Top = 16
    Width = 345
    Height = 21
    Anchors = [akLeft, akTop, akRight]
    TabOrder = 14
    Text = #20808#23398#30528#35753#33258#24049#20540#38065
  end
  object ButtonToHex: TButton
    Left = 293
    Top = 41
    Width = 62
    Height = 22
    Caption = 'ToHex'
    TabOrder = 15
    OnClick = ButtonConvertClick
  end
  object ButtonToBase64: TButton
    Left = 713
    Top = 41
    Width = 65
    Height = 22
    Anchors = [akTop, akRight]
    Caption = 'ToBase64'
    TabOrder = 16
    OnClick = ButtonConvertClick
  end
  object EditDest: TEdit
    Left = 361
    Top = 41
    Width = 345
    Height = 21
    Anchors = [akLeft, akTop, akRight]
    TabOrder = 17
  end
  object RzEdit1: TEdit
    Left = 57
    Top = 32
    Width = 196
    Height = 21
    TabOrder = 18
    Text = 'https://javalang.cn/crypto/sm4.html'
  end
  object ButtonFromHex: TButton
    Left = 294
    Top = 16
    Width = 61
    Height = 22
    Caption = 'FromHex'
    TabOrder = 19
    OnClick = ButtonFromClick
  end
  object ButtonFramBase64: TButton
    Left = 713
    Top = 15
    Width = 65
    Height = 22
    Anchors = [akTop, akRight]
    Caption = 'FromBase64'
    TabOrder = 20
    OnClick = ButtonFromClick
  end
end
unit uSM4;

interface

uses
$IF CompilerVersion <= 22
  Forms, Classes, Windows, SysUtils, NetEncoding,
$ELSE
  Vcl.Forms, System.Classes, Winapi.Windows, System.SysUtils, System.NetEncoding,
$ENDIF
  uConst, uBuffer;

type
  TCipherMode  = (cmECB, cmCBC, cmPCBC, cmCFB, cmOFB, cmCTR);
  TPaddingMode = (pmZERO, pmPKCS5, pmPKCS7, pmISO10126, pmANSIX923, pmOneAndZero);

  TSM4 = class(TObject)
  private type
    TWord = UInt32;
    TBlock    = array[0..4 -1] of TWord;
    TRoundKey = array[0..32-1] of TWord;
    TCryptType = (ctEncrypt, ctDecrypt);
    TProc = procedure (var A: TBlock) of object;
  private
    RK: TRoundKey;
    MK, DataBlock, IVBlock: TBlock;
    BlockSize, BlockLen, RKLen: Integer;
    function SBoxMap(A: TWord): TWord;
    function ROTL(A: TWord; N: Byte): TWord;
    procedure CryptBlock(const CryptType: TCryptType; var A: TBlock);
    procedure ExpandKey;
    procedure ReverseEndian(var A: TBlock);
    procedure AppendPadding;
    procedure RemovePadding;
    procedure CheckKeyBufferAndIVBuffer;
    procedure XorBlock(var A: TBlock; const B: TBlock); overload;
    procedure XorBlock(var A: TBlock; const B, C: TBlock); overload;
    procedure IncBlock(Var A: TBlock);
    procedure Crypt(CryptType: TCryptType);
  public
    CipherMode : TCipherMode;
    PaddingMode: TPaddingMode;
    SrcBuffer, KeyBuffer, IVBuffer, DestBuffer: TBuffer;
    procedure Encrypt;
    procedure Decrypt;
    constructor Create(aCipherMode: TCipherMode = cmECB; aPaddingMode: TPaddingMode = pmPKCS7);
    destructor Destroy; override;
  end;

implementation

constructor TSM4.Create(aCipherMode: TCipherMode; aPaddingMode: TPaddingMode);
begin
  inherited Create;
  CipherMode  := aCipherMode;
  PaddingMode := aPaddingMode;
  SrcBuffer  := TBuffer.Create;
  KeyBuffer  := TBuffer.Create;
  IVBuffer   := TBuffer.Create;
  DestBuffer := TBuffer.Create;
  BlockSize := SizeOf(TBlock);  //16
  BlockLen  := SizeOf(TBlock) div Sizeof(UInt32);  //4
  RKLen := SizeOf(TRoundKey) div Sizeof(UInt32); //32
end;

destructor TSM4.Destroy;
begin
  SrcBuffer.Free;
  KeyBuffer.Free;
  IVBuffer.Free;
  DestBuffer.Free;
  inherited;
end;

procedure TSM4.Encrypt;
begin
  AppendPadding;
  Crypt(ctEncrypt);
end;

procedure TSM4.Decrypt;
begin
  Crypt(ctDecrypt);
  RemovePadding;
end;

procedure TSM4.Crypt(CryptType: TCryptType);
var
  InBlock: TBlock;
  DataLen, OffSet: Integer;
begin
  CheckKeyBufferAndIVBuffer;
  DataLen := SrcBuffer.Length;
  DestBuffer.Length := DataLen;
  KeyBuffer.ToBytes(MK, BlockSize);
  if CipherMode <> cmECB then
  begin
    IVBuffer.ToBytes(IVBlock, BlockSize);
  end;
  ExpandKey;
  OffSet := 0;
  while OffSet < DataLen do
  begin
    SrcBuffer.OffsetToBytes(OffSet, DataBlock, BlockSize);
    case CipherMode of
      cmECB: //Electronic Codebook (ECB)
        begin
          CryptBlock(CryptType, DataBlock);
        end;
      cmCBC: //Cipher Block Chaining (CBC)
        begin
          if CryptType = ctEncrypt then
          begin
            XorBlock(DataBlock, IVBlock);
            CryptBlock(CryptType, DataBlock);
            IVBlock := DataBlock;
          end
          else
          begin
            InBlock := DataBlock;
            CryptBlock(CryptType, DataBlock);
            XorBlock(DataBlock, IVBlock);
            IVBlock := InBlock;
          end;
        end;
      cmPCBC: //Propagating Cipher Block Chaining (PCBC)
        begin
          InBlock := DataBlock;
          if CryptType = ctEncrypt then
          begin
            XorBlock(DataBlock, IVBlock);
            CryptBlock(CryptType, DataBlock);
          end
          else
          begin
            CryptBlock(CryptType, DataBlock);
            XorBlock(DataBlock, IVBlock);
          end;
          XorBlock(IVBlock, DataBlock, InBlock);
        end;
      cmCFB: //Cipher Feedback (CFB)
        begin
          InBlock := DataBlock;
          CryptBlock(ctEncrypt, IVBlock); //不管是加密还是解密,这里都是用Encrypt对IV进行操作
          XorBlock(DataBlock, IVBlock);
          if CryptType = ctEncrypt then
            IVBlock := DataBlock
          else
            IVBlock  := InBlock;
        end;
      cmOFB: //Output Feedback (OFB)
        begin
          CryptBlock(ctEncrypt, IVBlock); //不管是加密还是解密,这里都是用Encrypt对IV进行操作
          XorBlock(DataBlock, IVBlock);
        end;
      cmCTR: //Counter (CTR)
        begin
          InBlock := IVBlock;
          CryptBlock(ctEncrypt, InBlock); //不管是加密还是解密,这里都是用Encrypt对IV进行操作
          XorBlock(DataBlock, InBlock);
          IncBlock(IVBlock);
        end;
    end;
    DestBuffer.OffsetFromBytes(OffSet, DataBlock, BlockSize);
    Inc(OffSet, BlockSize);
  end;
end;

procedure TSM4.AppendPadding; //添加填充
var
  I, M, N, Len, NewLen: Integer;
begin
  Len := Length(SrcBuffer.Data);
  M := (Len mod BlockSize);
  if  (M = 0) and (PaddingMode = pmZero) then
    N := 0
  else
    N := BlockSize - M;
  NewLen := Len + N;
  SetLength(SrcBuffer.Data, NewLen);

  case PaddingMode of
    pmPKCS5, pmPKCS7:
    begin
      for I := Len to NewLen-1 do SrcBuffer.Data[I] := N;
    end;
    pmANSIX923:
    begin
      for I := Len to NewLen-1 do
      begin
        if I < NewLen-1 then
          SrcBuffer.Data[I] := 0
        else
          SrcBuffer.Data[I] := N;
      end;
    end;
    pmISO10126:
    begin
      Randomize;
      for I := Len to NewLen-1 do
      begin
        if I < NewLen-1 then
          SrcBuffer.Data[I] := Random(255)
        else
          SrcBuffer.Data[I] := N;
      end;
    end;
    pmZERO:
    begin
      for I := Len to NewLen-1 do SrcBuffer.Data[I] := 0;
    end;
    pmOneAndZero:
    begin
      for I := Len to NewLen-1 do
      begin
        if I = Len then
          SrcBuffer.Data[I] := $80
        else
          SrcBuffer.Data[I] := 0;
      end;
    end;
  end;
end;

procedure TSM4.RemovePadding; //移除填充
var
  I, M, Len: Integer;
begin
  Len := Length(DestBuffer.Data);
  case PaddingMode of
    pmPKCS5, pmPKCS7, pmANSIX923, pmISO10126:
    begin
      M := DestBuffer.Data[Len-1];
      SetLength(DestBuffer.Data,  Len-M);
    end;
    pmZERO:
    begin
      for I := Len-1 downto 0 do
      begin
        if DestBuffer.Data[I] = 0 then
          Dec(Len)
        else
          Break;
      end;
      SetLength(DestBuffer.Data, Len);
    end;
    pmOneAndZero:
    begin
      for I := Len-1 downto 0 do
      begin
        if DestBuffer.Data[I] <> $80 then
          Dec(Len)
        else
          Break;
      end;
      SetLength(DestBuffer.Data, Len-1);
    end;
  end;
end;

procedure TSM4.CheckKeyBufferAndIVBuffer; //对Key和IV不足16Bytes的情况以0填足
var
  I, Len: Integer;
begin
  Len := Length(KeyBuffer.Data);
  SetLength(KeyBuffer.Data, BlockSize);
  for I := Len to BlockSize - 1 do
  begin
    KeyBuffer.Data[I] := 0;
  end;

  if CipherMode <> cmECB then //ECB模式不需要初始矢量IV
  begin
    Len := Length(IVBuffer.Data);
    SetLength(IVBuffer.Data, BlockSize);
    for I := Len to BlockSize - 1 do
    begin
      IVBuffer.Data[I] := 0;
    end;
  end;
end;

procedure TSM4.ExpandKey;
  function Transform(A: TWord): TWord; //合成置换
  begin
    A := SBoxMap(A); //非线性变换
    Result := A xor ROTL(A, 13) xor ROTL(A, 23); //线性变换
  end;
var
  K: TBlock;
  I: Integer;
  M0, M1, M2, M3: TWord;
begin
  ReverseEndian(MK); //32整数位大小端转换

  for I := 0 to BlockLen-1 do
  begin
    K[I] := MK[I] xor FK[I];
  end;

  for I := 0 to RKLen-1 do //32次迭代运算
  begin
    M0 := (I  ) mod 4;
    M1 := (I+1) mod 4;
    M2 := (I+2) mod 4;
    M3 := (I+3) mod 4;
    K[M0] := K[M0] xor Transform(K[M1] xor K[M2] xor K[M3] xor CK[I]); //迭代运算
    RK[I] := K[M0];
  end;
end;

procedure TSM4.CryptBlock(const CryptType: TCryptType; var A: TBlock);
  function TransForm(A: TWord): TWord; //合成置换
  begin
    A := SBoxMap(A); //非线性变换
    Result := A xor ROTL(A, 2) xor ROTL(A, 10) xor ROTL(A, 18) xor ROTL(A, 24); //线性变换
  end;
var
  T: TBlock;
  I,  R: Integer;
  M0, M1, M2, M3: TWord;
begin
  ReverseEndian(A); //32位整数大小端(Endian)转换

  for I := 0 to RKLen-1 do  //32次迭代运算
  begin
    if CryptType = ctEncrypt then
      R := I
    else
      R := 31 - I;  //解密时使用反序的RK

    M0 := (I  ) mod 4;
    M1 := (I+1) mod 4;
    M2 := (I+2) mod 4;
    M3 := (I+3) mod 4;

    A[M0] := A[M0] xor TransForm(A[M1] xor A[M2] xor A[M3] xor RK[R]); //迭代运算
  end;

  ReverseEndian(A); //32整数位大小端(Endian)转换

  T := A;
  for I := 0 to 3 do //Block内部的4个32位整数的位置做反序变换
  begin
    A[I] := T[3 - I];
  end;
end;

function TSM4.SBoxMap(A: TWord): TWord; //S盒非线性变换(Byte to Byte的固定映射
var
  I: Integer;
  B: array[0..3] of Byte absolute A;
  R: array[0..3] of Byte absolute Result;
begin
  for I := 0 to 3 do
  begin
    R[I] := SBox[B[I]];
  end;
end;

procedure TSM4.ReverseEndian(var A: TBlock); //32位大小端(Endian)变换
var
  I: Integer;
begin
  for I := 0 to BlockLen-1 do
  begin
    A[I] := ((A[I] and $FF000000) shr 24) or
            ((A[I] and $00FF0000) shr 8 ) or
            ((A[I] and $0000FF00) shl 8 ) or
            ((A[I] and $000000FF) shl 24);
  end;
end;

function TSM4.ROTL(A: TWord; N: Byte): TWord; //32位循环左移N位
begin
  Result := (A shl N) or (A shr (32-N));
end;

procedure TSM4.XorBlock(var A: TBlock; const B: TBlock); //两个Block异或
var
  I: Integer;
begin
  for I := 0 to BlockLen-1 do
  begin
    A[I] := A[I] xor B[I];
  end;
end;

procedure TSM4.XorBlock(var A: TBlock; const B, C: TBlock); //两个Block异或
var
  I: Integer;
begin
  for I := 0 to BlockLen-1 do
  begin
    A[I] := B[I] xor C[I];
  end;
end;

procedure TSM4.IncBlock(var A: TBlock); //Block值加1
var
  I: Integer;
begin
  for I := 0 to BlockLen-1 do
  begin
    if I > 0 then
    begin
      A[I-1] := 0;
    end;
    if (I = BlockLen-1) or (A[I] < $FFFFFFFF) then
    begin
      Inc(A[0]);
      Exit;
    end;
  end;
end;

end.
unit uConst;

interface

const
// S盒
  Sbox: array[0..255] of Byte = (
    $d6,$90,$e9,$fe,$cc,$e1,$3d,$b7,$16,$b6,$14,$c2,$28,$fb,$2c,$05,
    $2b,$67,$9a,$76,$2a,$be,$04,$c3,$aa,$44,$13,$26,$49,$86,$06,$99,
    $9c,$42,$50,$f4,$91,$ef,$98,$7a,$33,$54,$0b,$43,$ed,$cf,$ac,$62,
    $e4,$b3,$1c,$a9,$c9,$08,$e8,$95,$80,$df,$94,$fa,$75,$8f,$3f,$a6,
    $47,$07,$a7,$fc,$f3,$73,$17,$ba,$83,$59,$3c,$19,$e6,$85,$4f,$a8,
    $68,$6b,$81,$b2,$71,$64,$da,$8b,$f8,$eb,$0f,$4b,$70,$56,$9d,$35,
    $1e,$24,$0e,$5e,$63,$58,$d1,$a2,$25,$22,$7c,$3b,$01,$21,$78,$87,
    $d4,$00,$46,$57,$9f,$d3,$27,$52,$4c,$36,$02,$e7,$a0,$c4,$c8,$9e,
    $ea,$bf,$8a,$d2,$40,$c7,$38,$b5,$a3,$f7,$f2,$ce,$f9,$61,$15,$a1,
    $e0,$ae,$5d,$a4,$9b,$34,$1a,$55,$ad,$93,$32,$30,$f5,$8c,$b1,$e3,
    $1d,$f6,$e2,$2e,$82,$66,$ca,$60,$c0,$29,$23,$ab,$0d,$53,$4e,$6f,
    $d5,$db,$37,$45,$de,$fd,$8e,$2f,$03,$ff,$6a,$72,$6d,$6c,$5b,$51,
    $8d,$1b,$af,$92,$bb,$dd,$bc,$7f,$11,$d9,$5c,$41,$1f,$10,$5a,$d8,
    $0a,$c1,$31,$88,$a5,$cd,$7b,$bd,$2d,$74,$d0,$12,$b8,$e5,$b4,$b0,
    $89,$69,$97,$4a,$0c,$96,$77,$7e,$65,$b9,$f1,$09,$c5,$6e,$c6,$84,
    $18,$f0,$7d,$ec,$3a,$dc,$4d,$20,$79,$ee,$5f,$3e,$d7,$cb,$39,$48
  );

// 密钥扩展算法的常数FK
  FK: array[0..3] of UInt32 = ($a3b1bac6, $56aa3350, $677d9197, $b27022dc);

// 密钥扩展算法的固定参数CK
  CK: array[0..31] of UInt32 = (
    $00070e15, $1c232a31, $383f464d, $545b6269,
    $70777e85, $8c939aa1, $a8afb6bd, $c4cbd2d9,
    $e0e7eef5, $fc030a11, $181f262d, $343b4249,
    $50575e65, $6c737a81, $888f969d, $a4abb2b9,
    $c0c7ced5, $dce3eaf1, $f8ff060d, $141b2229,
    $30373e45, $4c535a61, $686f767d, $848b9299,
    $a0a7aeb5, $bcc3cad1, $d8dfe6ed, $f4fb0209,
    $10171e25, $2c333a41, $484f565d, $646b7279
);

implementation

end.
unit uBuffer;

interface

uses
$IF CompilerVersion <= 22
  Forms, Classes, Windows, SysUtils, NetEncoding;
$ELSE
  Vcl.Forms, System.Classes, Winapi.Windows, System.SysUtils, System.NetEncoding;
$ENDIF

type
  TBuffer = class
  private
    function  GetDataLength: Integer; inline;
    procedure SetDataLength(Len: Integer); inline;
    function  GetItem(Index: Integer): Byte; inline;
    procedure SetItem(Index: Integer; Value: Byte); inline;
  public
    Data: TBytes;   //TBytes = array of Byte;
    procedure FromString(const Str: String); overload;  //默认为utf8
    procedure FromString(const Str: String; Encoding: TEncoding); overload;
    procedure FromHexString(const Str: String);
    procedure FromDelimitedHexString(HexStr: String; Prefix: String = '$'; Delimitor: String = ',');
    procedure FromBase64String(const Str: String);
    procedure FromBytes(const InBytes: TBytes; ByteLen: Integer = -1); overload;
    procedure FromBytes(const InBytes: array of Byte; ByteLen: Integer = -1); overload;
    procedure FromBytes(const Ints: array of UInt32; ByteLen: Integer); overload;
    procedure OffsetFromBytes(Offset: Integer; const Ints: array of UInt32; ByteLen: Integer);
    procedure FromStream(const Stream: TStream; ByteLen: Integer = -1);
    procedure FromFile(const FileName: String);
    function  ToString: String; reintroduce; overload;  //默认为utf8
    function  ToString(Encoding: TEncoding): String; reintroduce; overload;
    function  ToHexString: String;
    function  ToDelimitedHexString(Prefix: String = '$'; Delimitor: String = ', '): String;
    function  ToBase64String: String;
    procedure ToBytes(var OutBytes: TBytes; ByteLen: Integer = -1); overload;
    procedure ToBytes(var OutBytes: array of Byte; ByteLen: Integer = -1); overload;
    procedure ToBytes(var Ints: array of UInt32; ByteLen: Integer = 1); overload;
    procedure OffsetToBytes(Offset: Integer; var Ints: array of UInt32; ByteLen: Integer);
    procedure ToStream(const Stream: TStream);
    procedure ToFile(const FileName: String; Warning: Boolean = True);
    property  Length: Integer read GetDataLength write SetDataLength;
    property  Bytes[Index: Integer]: Byte read GetItem write SetItem; default;
  end;

resourcestring
  SInvalidBufSize = 'Invalid buffer size for ouput';

implementation

function TBuffer.GetDataLength: Integer;
begin
  Result := System.Length(Data);
end;

procedure TBuffer.SetDataLength(Len: Integer);
begin
  System.SetLength(Data, Len);
end;

function  TBuffer.GetItem(Index: Integer): Byte;
begin
  Result := Data[Index];
end;
procedure TBuffer.SetItem(Index: Integer; Value: Byte);
begin
  Data[Index] := Value;
end;

procedure TBuffer.FromString(const Str: String);
begin
  Data := TEncoding.UTF8.GetBytes(Str);
end;

procedure TBuffer.FromString(const Str: String; Encoding: TEncoding);
begin
  Data := Encoding.GetBytes(Str);
end;

procedure TBuffer.FromHexString(const Str: String);
var
  Len: Integer;
begin
  Len := System.Length(Str) div 2;
  SetLength(Data, Len);
  HexToBin(PChar(Str), @Data[0], Len)
end;

procedure TBuffer.FromDelimitedHexString(HexStr: String; Prefix: String; Delimitor: String);
var
  Len: Integer;
begin
  HexStr := HexStr.Replace(Prefix      , '');
  HexStr := HexStr.Replace(Delimitor , '');
  HexStr := HexStr.Replace(' '       , '');
  Len := System.Length(HexStr) div 2;
  SetLength(Data, Len);
  HexToBin(PChar(HexStr), @Data[0], Len)
end;

procedure TBuffer.FromBase64String(const Str: String);
var
  Base64Encoding: TBase64Encoding;
begin
//Base64Encoding := TBase64Encoding.Create; //含换行符
  Base64Encoding := TBase64Encoding.Create(0); //不含换行符
  Data := Base64Encoding.DecodeStringToBytes(Str);
  Base64Encoding.Free;
end;

procedure TBuffer.FromBytes(const InBytes: array of Byte; ByteLen: Integer);
begin
  if (ByteLen = -1) then  ByteLen := System.Length(InBytes);
  SetLength(Data, ByteLen);
  Move(InBytes[0], Data[0], ByteLen);
end;

procedure TBuffer.FromBytes(const InBytes: TBytes; ByteLen: Integer);
begin
  if (ByteLen = -1) then  ByteLen := System.Length(InBytes);
  SetLength(Data, ByteLen);
  Move(InBytes[0], Data[0], ByteLen);
end;

procedure TBuffer.FromBytes(const Ints: array of UInt32; ByteLen: Integer);
begin
  SetLength(Data, ByteLen);
  Move(Ints[0], Data[0], ByteLen);
end;

procedure TBuffer.OffsetFromBytes(Offset: Integer; const Ints: array of UInt32; ByteLen: Integer);
begin
  Move(Ints[0], Data[Offset], ByteLen);
end;

procedure TBuffer.FromStream(const Stream: TStream; ByteLen: Integer);
begin
  if (ByteLen = -1) then ByteLen := Stream.Size;
  SetLength(Data, ByteLen);
  Stream.Read(Data, ByteLen);
end;

procedure TBuffer.FromFile(const FileName: String);
var
  Stream: TFileStream;
begin
  Stream := TFileStream.Create(FileName, fmOpenRead);
  SetLength(Data, Stream.Size);
  Stream.Read(Data, Stream.Size);
  Stream.Free;
end;

function TBuffer.ToString: String;
begin
  Result := TEncoding.UTF8.GetString(Data);
end;

function TBuffer.ToString(Encoding: TEncoding): String;
begin
  Result := Encoding.GetString(Data);
end;

function TBuffer.ToHexString: String;
var
  Len: Integer;
begin
  Len := System.Length(Data);
  SetLength(Result, 2*Len);
  BinToHex(@Data[0], PChar(Result), Len);
end;

function TBuffer.ToDelimitedHexString(Prefix: String; Delimitor: String): String;
var
  I, Len: Integer;
begin
  Result := '';
  Len := System.Length(Data);
  for I := 0 to Len-1 do
  begin
    Result := Result + Prefix + IntToHex(Data[I], 2);
    if I < Len-1 then
      Result := Result + Delimitor;
  end;
end;

function TBuffer.ToBase64String: String;
var
  Base64Encoding: TBase64Encoding;
begin
//Base64Encoding := TBase64Encoding.Create; //含换行符
  Base64Encoding := TBase64Encoding.Create(0); //不含换行符
  Result := Base64Encoding.EncodeBytesToString(Data);
  Base64Encoding.Free;
end;

procedure TBuffer.ToBytes(var OutBytes: array of Byte; ByteLen: Integer);
begin
  if (ByteLen = -1) then ByteLen := System.Length(Data);
  if (ByteLen > System.Length(OutBytes)) then
    raise Exception.Create(SInvalidBufSize);
  Move(Data[0], OutBytes[0], ByteLen);
end;

procedure TBuffer.ToBytes(var OutBytes: TBytes; ByteLen: Integer);
begin
  if ByteLen = -1 then ByteLen := System.Length(Data);
  SetLength(OutBytes, ByteLen);
  Move(Data[0], OutBytes[0], ByteLen);
end;

procedure TBuffer.ToBytes(var Ints: array of UInt32; ByteLen: Integer);
begin
  if ByteLen = -1 then ByteLen := System.Length(Data);
  Move(Data[0], Ints[0], ByteLen);
end;

procedure TBuffer.OffsetToBytes(Offset: Integer; var Ints: array of UInt32; ByteLen: Integer);
begin
  Move(Data[Offset], Ints[0], ByteLen);
end;

procedure TBuffer.ToStream(const Stream: TStream);
begin
  Stream.Write(Data, System.Length(Data));
end;

procedure TBuffer.ToFile(const FileName: String; Warning: Boolean);
var
  Stream: TFileStream;
begin
  if Warning and FileExists(FileName) and
     (Application.MessageBox(PChar('File ' + FileName + ' Exists, Overwrite It?'),
                  'Warning: File Exists', MB_YESNO) = IDNO)  then Exit;

  Stream := TFileStream.Create(FileName, fmCreate);
  Stream.Write(Data, System.Length(Data));
  Stream.Free;
end;

end.

谈谈PBOC3.0中使用的国密SM2算法

转载请注明出处

http://blog.csdn.net/pony_maggie/article/details/39780825


作者:小马


 

一 知识准备

 

SM2是国密局推出的一种他们自己说具有自主知识产权的非对称商用password算法。本身是基于ECC椭圆曲线算法的。所以要讲sm2, 先要弄懂ECC。

 

全然理解ECC算法须要一定的数学功底。由于涉及到射影平面坐标系,齐次方程求解, 曲线的运算规则等概念。

这里不做过多的数学分析(主要是我自己也没有全然整明确)。

想要深入了解ECC的我推荐网名为ZMWorm 的大牛在多年前写的<<椭圆曲线ECC加密算法入门介绍>>。此人是早年看雪论坛中的一个版主,对算法和password学非常有研究。

 

本篇的主旨还是希望能以简单通俗的语言,讲清楚PBOC3.0认证过程中,所用到的SM2的相关概念,包含它的实现,使用等。

 

1 椭圆曲线究竟是什么样的

 

技术分享

图1

技术分享

图2


 

上面是两个不同椭圆曲线在坐标系中的几何表示, 只是这个坐标系不是二维坐标系,而是射影坐标系。能够用空间思维想像一下(可是注意不是三维坐标系), 打个例如,你晚上站在一个路灯前面,地上有你的影子,你本身是在一个二维坐标系(把你想像成一个纸片),和你的影子一起构成一个射影坐标系。

 

曲线的每个点, 用三个參量表示, (X,Y,Z)。我们知道在二维坐标系里的每个图形都遵循一个方程,比方直接的二元一次方程是y=kx+b, 圆的方程是(x-a)2+(y-b)2=r2, 椭圆曲线在射影坐标系里也有自己的定义:

 

Y2Z+a1XYZ+a3YZ2=X3+a2X2Z+a4XZ2+a6Z3

 

全部椭圆曲线上的点都满足上述方程。a1,a2,a3,a4,a6是系数,决定曲线的形状和位置。

 

二维坐标和射影坐标有一个相应关系。即x=X/Z, y=Y/Z, 这样就能够把上面的方程转成普通的二维坐标系方程:

y2+a1xy+a3y= x3+a2x2+a4x+a6

 

 

2 离散的椭圆曲线

上面的坐标系都是基于实数的,椭圆曲线看起来都是平滑的,假设我们限制曲线的点都必须是整数。曲线就变成离散的了。如图3所看到的:

技术分享

图3

 

再进一步限制。要求整数必须大于0, 小于某个大整数P, 这样就形成了一个有限域Fp.然后我们在这个有限域里定义一些点与点之间的加减乘除运算规则。比方A点加B点得到C点(记做A+B≡C (mod p))。或者A点乘以n得到K点(记做A×n≡K (mod p))。

至于详细规则细节能够不用关心,仅仅要知道有这种操作就可以。

 

选一条曲线,比方

y2=x3+ax+b

把它定义在Fp上, 要求a,b满足:

4a3+27b2≠0 (mod p)

 

我们把这种曲线记为Ep(a,b)

 

加解密是基于这种数学难题,K=kG,当中 K,G为Ep(a,b)上的点,k是整数,小于G点(注意区分。不是我们寻常说的那个意思)的阶(不用关心什么是点的阶)。给定k和G,计算K非常easy;但给定K和G,求k就困难了。

这样。G就叫做基点。k是私钥。K是公钥。

 

最后总结。描写叙述一条Fp上的椭圆曲线,有六个參量:
T=(p,a,b,G,n,h)。


p 、a 、b 用来确定一条椭圆曲线,
G为基点。
n为点G的阶。
h 是椭圆曲线上全部点的个数m与n相除的整数部分)

 

知识准备阶段知道这么多就能够了。

 

 

二 SM2在PBOC认证中的使用

 

1 签名和验签的原理

 

前面提到依据系数的不同,ECC曲线能够有非常多,SM2使用当中一种,这就表明它的曲线方程,以及前面说到的六个參量都是固定的。

依据国密局给出的规范定义例如以下:

 

y2=x3+ax+b
 
p=FFFFFFFE FFFFFFFFFFFFFFFF FFFFFFFF FFFFFFFF 00000000 FFFFFFFF FFFFFFFF
a=FFFFFFFE FFFFFFFFFFFFFFFF FFFFFFFF FFFFFFFF 00000000 FFFFFFFF FFFFFFFC
b=28E9FA9E 9D9F5E344D5A9E4B CF6509A7 F39789F5 15AB8F92 DDBCBD41 4D940E93
n=FFFFFFFE FFFFFFFFFFFFFFFF FFFFFFFF 7203DF6B 21C6052B 53BBF409 39D54123
Gx=32C4AE2C 1F198119 5F990446 6A39C994 8FE30BBF F2660BE1 715A4589 334C74C7
Gy=BC3736A2 F4F6779C 59BDCEE3 6B692153 D0A9877C C62A4740 02DF32E5 2139F0A0


这里easy引起一个误解,会觉得參数都固定了,公私钥是不是仅仅能有一对?当然不是,注意前面提到的K=kG的模型,K才是公钥,所以公钥事实上是曲线在离散坐标系中,满足条件的一个曲线上的点。能够有非常多个。另外, 从这几个參量能够获知PBOC 3.0的公钥长度都是256位。

 

基于这样的离散椭圆曲线原理的SM2算法一般有三种使用方法。签名验签。加解密, 密钥交换。PBOC 3.0中的脱机数据认证仅仅用到签名验签的功能。

终端关心的是怎样验签,卡片则要考虑怎样实现生成签名。

 

2 基于openssl实现sm2

这里给出一个基于openssl的sm2实现, 假设不了解openssl,能够先搜索一下相关知识,这里不解说。

openssl已经实现ECC算法接口,也就是核心已经有了。实现sm2事实上并不难,关键是理解它里面各种接口怎样使用。以下就分析一个终端验签的sm2实现。另外须要说明这里给出的仅仅是代码片段。仅供理解用。

 

函数接口

int SM2_Verify(BYTE* Px,BYTE* Py, BYTE* DataIn,DWORD DataLen, BYTE* sigrs)

前两个參数是K的坐标值,也就是公钥。

Datain和datalen各自是明文数据和其长度,最后一个參数是待验证的签名。

 

曲线的參数常量定义例如以下:

////////////////////////////////////////////////////////////////
static const char *group_p ="FFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000FFFFFFFFFFFFFFFF";
static const char *group_a ="FFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000FFFFFFFFFFFFFFFC";
static const char *group_b ="28E9FA9E9D9F5E344D5A9E4BCF6509A7F39789F515AB8F92DDBCBD414D940E93";
static const char *group_Gx ="32C4AE2C1F1981195F9904466A39C9948FE30BBFF2660BE1715A4589334C74C7";
static const char *group_Gy ="BC3736A2F4F6779C59BDCEE36B692153D0A9877CC62A474002DF32E52139F0A0";
static const char *group_n = "FFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFF7203DF6B21C6052B53BBF40939D54123";
static const char *ENTL_ID ="008031323334353637383132333435363738";
#define SM2_KEY_LENGTH 32 //256位曲线
////////////////////////////////////////////////////////////////

ENTL_ID是pboc规范中指定的用于SM3产生摘要的报文头数据。

 

 

strcpy(szBuff, ENTL_ID);
strcat(szBuff,group_a);
strcat(szBuff,group_b);
strcat(szBuff,group_Gx);
strcat(szBuff,group_Gy);
AscToBcd(szDataForDigest,(unsigned char *)szBuff, nLen);
 
      ….
 
SM3(szDataForDigest,nLen+SM2_KEY_LENGTH*2, digestZA);
 
memcpy(szDataForDigest,digestZA, ECC_LENGTH);
memcpy(szDataForDigest+ECC_LENGTH,DataIn, DataLen);
 
SM3(szDataForDigest,DataLen+ECC_LENGTH, digestH);
第一步,对上述数据用sm3做摘要,摘要的结果与数据拼接后再做摘要。

 

p = BN_new();
a = BN_new();
b = BN_new();
group = EC_GROUP_new(EC_GFp_mont_method());
 
BN_hex2bn(&p, group_p))
BN_hex2bn(&a, group_a))
BN_hex2bn(&b, group_b))
这里是把定义的曲线常量转换成大数表式,这样才干使用openssl中的接口。


Group是ECC中的曲线组,它是ECC算法的核心。为什么这么说呢? 由于这个group里的全部字段就确定了曲线的全部信息, 后面会看到,这里仅仅是用EC_GROUP_new生成一个空的group, 然后由p,a,b等參数来填充group, 再以这个group为基础去生成曲线上的点。

 

if (!EC_GROUP_set_curve_GFp(group, p, a, b,ctx))
{
                   gotoerr_process;
}
 
P = EC_POINT_new(group);
Q = EC_POINT_new(group);
R = EC_POINT_new(group);
if (!P || !Q || !R)
{
         gotoerr_process;
}
这一段就确定了group的全部信息。而且依据group生成三个曲线上的点(点一定在曲线上,这个非常重要)。

 

 

<span style="white-space:pre">	</span>x = BN_new();
         y= BN_new();
         z= BN_new();
         if(!x || !y || !z)
         {
                   gotoerr_process;
         }
 
         //Gx
         if(!BN_hex2bn(&x, group_Gx))
         {
                   gotoerr_process;
         }
 
         if(!EC_POINT_set_compressed_coordinates_GFp(group, P, x, 0, ctx))
         {
                   gotoerr_process;
         }
        
         if(!BN_hex2bn(&z, group_n))
         {
                   gotoerr_process;
         }
         if(!EC_GROUP_set_generator(group, P, z, BN_value_one()))
         {
                   gotoerr_process;
         }
 
         if(!EC_POINT_get_affine_coordinates_GFp(group, P, x, y, ctx))
         {
                   gotoerr_process;
         }

这一段首先是设置n到group, n前面讲过,是曲线的阶。另外就是由G点坐标x,y确定点P,这里我事实上也有点不太明确。G点坐标y本来就是已知的,为什么要再通过曲线变换表式生成y,是不是想要对照前面的group信息是否正确?仅仅是为了校验?

 

 

if ((eckey = EC_KEY_new()) == NULL)
         {
                   gotoerr_process;
         }
         if(EC_KEY_set_group(eckey, group) == 0)
         {
                   gotoerr_process;
         }
 
EC_KEY_set_public_key(eckey, P);
         if(!EC_KEY_check_key(eckey))
         {
                   gotoerr_process;
         }
 
 
         if(SM2_do_verify(1, digestH, SM2_KEY_LENGTH, signature, sig_len, eckey) != 1)
         {
                   gotoerr_process;
         }

这段比較好理解,生成公钥eckey,并由eckey终于验签。

验签的运行函数sm2_do_verify有点复杂,这里不做过多的解释(事实上是我解释不清楚, 哇哈哈),在一个国外的站点上找到一个比較好的描写叙述,拿过来用用。

 

技术分享

技术分享


3 脱机数据认证使用sm2的详细流程


我如果看这篇文章的人对PBOC 2.0中基于RSA国际算法的脱机数据认证流程已经比較了解。相关概念不再过多描写叙述。重点关注二者的差异性。

 

另外,简单说一下sm3,由于以下会用到。sm3是一个类似hash的杂凑算法,即给定一个输入(一般非常长),产生一个固定长度的输出(sm3是32个字节,hash是20个字节)。它有两个特点:

1 不可逆性。即无法由输出推导出输入。

2 不同的输入,产生不同的输出。

 

以下就拿终端SDA认证卡片来看看sm2怎样在pboc中使用的。

 

首先。发卡行公钥等数据(还包含公钥,算法标识,有效期等信息)被CA私钥签名(注意不是加密)生成发卡行公钥证书,这个证书会个人化到ic卡中。这些数据例如以下图所看到的:

技术分享

图4

 

签名事实上就是对这个表里的数据做一系列运算,终于生成一个64字节的数据,分别由各32字节的r和s拼接而成(r和s仅仅是个符号而已,没有特别意思)。这64字节的签名拼接到图4后面就是发卡行公钥证书。

 

这里与国际算法的公钥证书就有明显的差别了。国际算法证书是密文的。发卡行公钥用rsa加密。

而国密的公钥证书全然是明文。用数据举例,以下这些数据来自pboc3.0的卡片送检指南,就是检測时要求个人化到卡里的数据。

 

【发卡行公钥】 :
173A31DD681C6F8FE3BA6C354AD3924A4ADFD15EB0581BC1B37A1EB1C88DA29B47155F62FCF4CCCD201B134351A049D77E81F6A6C66E9CB32664F41348DA11F
 
【CA哈希值】(r||s) :
3499A2A0A7FED8F74F119B416FF728BA98EF0A32A36BCCB8D0110623D466425CA44C68F8E49121D9BFA9484CAEF9B476C5EB576D1A8DD6BC4A0986AF4134ABAF
 
【Tag_90 】(发卡行公钥证书) :
1262280001122000000204001140173A31DD681C6F8FE3BA6C354AD3924A4ADFD15EB0581BC1B37A1EB1C88DA29B47155F62FCF4CCCD201B134351A049D77E86A6C66E9CB32664F41348DA11F63499A2A0A7FED8F74F119B416FF728BA98EF0A32A36BCCB8D0110623D466425CA44C68F8E49121D9BFA9484CAEF9B476C5E56D1A8DD6BC4A0986AF4134ABAF

能够自己的解析一下,看看是否和上面表格中的数据一致, 而且都是明文。

 

 

终端在读记录阶段获取发卡行公钥证书。国际算法须要用rsa公钥解密整个证书,然后验证hash,通过后取出发卡行公钥。而国密算法,终端仅仅要用sm2公钥验64字节的签名,通过后直接取明文发卡行公钥,所以国密的验签的动作事实上就相当于国际里的rsa解密和hash对照两个动作。

 

接着终端进行静态数据签名的验证,情况类似,对于国密,这些数据都是明文形式,后面拼接64字节的sm2签名,这64字节是用发卡行私钥对明文数据签名得到的。终端要做的就是拿刚刚获取的发卡行公钥对这64字节数据验证就可以,验证通过就表示SDA通过。

 

四 PBOC为什么要选择国密

 

首先从技术角度,实现相同的计算复杂度,ECC的计算量相对RSA较小,所以效率高。RSA如今在不断的添加模长,眼下都用到了2048位。并非说如今一定要用2048位才是安全的,仅仅是它的安全性更高,破解难度更大,这个要综合考虑,位数高也意味着成本高。

有些不差钱的大公司比方谷歌就已经未雨绸缪的把2048位用在了它们的gmail邮箱服务中。PBOC3.0 认证中眼下仅仅用到1984位。事实上也是相对安全的。

 

只是这个观点眼下还存在争议。

前段时间在清华大学听了一个关于password算法的课,清华有个教授觉得sm2并不见得比rsa更高级,仅仅是sm2的原理比rsa难理解,所以大部分人觉得它会相对安全些。一旦椭圆曲线被大家研究透了。sm2的光环也可能就此褪去。当然这个也是他个人的观点。

 

另外一个因素,要从国家战略的角度考虑,RSA之前一直被传与美国安全局合作,在算法中增加后门。这样的事是宁可信其有的。

国密算法咱起码是自己研发的东西,全部的过程细节都一清二楚, 不用担收后门的事情。


央行如今非常重视国产安全芯片的推进工作,前些天央行的李晓枫还公开强调未来金融IC卡芯片要国产化,国密算法是当中非常关键的一步。

未来国产芯片加国密算法,才会有真正自主, 安全的国产金融IC卡产品。

 





















以上是关于[Delphi]一个功能完备的国密SM4类(TSM4)的主要内容,如果未能解决你的问题,请参考以下文章

Java笔记-SM3(国密3)和SM4(国密4)的使用

密码技术--国密SM4分组密码算法及Go语言应用

国密SM4分组密码算法(对称加密)的JS和JAVA类库

国密gmssl介绍(SM2SM3SM4算法)

国密算法 SM2 SM3 SM4分别用作什么

利用PBFunc在Powerbuilder中进行国密SM4的加密解密操作