这个是本人原创,请在引用时保留本人名号;非常感谢CnPack的CnSM3!
该算法可用于密码保护,基于国密SM3,并进行了加盐及混淆处理,比较安全!
可实现的功能:将密码进行加密后可直接存储至数据库;另外可直接调用函数直接进行密码校验;
{******************************************************************************}
{ 基于国密SM3的加盐密钥算法 }
{ Chrysalis }
{ QQ:342667266 }
{ ------------------------------------ }
{ }
{ 本单元是完全开源的,您可以遵照Apache License 2.0的发布 }
{ 协议来修改和重新发布这一程序。 }
{ }
{ 发布这一单元的目的是希望它有用,但没有任何担保。甚至没有 }
{ 适合特定目的而隐含的担保。 }
{ }
{ 如果您有更好的代码,在修改后还望抄送一份给我,谢谢 }
{ 电子邮件:342667266@qq.com }
{ }
{******************************************************************************}
unit uSM3Salt;
{* |<PRE>
================================================================================
* 单元名称:基于国密SM3的加盐密钥算法实现单元
* 单元作者:Chrysalis
* 备 注:本单元是在调用CnPack 开发组的算法库CnSM3的基础上,
* 实现了国家商用密码 SM3 杂凑算法加盐处理。
* 参考国密算法公开文档《SM3 Cryptographic Hash Algorith》
* http://www.oscca.gov.cn/UpFile/20101222141857786.pdf
* CnPack 算法包
* https://www.cnpack.org/download.php?id=757&lang=zh-cn
*
* 开发平台:Windows 10 + Delphi XE11
* 兼容测试:无
* 本 地 化:该单元中的字符串均符合本地化处理方式
* 修改记录:2025.05.29 V0.1
* 移植并创建单元
*
================================================================================
|</PRE>}
{
使用方法:
1. 将密码加密后存入数据库
var
sPassWord : string;
begin
sPassWord := '12?3aB#323';
Result := HashPassword(sPassWord); //其中Result可直接存储至数据库
//$gs$432137$79ZjUxOGY4OTMyMzcyNDI4ZjhlMTYzNzBlZDRhYmU0Zjc=$63QTk0NDQ0RUNDQTEwQjZENURCOUEzRDdFNzM2NkI2ODkwRkRFRDIyNjNDMTc4RTJGNUQ4QzMwRkU0NTY5RENEQQ==
end;
2. 使用校验
var
sPassWord, sPassWord_DB : string; //sPassWord 需要校验的密码明文 sPassWord_DB 数据库中存储的加密字符串
begin
sPassWord := '12?3aB#323';
sPassWord_DB := LoadFromDB; //从数据库中获取的需要比较的加密字符串
if CheckPassword(sPassWord, sPassWord_DB) then
showmessage('PASS')
else
showmessage('NoPASS')
end;
特别说明:
1. 目前盐 使用GUID值,比较单一,可自行生成更复杂的盐
2. 目前混淆算法除了常用的前缀法和后缀法以外,还自行编写了交叉法以及交叉倒叙法,加强混淆
3. 在加密密码时,故意增加了一个小的校验值;目的是干扰破解者视线以及增加一点点的校验功能
}
interface
uses
system.SysUtils, System.NetEncoding, System.Classes, System.Math, CnSM3;
const
ConfusionModeCount = 6;
divisor = 100;
type
TConfusionMode = (
CM_Prefix = 1, //前缀
CM_Suffix = 2, //后缀
CM_Cross_P = 3, //交叉_由Passphrase开始
CM_Cross_S = 4, //交叉_由salt开始
CM_Cross_P_D = 5, //交叉_由Passphrase开始_倒序
CM_Cross_S_D = 6 //交叉_由salt开始_倒序
);
/// <summary>加盐函数</summary>
/// <remarks> 通过该函数,可以将原始密码进行加盐混淆处理 </remarks>
/// <param name="Passphrase">String 输入的原始密码</param>
/// <param name="salt">string 盐字符串</param>
/// <param name="PP">Integer 混淆算法ID</param>
/// <returns>string</returns>
function merge(Passphrase:string; salt:string; PP:Integer):string;
/// <summary>密码生成</summary>
/// <remarks> 通过该函数,可生成用于数据库存储的密钥 </remarks>
/// <param name="Passphrase">String 输入的原始密码</param>
/// <returns>string</returns>
function HashPassword(const Passphrase : String) : string;
/// <summary>密码校验</summary>
/// <remarks> 通过该函数,可生成用于明文密码与数据库存储的密钥对比 </remarks>
/// <param name="Passphrase">String 需校验的密码</param>
/// <param name="ExpectedHashString">String 数据库中存储密钥加密后的字符串</param>
/// <returns>Boolean</returns>
function CheckPassword(const Passphrase : String; ExpectedHashString:string):Boolean;
implementation
function merge(Passphrase:string; salt:string; PP:Integer):string;
var
I : Integer;
_iMod : Integer;
_iLen_P, _iLen_S : Integer;
begin
Result := '';
if Passphrase = '' then Exit;
_iMod := PP mod ConfusionModeCount;
case _iMod of
Ord(CM_Prefix) :
begin
//前缀
Result := salt + Passphrase;
end;
Ord(CM_Suffix) :
begin
//后缀
Result := Passphrase + salt;
end;
Ord(CM_Cross_P) :
begin
//交叉_由Passphrase开始
_iLen_P := Length(Passphrase);
_iLen_S := Length(salt);
for I := 1 to Max(_iLen_P, _iLen_S) do
begin
if I < _iLen_P then
Result := Result + Passphrase[I];
if I < _iLen_S then
Result := Result + salt[I];
end;
end;
Ord(CM_Cross_S) :
begin
//交叉_由salt开始
_iLen_P := Length(Passphrase);
_iLen_S := Length(salt);
for I := 1 to Max(_iLen_P, _iLen_S) do
begin
if I < _iLen_S then
Result := Result + salt[I];
if I < _iLen_P then
Result := Result + Passphrase[I];
end;
end;
Ord(CM_Cross_P_D) :
begin
//交叉_由Passphrase开始_倒序
_iLen_P := Length(Passphrase);
_iLen_S := Length(salt);
for I := Max(_iLen_P, _iLen_S) downto 1 do
begin
if I < _iLen_P then
Result := Result + Passphrase[I];
if I < _iLen_S then
Result := Result + salt[I];
end;
end;
Ord(CM_Cross_S_D) :begin
//交叉_由salt开始_倒序
_iLen_P := Length(Passphrase);
_iLen_S := Length(salt);
for I := Max(_iLen_P, _iLen_S) downto 1 do
begin
if I < _iLen_S then
Result := Result + salt[I];
if I < _iLen_P then
Result := Result + Passphrase[I];
end;
end;
end;
end;
function HashPassword(const Passphrase : String) : string;
var
_BytesSrc, _ByteSalt : TBytes;
_Passphrase, _sSrc, _sDec : String;
_iLen, _iPP, _iS , _iP : Integer;
_sSalt : string;
function GetGUID: string;
var
LTep: TGUID;
sGUID: string;
begin
CreateGUID(LTep);
sGUID := GUIDToString(LTep);
sGUID := StringReplace(sGUID, '-', '', [rfReplaceAll]);
sGUID := Copy(sGUID, 2, Length(sGUID) - 2);
Result := LowerCase(sGUID);
end;
begin
{
Generate a password hash, letting sm3 decide the best parameters.
Password hash is returned as a string of the form:
$gs$ppsspp$salt$hash
pp - model
ss - salt check
ps - password check
salt - base64 encoded salt 1-16 bytes decoded
hash - base64 encoded 64-byte scrypt hash
For example.
Password: "correct horse battery staple"
Hash: "$gs$124454$G34Rvmk2DSkp9sFJyyM49O$z3XxEUNlHDhq2nCR1Yh4tqKCelFQ9gnFNgtmgoBJHW4zeAIDoAjV5zcOLYk5lLqoGEFQNQ6YoOvXHAlVjPJS9e"
pp 12
ss 44
ps 55
Salt (base64): G34Rvmk2DSkp9sFJyyM49O
Password (base64): z3XxEUNlHDhq2nCR1Yh4tqKCelFQ9gnFNgtmgoBJHW4zeAIDoAjV5zcOLYk5lLqoGEFQNQ6YoOvXHAlVjPJS9e
}
try
Result := '';
_Passphrase := Trim(Passphrase);
_iLen := Length(_Passphrase);
if _iLen <= 0 then Exit;
//salt generate salt guid
_sSalt := GetGUID;
_iPP := Random(divisor-1);
//merge
_sSrc := merge(_Passphrase, _sSalt, _iPP);
// _ByteSalt := TEncoding.Unicode.GetBytes(_sSalt);
_BytesSrc := TEncoding.Unicode.GetBytes(_sSrc);
if Length(_BytesSrc) <= 0 then Exit;
_iS := Random(divisor-1);
_iP := Random(divisor-1);
_sSalt := Format('%.2d',[divisor - _iS]) + StringReplace(TNetEncoding.Base64.Encode(_sSalt), #13#10, '', [rfReplaceAll]);
_sDec := Format('%.2d',[divisor - _iP]) + StringReplace(TNetEncoding.Base64.Encode(SM3Print(SM3Bytes(_BytesSrc))), #13#10, '', [rfReplaceAll]);
Result := Format('$gs$%.2d%.2d%.2d$%s$%s', [_iPP, _iS, _iP, _sSalt, _sDec])
except
on E:Exception do
begin
raise Exception.Create(E.Message);
end;
end;
end;
function CheckPassword(const Passphrase : String; ExpectedHashString:string):Boolean;
var
_BytesSrc, _ByteSalt : TBytes;
_Passphrase, _sSrc, _sExpect, _sDec1, _sDec2 : String;
_iLen, _iPP, _iS , _iP, _iTmp : Integer;
_sSalt, _sParam, _sTmp : string;
_sList : TStringList;
begin
{
Generate a password hash, letting sm3 decide the best parameters.
Password hash is returned as a string of the form:
$gs$ppsspp$salt$hash
pp - model
ss - salt check
ps - password check
salt - base64 encoded salt 1-16 bytes decoded
hash - base64 encoded 64-byte scrypt hash
For example.
Password: "correct horse battery staple"
Hash: "$gs$124454$G34Rvmk2DSkp9sFJyyM49O$z3XxEUNlHDhq2nCR1Yh4tqKCelFQ9gnFNgtmgoBJHW4zeAIDoAjV5zcOLYk5lLqoGEFQNQ6YoOvXHAlVjPJS9e"
pp 12
ss 44
ps 55
Salt (base64): G34Rvmk2DSkp9sFJyyM49O
Password (base64): z3XxEUNlHDhq2nCR1Yh4tqKCelFQ9gnFNgtmgoBJHW4zeAIDoAjV5zcOLYk5lLqoGEFQNQ6YoOvXHAlVjPJS9e
}
_sList := TStringList.Create;
try
try
Result := False;
_Passphrase := Trim(Passphrase);
_iLen := Length(_Passphrase);
if _iLen <= 0 then Exit;
_sExpect := Trim(ExpectedHashString);
if _sExpect = '' then Exit;
//分解对比函数
_sList.StrictDelimiter := True ; //排除空格的方式
_sList.Delimiter := '$';
_sList.DelimitedText := _sExpect;
if _sList.Count <> 5 then Exit;
if _sList.Strings[1] <> 'gs' then Exit;
_sParam := Trim(_sList.Strings[2]);
if Length(_sParam) <> 6 then Exit;
//解析Param参数部分
_iPP := StrToIntDef(_sParam[1] + _sParam[2], 0);
_iS := StrToIntDef(_sParam[3] + _sParam[4], 0);
_iP := StrToIntDef(_sParam[5] + _sParam[6], 0);
//校验并解析salt
_sTmp := trim(_sList.Strings[3]);
if Length(_sTmp) < 2 then Exit;
_iTmp := StrToIntDef(_sTmp[1] + _sTmp[2], 0);
if _iTmp + _iS <> divisor then Exit;
_sTmp := Copy(_sTmp, 3, Length(_sTmp) - 2);
_sSalt := TNetEncoding.Base64.Decode(_sTmp);
_sTmp := trim(_sList.Strings[4]);
if Length(_sTmp) < 2 then Exit;
_iTmp := StrToIntDef(_sTmp[1] + _sTmp[2], 0);
if _iTmp + _iP <> divisor then Exit;
_sTmp := Copy(_sTmp, 3, Length(_sTmp) - 2);
_sDec1 := TNetEncoding.Base64.Decode(_sTmp);
//merge
_sSrc := merge(_Passphrase, _sSalt, _iPP);
// _ByteSalt := TEncoding.Unicode.GetBytes(_sSalt);
_BytesSrc := TEncoding.Unicode.GetBytes(_sSrc);
if Length(_BytesSrc) <= 0 then Exit;
_sDec2 := SM3Print(SM3Bytes(_BytesSrc));
if _sDec1 = _sDec2 then Result := True;
except
on E:Exception do
begin
raise Exception.Create(E.Message);
end;
end;
finally
if Assigned(_sList) then FreeAndNil(_sList);
end;
end;
initialization
Randomize; //初始化随机数发生器
end.
Pascal