曾程勝
(江西省撫州市臨川區(qū)政務(wù)信息化工作辦公室,江西 撫州 344000)
為了保證應(yīng)用前端向后端傳輸數(shù)據(jù)的安全,我們通常采用SSL加密方式,本文是在通常HTTP上實(shí)現(xiàn)前臺(tái)Javascript向后臺(tái)Delphi開發(fā)的服務(wù)器端傳輸數(shù)據(jù)的加密[1],防止由于第三方攻擊導(dǎo)致重要信息泄露。由于RSA加密[2]對(duì)明文字符長(zhǎng)度有限制,所以設(shè)計(jì)思想是:客戶端首先向服務(wù)器端發(fā)出AJAX請(qǐng)求,獲取后臺(tái)隨機(jī)生的RSA加密算法的公鑰,然后客戶端隨機(jī)生成 AES對(duì)稱加密算法[3]的Key及 iv向量,當(dāng)客戶端提交表單數(shù)據(jù)時(shí),利用AES加密表單數(shù)據(jù),再用RSA公鑰加密AES的Key、iv向量,并以jQuery POST[4]方式向后臺(tái)提交這些加密數(shù)據(jù),最后在后臺(tái)使用RSA私鑰解密Key、iv,從而解密表單數(shù)據(jù)。
前端使用了JAVASCRIPT加密庫(kù)FORGE[5],下載地址 https://github.com/digitalbazaar/forge以及forge瀏覽器支持庫(kù) https://github.com/digitalbazaar/forge-dist,具體實(shí)現(xiàn)代碼如下:
//引入JS庫(kù)
var _Pubkey="";//保存獲取的公鑰
//以RSA方式加密
function Encrypt_RSA(Str, pubkey) {
var pki = forge.pki;
var publicKey = pki.publicKeyFromP-em(pubkey);
return publicKey.encrypt(Str);
}
//轉(zhuǎn)成十六進(jìn)制字符,便于傳輸
function BinToHex(obj) {return forge.util.bytesToHex(obj);
}
$("#btnSend").click(function(){
var sysTxt=$("#txtStr").val();//獲取需加密的數(shù)據(jù)
// 隨機(jī)生成key and IV
// key 16 bytes 將使用 AES-128, 24=> AES-192, 32 => AES-256
var key = forge.random.getBytesSync(32);
var iv = forge.random.getBytesSync(16);
var cipher = forge.cipher.createCipher('AES-CBC', key);
cipher.start({iv: iv});
cipher.update(forge.util.createBuffer(sysTxt));
cipher.finish();
var encrypted = cipher.output;
// outputs encrypted hex
var encryptedTxt=encrypted.toHex();
// outputs encrypted key iv encode64
var _cipher_key = forge.util.encode64(Encrypt_RSA(BinToHex(key), _Pubkey));
var _cipher_iv = forge.util.encode64(Encrypt_RSA(BinToHex(iv), _Pubkey));
//將加密數(shù)據(jù)發(fā)送至后端,并將后端返回的解密數(shù)據(jù)顯示出來(lái)進(jìn)行驗(yàn)證
$.post('PrdWebAPP.dll/SENDENCTXT',{"key":_ci
pher_key,"iv":_cipher_iv,"encrypted":encryptedTxt},f unction(data){alert(data.str);},'json');});
//向后端獲取公鑰
$(function(){
$.getJSON("PrdWebAPP.dll/GetPubKey")
.done(function (data) {
_Pubkey=data.pubkey;
})
})
下面我將分別探討使用 OPENSSL[6]動(dòng)態(tài)鏈接庫(kù)及商用組件SecureBlackbox[7]實(shí)現(xiàn)后端處理。我這里服務(wù)器端開發(fā)采用 IIS[8]+RealThinClient[9]+ISAPI[10]方式,僅供參考。
由于直接使用openssl動(dòng)態(tài)鏈接庫(kù)不是很方便,我借助開源組件 ICS[11]進(jìn)行開發(fā),該組件完整實(shí)現(xiàn)了對(duì) OPENSSL的封裝,下載地址為 http://wiki.overbyte.eu/arch/icsv8w.zip。該方案優(yōu)點(diǎn)是性能好,處理速度快,缺點(diǎn)是部署時(shí)須附帶 OPENSSL動(dòng)態(tài)鏈接庫(kù)。
uses
System.SysUtils, System.Classes, rtcConn,rtcDataSrv, rtcISAPISrv, rtcInfo,
rtcSys
tem,OverbyteIcsSSLEAY,OverbyteIcsMimeUtils,Over byteIcsWSocket,
Over
byteIcsSslX509Utils,OverbyteIcsLibeay,OverByteIcs MD5,OverbyteIcsTypes,
OverbyteIcsLogger,OverbyteIcsUtils;
type
TWebModule = class(TDataModule)
GetPubKey: TRtcDataProvider;
PrdWebServer: TRtcISAPIServer;
SendEncTxt: TRtcDataProvider;
procedure GetPubKeyCheckRequest(Sender:TRtcConnection);
procedure SendEncTxtCheckRequest(Sender: TRtcConnection);
procedure SendEncTxtDataReceived(Sender:TRtcConnection);
procedure DataModuleCreate(Sender: Tobject);
procedure DataModuleDestroy(Sender: Tobject);
private
{ Private declarations }
FProgDir:string;
FSslCertTools: TSslCertTools;
public
{ Public declarations }
end;
const
AES_BLOCK_SIZE = 16;
type
TAesType = (aesCbc, aesEcb);
TAesContext = packed record
Ctx : PEVP_CIPHER_CTX;
Aes : PEVP_CIPHER;
Encrypt : Boolean;//True為加密False為解密
end;
PAesContext = ^TAesContext;
function StrToHex(AStr: AnsiString): AnsiString;
var
len : Integer;
begin
len:=Length(AStr);
SetLength(Result, len*2);
BinToHex(@AStr[1], PAnsiChar(Result), len);
Result:=string.LowerCase(Result);
end;
function HexToStr(AHex: AnsiString): AnsiS-
tring;
var
len : Integer;
begin
len:=Length(AHex)div 2;
SetLength(Result, len);
HexToBin(PAnsiChar(AHex),Result[1] , len);
end;
procedure AesFinalize(var AesCtx: TAesContext);
begin
if Assigned(AesCtx.Ctx) then begin
f_EVP_CIPHER_CTX_cleanup(AesCtx.Ctx);
f_EVP_CIPHER_CTX_free(AesCtx.Ctx);
end;
FillChar(AesCtx, SizeOf(AesCtx), #0);
end;
procedure AesInitialize(
var AesCtx : TAesContext;
Key : PAnsiChar; // if not nil Pwd is ignored and the user is responsible to provide a valid Key and IV
IV : PAnsiChar;
AesType : TAesType;
Enc : Boolean);
begin
AesFinalize(AesCtx);
AesCtx.Encrypt := Enc;
case AesType of
aesCbc, aesEcb :
begin
if AesType = aesCbc then
AesCtx.Aes := f_EVP_aes_256_cbc//CBC加密方式
else begin
AesCtx.Aes := f_EVP_aes_256_ecb;//ECB加密
end;
end;
else
raise Exception.Create('Not implemented');
end;
AesCtx.Ctx := f_EVP_CIPHER_CTX_new;
if ICS_OPENSSL_VERSION_NUMBER f_EVP_CIPHER_CTX_init(AesCtx.Ctx) else f_EVP_CIPHER_CTX_reset(AesCtx.Ctx); if not f_EVP_CipherInit_ex(AesCtx.Ctx,AesCtx.Aes, nil, @key[0], @IV[0], Ord(Enc)) then raise Exception.Create('Function f_EVP_CipherInit_ex'); end; procedure AesUpdate( const InBuf; InLen : Integer; const OutBuf; var OutLen : Integer; AesCtx : TAesContext); begin if not Assigned(AesCtx.Ctx) then raise Exception.Create('Aes context not initialized'); if not f_EVP_CipherUpdate(AesCtx.Ctx,PAnsiChar(@OutBuf), OutLen, AnsiChar(@InBuf),InLen) then raise Exception.Create('f_EVP_Cipher-Update') end; procedure AesFinal( const OutBuf; var OutLen : Integer; AesCtx : TAesContext); begin if not Assigned(AesCtx.Ctx) then raise Exception.Create('Aes context not initialized'); if not f_EVP_CipherFinal_ex(AesCtx.Ctx, PAnsiChar(@OutBuf), OutLen) then raise Exception.Create('Function f_ EVP_CipherFinal_ex:'); end; { Takes plain text, returns an encrypted string,optionally base64 encoded } function StrEncAES( const S : AnsiString; Key : PAnsiChar; IV : PAnsiChar; B64 : Boolean): AnsiString; var Len, TmpLen : Integer; AesCtx : TAesContext; begin FillChar(AesCtx, SizeOf(AesCtx), #0); AesInitialize(AesCtx, Key, IV, aesCbc, True); try Len := Length(S); SetLength(Result, Len + AES_BLOCK_SIZE); AesUpdate(S[1], Length(S), Result[1],Len, AesCtx); AesFinal(Result[Len + 1], TmpLen,AesCtx); Inc(Len, TmpLen); SetLength(Result, Len); finally AesFinalize(AesCtx); end; if B64 then Result := Base64Encode(Result) else Result := StrToHex(Result) end; function StrDecAES( S : AnsiString; Key : PAnsiChar; IV : PAnsiChar; B64 : Boolean): AnsiString; var Len, TmpLen : Integer; AesCtx : TAesContext; begin FillChar(AesCtx, SizeOf(AesCtx), #0); AesInitialize(AesCtx, Key, IV, aesCbc,False); try if B64 then S := Base64Decode(S); Len := Length(S); SetLength(Result, Len + AES_BLOCK_SIZE); AesUpdate(S[1], Length(S), Result[1],Len, AesCtx); AesFinal(Result[Len + 1], TmpLen, AesCtx); Inc(Len, TmpLen); SetLength(Result, Len);finally AesFinalize(AesCtx); end;end; procedure TWebModule.DataModuleCreate(Sender: TObject); begin //掛載OPENSSL的動(dòng)態(tài)鏈接庫(kù),這里須將動(dòng)態(tài)鏈接庫(kù)拷貝至運(yùn)行目錄下 FProgDir := ExtractFilePath(ParamStr(0)); GSSLEAY_DLL_IgnoreNew := False; { V8.38 don't ignore OpenSSL 1.1.0 and later } //GSSLEAY_DLL_IgnoreNew := True; { V8.38 don't ignore OpenSSL 1.1.0 and later } //GSSLEAY_DLL_IgnoreOld := True; { V8.38 ignore OpenSSL 1.0.2 and earlier } GSSL_DLL_DIR := FProgDir; { V8.38 only from our directory } //GSSL_SignTest_Check := True; { V8.38 check digitally signed } //GSSL_SignTest_Certificate := True; { V8.38 check digital certificate } OverbyteIcsWSocket.LoadSsl; FSslCertTools := TSslCertTools.Create(self); //TSslPrivKeyType(0),0--1024位,1--2048 2--3072 3--4096 4--7680 5--15360 FSslCertTools.PrivKeyType :=TSslPrivKeyType(1);//2048位足夠安全,過(guò)高將嚴(yán)重影響性能 FSslCertTools.DoKeyPair;//隨機(jī)產(chǎn)生 RSA公鑰及私鑰 end; procedure TWebModule.DataModuleDestroy(Sender: TObject); begin FreeAndNil(FSslCertTools); end; //允許請(qǐng)求獲取公鑰 procedure TWebModule.GetPubKeyCheckRequest(Sender: TRtcConnection); begin with Sender as TRtcDataServer do if UpperCase(Request.FileName)='/GETPUBKEY' then Accept; end; //將公鑰發(fā)至前端 procedure TWebModule.GetPubKeyDataReceived(Sender: TRtcConnection); var ABio: PBIO; Len: Integer; PubKey:string; t:TRtcRecord; begin with Sender as TRtcDataServer do if Request.Complete then begin ABio := f_BIO_new(f_BIO_s_mem);//建立內(nèi)存流 f_PEM_write_bio_PUBKEY(ABio, FSslCertTools.PrivateKey);//把從私鑰中導(dǎo)出公鑰并寫入內(nèi)存流 Len := f_BIO_ctrl(ABio, BIO_CTRL_PENDING, 0, nil); PubKey := String(FSslCertTools.ReadStr-Bio(ABio, Len));{ V8.41 } f_bio_free(ABio);//釋放內(nèi)存流 Re sponse.ContentType:='application/json'; t:=TRtcRecord.Create; t.asText['pubkey']:=PubKey; Write(t.toJSON); t.Free; end; end; procedure TWebModule.SendEncTxtCheckRequest(Sender: TRtcConnection); begin with Sender as TRtcDataServer do if UpperCase(Request.FileName)='/SENDENCTXT' then Accept; end; procedure TWebModule.SendEncTxtDataReceived(Sender: TRtcConnection); var CipherText:AnsiString; Key:AnsiString; IV:AnsiString; t:TRtcRecord; begin with Sender as TRtcDataServer do if Request.Complete then begin Request.Params.AddText(Read); Key:=AnsiString(StrDecRsa(FSslCertTools.Priva teKey, AnsiString(TNetEncoding.URL.Decode(Request.Params['key'])),True)); IV:=AnsiString(StrDecRsa(FSslCertTools.PrivateKey, AnsiString(TNetEncoding.URL.Decode(Request.Params['iv'])),True)); CipherText:=AnsiString(Request.Params['encrypted']); Response.ContentType:='application/json'; t:=TRtcRecord.Create; //將解密的字符發(fā)送至前端進(jìn)行驗(yàn)證 t.asText['str']:=StrDecAES(HexToStr(CipherText),PAnsiChar(HexToStr(Key)),PAnsiChar(HexToStr(IV)),False); Write(t.toJSON); t.Free; end; end; 此方案優(yōu)點(diǎn)是部署無(wú)須附帶額外文件,缺點(diǎn)就是處理速度稍慢,并且此組件正版費(fèi)用較高。 uses System.SysUtils, System.Classes, rtcConn,rtcDataSrv, rtcISAPISrv, rtcInfo, rtcSystem,SBTypes, SBUtils, SBSymmetric-Crypto, SBConstants, SBHashFunction, SBEncoding, SBRandom, SBPublicKeyCrypto,SBX509,System.NetEncoding; type TWebModule = class(TDataModule) GetPubKey: TRtcDataProvider; PrdWebServer: TRtcISAPIServer; SendEncTxt: TRtcDataProvider; procedure GetPubKeyCheckRequest(Sender:TRtcConnection); procedure GetPubKeyDataReceived(Sender:TRtcConnection); procedure SendEncTxtCheckRequest(Sender: TRtcConnection); procedure SendEncTxtDataReceived(Sender:TRtcConnection); procedure DataModuleCreate(Sender: TObject); procedure DataModuleDestroy(Sender: Tobject); private { Private declarations } FFactory : TElSymmetricCryptoFactory; RSAKeyMaterial: TElRSAKeyMaterial; RSACrypto : TElRSAPublicKeyCrypto; function CreateKeyMaterial(AESKey:AnsiString;AESIV:AnsiString): TelSymmetricKeyMaterial; public { Public declarations }end; function TWebModule.CreateKeyMaterial(AESKey:AnsiString;AESIV:AnsiString): TelSymmetricKeyMaterial; var IV, Key : ByteArray; Size : integer; begin if (Length(AESKey) <> 256 div 4) or(Length(AESIV) <> 128 div 4) then raise Exception.Create('Empty or invalid Key/IV value!'); SetLength(IV, 16); Size := 16; SBUtils.StringToBinary(AESIV, @IV[0], Size); SetLength(IV, Size); SetLength(Key, 32); Size := 32; SBUtils.StringToBinary(AESKey, @Key[0], Size); SetLength(Key, Size); Result := TElSymmetricKeyMaterial.Create; Result.Key := Key; Result.IV := IV; end; procedure TWebModule.DataModuleCreate(Sender: TObject); begin FFactory := TElSymmetricCryptoFactory.Create; RSAKeyMaterial := TElRSAKeyMaterial.Create; RSAKeyMaterial.PEMEncode:=True;//編碼公鑰私鑰 RSAKeyMaterial.Generate(2048);//產(chǎn)生 2048位密鑰 RSACrypto := TElRSAPublicKeyCrypto.Create(); RSACrypto.KeyMaterial := RSAKeyMaterial; end; procedure TWebModule.DataModuleDestroy(Sender: TObject); begin FreeAndNil(FFactory); FreeAndNil(RSAKeyMaterial); FreeAndNil(RSACrypto); end; procedure TWebModule.GetPubKeyCheckRequest(Sender: TRtcConnection); begin with Sender as TRtcDataServer do if UpperCase(Request.FileName)='/GETPUBKEY' then Accept; end; procedure TWebModule.GetPubKeyDataReceived(Sender: TRtcConnection); var KeyStream:TStringStream; t:TRtcRecord; begin with Sender as TRtcDataServer do if Request.Complete then begin KeyStream:=TStringStream.Create; RSAKeyMate rial.SavePublic(KeyStream);//導(dǎo)出公鑰 //將生成的公鑰發(fā)送到客戶端用于加密AES KEY IV Response.ContentType:='application/json'; t:=TRtcRecord.Create; t.asText['pubkey']:=KeyStream.DataString; Write(t.toJSON); t.Free; FreeAndNil(KeyStream); end; end; procedure TWebModule.SendEncTxtCheckRequest(Sender: TRtcConnection); begin with Sender as TRtcDataServer do if UpperCase(Request.FileName)='/SENDENCTXT' then Accept; end; procedure TWebModule.SendEncTxtDataReceived(Sender: TRtcConnection); var Crypto : TElSymmetricCrypto; KeyMaterial : TElSymmetricKeyMaterial; InBuf, OutBuf : ByteArray; //BufSt : AnsiString; CipherSize:Integer; OutSize : integer; Key:AnsiString; IV:AnsiString; CipherText:AnsiString; DecryptedStr:AnsiString; InputStream:TStringStream; OutputStream:TStringStream; t:TRtcRecord; begin with Sender as TRtcDataServer do if Request.Complete then begin Request.Params.AddText(Read); //用RSA私鑰解密出AES的KEY IV RSACrypto.InputEncoding := pkeBase64;//輸入流編碼方式 RSACrypto.OutputEncoding := pkeBinary;//輸出流編碼方式 InputStream:=TStringStream.Create(AnsiString(TNet- Encoding.URL.Decode(Request.Params['key']))); OutputStream:= TStringStream.Create; RSACrypto.Decrypt(InputStream, OutputStream); Key:=OutputStream.DataString;InputStream:=TStringStream.Create(AnsiString(Tnet-Encoding.URL.Decode(Request.Params['iv']))); OutputStream:= TStringStream.Create; RSACrypto.Decrypt(InputStream, OutputStream); IV:=OutputStream.DataString; FreeAndNil(InputStream); FreeAndNil(OutputStream); //用KEY IV解密字符串 Cipher- Text:=AnsiString(Request.Params['encrypted']); Crypto := FFactory.CreateInstance(SB_ALGORITHM_CNT_AES256, cmCBC); CipherSize:=Length(CipherText) div 2; SetLength(InBuf,CipherSize); SBU- tils.StringToBinary(CipherText,@InBuf[0],CipherSize); KeyMaterial := CreateKeyMaterial(Key,IV); Crypto.KeyMaterial := KeyMaterial; OutSize := 0; Crypto.Decrypt(@InBuf[0],Length(InBuf), nil, OutSize); SetLength(OutBuf, OutSize); Crypto.Decrypt(@InBuf[0],Length(InBuf), @OutBuf[0], OutSize); SetLength(OutBuf, OutSize); DecryptedStr := SBUtils.AnsiStringOf-Bytes(OutBuf); FreeAndNil(KeyMaterial); FreeAndNil(Crypto); //將AES加密的字符解密后發(fā)送至客戶端進(jìn)行驗(yàn)證 Response.ContentType:='application/json';t:=TRtcRecord.Create; t.asText['str']:=DecryptedStr; Write(t.toJSON); t.Free; end; end; 解密的結(jié)果正確,驗(yàn)證通過(guò)。 [1] 王珊珊. 計(jì)算機(jī)網(wǎng)絡(luò)安全防范措施探討[J]. 軟件, 2013,(8). [2] 馬玉琢, 郭玉翠. 基于3DES和RSA的Android系統(tǒng)短信加密設(shè)計(jì)與實(shí)現(xiàn)[J], 軟件, 2016, (6). [3] Daryl Miller, 辛磊夫. 網(wǎng)絡(luò)安全比加密更重要[J]. 軟件,2007, (1). [4] Bear Bibeault, Yehuda Katz. jQuery實(shí)戰(zhàn)[D]. 人民郵電出版社. 2010. [5] GitHub-digitalbazaar/forge. https://github.com/digitalbazaar/forge. [6] openssl Cryptography and SSL/TLS Toolkit. https://www.openssl.org/. [7] SecureBlackbox. https://www.secureblackbox. com/. [8] Internet Information Services Development. https://msdn.microsoft.com/en-us/library/ms692515(v=vs. 90).Aspx. [9] RTC HTTP Server in 199 lines of code. http://www. realthinclient.com/write-a-robust-cross-platform-server-in-199- lines/. [10] ISAPI Extension Overview. https://msdn.microsoft.com/en-us/library/ms525172(v=vs. 90). Aspx. [11] Internet Component Suite. http://wiki.overbyte.eu/wiki/index.php/Main_Page.3.2 使用SecureBlackbox組件實(shí)現(xiàn)
4 現(xiàn)在編譯部署后運(yùn)行進(jìn)行驗(yàn)證: