DELPHI盒子
!实时搜索: 盒子论坛 | 注册用户 | 修改信息 | 退出
检举帖 | 全文检索 | 关闭广告 | 捐赠
技术论坛
 用户名
 密  码
自动登陆(30天有效)
忘了密码
≡技术区≡
DELPHI技术
lazarus/fpc/Free Pascal
移动应用开发
Web应用开发
数据库专区
报表专区
网络通讯
开源项目
论坛精华贴
≡发布区≡
发布代码
发布控件
文档资料
经典工具
≡事务区≡
网站意见
盒子之家
招聘应聘
信息交换
论坛信息
最新加入: a12315
今日帖子: 49
在线用户: 8
导航: 论坛 -> DELPHI技术 斑竹:liumazi,sephil  
作者:
男 janker (janker) ★☆☆☆☆ -
盒子活跃会员
2021/6/12 17:31:30
标题:
AEAD_AES_256_GCM 加解密函数 浏览:3558
加入我的收藏
楼主: 利用 OpenSSL API 实现的AEAD_AES_256_GCM 加解密函数
测试的OpenSSL版本是 1.1.1g,可以从git上下载,一个NB的公司提供的:
https://github.com/grijjy/DelphiOpenSsl
还提供了老版本的:
https://github.com/grijjy/GrijjyFoundation (这个基础库里有许多好东西)
DelphiOpenSsl这个例子提供了x509证书的相关操作,签名和验签等,
但是没有提供AEAD_AES_256_GCM 的例子,翻了好多网上的例子,主要是C++的例子,被误导了,还是OpenSSL的官方例子靠谱
EVP Authenticated Encryption and Decryption - OpenSSLWiki
参考这个文章,总算可以了:

function Encrypt_AEAD_AES_256_GCM(const AData, AKey, AIV, AAD: TBytes; out AEnCryptData, ATag: TBytes): Boolean;
var
  Ctx: PEVP_CIPHER_CTX;
  Len: Integer;
  DataLen: Integer;
begin
  Result := False;
  Len := 0;
  DataLen := 0;
  SetLength(AEnCryptData, 8192);

  Ctx := EVP_CIPHER_CTX_new();
  try
    EVP_EncryptInit_ex(Ctx, EVP_aes_256_gcm(), nil, nil, nil);
    // Set IV length if default 96 bits is not appropriate (12byte)
    EVP_CIPHER_CTX_ctrl(Ctx, EVP_CTRL_GCM_SET_IVLEN, Length(AIV), nil);
    EVP_EncryptInit_ex(Ctx, nil, nil, @AKey[0], @AIV[0]);

    if EVP_EncryptUpdate(Ctx, nil, @Len, @AAD[0], Length(AAD)) <> 1 then
      Exit(False);
    if EVP_EncryptUpdate(Ctx, @AEnCryptData[0], @Len, @AData[0], Length(AData)) <> 1 then
      Exit(False);

    DataLen := Len;

    if EVP_EncryptFinal_ex(Ctx, @AEnCryptData[DataLen], @Len) <> 1 then
      Exit(False);

    DataLen := DataLen + Len;
    SetLength(AEnCryptData, DataLen);

    //get tag
    SetLength(ATag, 16);
    if EVP_CIPHER_CTX_ctrl(Ctx, EVP_CTRL_GCM_GET_TAG, 16, @ATag[0]) <> 1 then
      Exit(False);

    Result := True;
  finally
    EVP_CIPHER_CTX_free(Ctx);
  end;
end;

class function TgoOpenSSLHelper.Decrypt_AEAD_AES_256_GCM(const AData, AKey, AIV, AAD, ATag: TBytes; out ADeCryptData: TBytes): Boolean;
var
  Ctx: PEVP_CIPHER_CTX;
  Len: Integer;
  DataLen: Integer;
  TAG: TBytes;
begin
  Result := False;
  Len := 0;
  DataLen := 0;
  SetLength(ADeCryptData, 8192);

  Ctx := EVP_CIPHER_CTX_new();
  try
    EVP_DecryptInit_ex(Ctx, EVP_aes_256_gcm, nil, nil, nil);
    // Set IV length if default 96 bits is not appropriate (12byte)
    EVP_CIPHER_CTX_ctrl(Ctx, EVP_CTRL_GCM_SET_IVLEN, Length(AIV), nil);
    EVP_DecryptInit_ex(Ctx, nil, nil, @AKey[0], @AIV[0]);

    if EVP_DecryptUpdate(Ctx, nil, @Len, @AAD[0], Length(AAD)) <> 1 then
      Exit(False);
    if EVP_DecryptUpdate(Ctx, @ADeCryptData[0], @Len, @AData[0], Length(AData)) <> 1 then
      Exit(False);

    DataLen := Len;
    //set tag
    if EVP_CIPHER_CTX_ctrl(Ctx, EVP_CTRL_GCM_SET_TAG, 16, @ATag[0]) <> 1 then
      Exit(False);

    if EVP_DecryptFinal_ex(Ctx, @ADeCryptData[DataLen], @Len) <= 0 then
      Exit(False);

    DataLen := DataLen + Len;
    SetLength(ADeCryptData, DataLen);
    Result := True;
  finally
    EVP_CIPHER_CTX_free(Ctx);
  end;

end;
----------------------------------------------
-
作者:
男 glwang (glwang) ★☆☆☆☆ -
盒子活跃会员
2021/6/13 9:49:27
1楼: grijjy牛X的公司!牛X的Delphi大佬!!

https://blog.grijjy.com/

写的每一篇博客都是干货!
----------------------------------------------
作者:
男 www12345 (风云) ★☆☆☆☆ -
盒子活跃会员
2021/6/13 20:46:12
2楼: https://blog.grijjy.com/2020/02/11/25-years-of-delphi-and-me/
----------------------------------------------
一卡通专家的中专家www.cnduh.com
作者:
男 keymark (嬲) ▲▲▲△△ -
普通会员
2021/6/16 19:16:03
3楼: 发现一个纯pas的.可惜项目挂了。。压缩包也坏了。
..额有点老
crypto2012
此帖子包含附件:
PNG 图像
大小:3,164B
----------------------------------------------
[alias]  co = clone --recurse-submodules  up = submodule update --init --recursiveupd = pullinfo = statusrest = reset --hard懒鬼提速https://www.cctry.com/>http://qalculate.github.io/downloads.htmlhttps://www.cctry.com/
作者:
男 keymark (嬲) ▲▲▲△△ -
普通会员
2021/6/17 11:21:22
4楼: Authenticated Encryption with Associated Data

lz翻译的代码时间点
Evp-gcm-encrypt.c  2019-04-30 15:04  7.4K  

不过我不认同
  SetLength(AEnCryptData, 8192);
这么写

C
    if(1 != EVP_EncryptInit_ex(ctx, EVP_aes_256_gcm(), NULL, NULL, NULL))
        handleErrors();
出了问题可能不会知道是那行 啥错误。。。

建议改改
以上是个人观点  对API调用报错使用得出结论
----------------------------------------------
[alias]  co = clone --recurse-submodules  up = submodule update --init --recursiveupd = pullinfo = statusrest = reset --hard懒鬼提速https://www.cctry.com/>http://qalculate.github.io/downloads.htmlhttps://www.cctry.com/
作者:
男 janker (janker) ★☆☆☆☆ -
盒子活跃会员
2021/6/17 16:35:14
5楼: @keymark (嬲)
是的,代码很不严谨,还是测试用的,直接加在了别人的DEMO的TgoOpenSSLHelper类了,还没整理。。。
RSA还没搞明白,又有ECC,ECDSA,ECDH,要晕的感觉...
----------------------------------------------
-
作者:
男 keymark (嬲) ▲▲▲△△ -
普通会员
2021/6/17 17:11:37
6楼: 我记得有个中文的d7的用openssl的demo在github找找。
----------------------------------------------
[alias]  co = clone --recurse-submodules  up = submodule update --init --recursiveupd = pullinfo = statusrest = reset --hard懒鬼提速https://www.cctry.com/>http://qalculate.github.io/downloads.htmlhttps://www.cctry.com/
作者:
男 janker (janker) ★☆☆☆☆ -
盒子活跃会员
2021/6/17 17:55:20
7楼: github上的D7的openssl下了,不了解Openssl,这个写法看不懂,而且代码是多年前写的,没有提供AEAD_AES_256_GCM的例子,Sign名也不用那么麻烦,直接用TgoOpenSSLHelper里的方法就好,更简单,
这个D7的DEMO,也是一个DEMO啊,一个OpenSSLApi的判断都没有....

比如: 
function TRSAOpenSSL.SHA256_Sign_PK(msg : String):string;
var
    ctx : EVP_MD_CTX   ;
    pKey :    PEVP_PKEY;   //Q4A38GwtqQ
   s1:string;
  midbuf_Len,outbuf_Len: cardinal;
  mdctx: EVP_MD_CTX;
  inbuf, midbuf,outbuf: array [0..10230] of char;
  key: pEVP_PKEY;
  i,count:Integer;
begin
  pKey := LoadPrivateKey(fPrivateKeyPath);
  StrPCopy(inbuf, msg);
EVP_MD_CTX_init(@ctx);
EVP_SignInit(@ctx,EVP_sha256());
EVP_SignUpdate(@ctx,@inbuf,StrLen(inbuf));
EVP_DigestFinal(@ctx,@midbuf, midbuf_Len);
rSA_sign(EVP_sha256()._type,@midbuf,midbuf_Len,outbuf,@outbuf_Len,pkey.pkey.rsa);
EVP_MD_CTX_cleanup(@ctx);

count := outbuf_Len;
for i:=0 to count-1 do
begin
    s1 := s1 + outbuf[i];
end;

Result:= deltail(EncodeString(s1)) ;
end;
只能参考了.....
----------------------------------------------
-
作者:
男 keymark (嬲) ▲▲▲△△ -
普通会员
2021/6/17 20:09:39
8楼: 按此在新窗口浏览图片 不接返回值不怕背锅吗,
只要胆子大崩溃都不怕。。。。。。。。。。
----------------------------------------------
[alias]  co = clone --recurse-submodules  up = submodule update --init --recursiveupd = pullinfo = statusrest = reset --hard懒鬼提速https://www.cctry.com/>http://qalculate.github.io/downloads.htmlhttps://www.cctry.com/
作者:
男 keymark (嬲) ▲▲▲△△ -
普通会员
2021/6/17 20:20:18
9楼: 看了下msys2的openssl
openssl enc -ciphers
Supported ciphers:
-aes-128-cbc          -aes-128-cfb          -aes-128-cfb1
-aes-128-cfb8          -aes-128-ctr          -aes-128-ecb
-aes-128-ofb          -aes-192-cbc          -aes-192-cfb
-aes-192-cfb1          -aes-192-cfb8          -aes-192-ctr
-aes-192-ecb          -aes-192-ofb          -aes-256-cbc
-aes-256-cfb          -aes-256-cfb1          -aes-256-cfb8
-aes-256-ctr          -aes-256-ecb          -aes-256-ofb
-aes128          -aes128-wrap          -aes192
-aes192-wrap          -aes256          -aes256-wrap
-aria-128-cbc          -aria-128-cfb          -aria-128-cfb1
-aria-128-cfb8          -aria-128-ctr          -aria-128-ecb
-aria-128-ofb          -aria-192-cbc          -aria-192-cfb
-aria-192-cfb1          -aria-192-cfb8          -aria-192-ctr
-aria-192-ecb          -aria-192-ofb          -aria-256-cbc
-aria-256-cfb          -aria-256-cfb1          -aria-256-cfb8
-aria-256-ctr          -aria-256-ecb          -aria-256-ofb
-aria128          -aria192          -aria256
-bf          -bf-cbc          -bf-cfb
-bf-ecb          -bf-ofb          -blowfish
-camellia-128-cbc          -camellia-128-cfb          -camellia-128-cfb1
-camellia-128-cfb8         -camellia-128-ctr          -camellia-128-ecb
-camellia-128-ofb          -camellia-192-cbc          -camellia-192-cfb
-camellia-192-cfb1         -camellia-192-cfb8         -camellia-192-ctr
-camellia-192-ecb          -camellia-192-ofb          -camellia-256-cbc
-camellia-256-cfb          -camellia-256-cfb1         -camellia-256-cfb8
-camellia-256-ctr          -camellia-256-ecb          -camellia-256-ofb
-camellia128          -camellia192          -camellia256
-cast          -cast-cbc          -cast5-cbc
-cast5-cfb          -cast5-ecb          -cast5-ofb
-chacha20          -des          -des-cbc
-des-cfb          -des-cfb1          -des-cfb8
-des-ecb          -des-ede          -des-ede-cbc
-des-ede-cfb          -des-ede-ecb          -des-ede-ofb
-des-ede3          -des-ede3-cbc          -des-ede3-cfb
-des-ede3-cfb1          -des-ede3-cfb8          -des-ede3-ecb
-des-ede3-ofb          -des-ofb          -des3
-des3-wrap          -desx          -desx-cbc
-id-aes128-wrap          -id-aes128-wrap-pad        -id-aes192-wrap
-id-aes192-wrap-pad        -id-aes256-wrap          -id-aes256-wrap-pad
-id-smime-alg-CMS3DESwrap  -idea          -idea-cbc
-idea-cfb          -idea-ecb          -idea-ofb
-rc2          -rc2-128          -rc2-40
-rc2-40-cbc          -rc2-64          -rc2-64-cbc
-rc2-cbc          -rc2-cfb          -rc2-ecb
-rc2-ofb          -rc4          -rc4-40
-rc5-cbc          -rc5-cfb          -rc5-ecb
-rc5-ofb          -seed          -seed-cbc
-seed-cfb          -seed-ecb          -seed-ofb
-sm4          -sm4-cbc          -sm4-cfb
-sm4-ctr          -sm4-ecb          -sm4-ofb
居然没发现gcm
msys/openssl 1.1.1.k-1 [installed]
这是淘汰了??
----------------------------------------------
[alias]  co = clone --recurse-submodules  up = submodule update --init --recursiveupd = pullinfo = statusrest = reset --hard懒鬼提速https://www.cctry.com/>http://qalculate.github.io/downloads.htmlhttps://www.cctry.com/
作者:
男 janker (janker) ★☆☆☆☆ -
盒子活跃会员
2021/6/18 0:43:46
10楼: 又补了几个DEMO没有的函数

生成RSAKeyPair, pkcs1和pkcs8格式的,pkcs12(浏览器用的还没整),
奇怪了,pkcs1有BIO方法,pkcs8只有EVP方法

class function TgoOpenSSLHelper.CreateRSAPairKey(const APKCSFormat: Integer; out APrivateKey, APublicKey: TBytes): Boolean;
var
  Bignum: PBIGNUM;
  BNGEN: PBN_GENCB;
  BIOPrivateKey: PBIO;
  BIOPublicKey: PBIO;
  RSA: PRSA;
  EVPKey: PEVP_PKEY;
  PrivateLen, PendingLen1: Integer;
  PublicLen, PendingLen2: Integer;
begin
  Result := False;
  PrivateLen := 0;
  PublicLen := 0;
  PendingLen1 := 0;
  PendingLen2 := 0;

  EVPKey := EVP_PKEY_new;
  if EVPKey = nil then
    Exit;
  BIOPrivateKey := BIO_new(BIO_s_mem);
  BIOPublicKey := BIO_new(BIO_s_mem);

  Bignum := BN_new;
  if Bignum = nil then
    Exit;
  BNGEN := BN_GENCB_new;
  if BNGEN = nil then
    Exit;
  try
    if BN_set_word(Bignum, RSA_F4) <> 1 then
      Exit;
    RSA := RSA_new;
    if RSA = nil then
      Exit;
    try
      if RSA_generate_key_ex(RSA, RSA_KEY_BITS, Bignum, nil) <> 1 then
        Exit;

      if APKCSFormat <> 8 then
      //PKCS1
      begin
        if PEM_write_bio_RSAPrivateKey(BIOPrivateKey, RSA, nil, nil, 0, nil, nil) <> 1 then
          Exit;
        if PEM_write_bio_RSAPublicKey(BIOPublicKey, RSA) <> 1 then
          Exit;
      end
      else
      //PKCS8
      begin
        if EVP_PKEY_assign(EVPKey, EVP_PKEY_RSA, RSA) <> 1 then
          Exit(False);

        if PEM_write_bio_PKCS8PrivateKey(BIOPrivateKey, EVPKey, nil, nil, 0, nil, nil) <> 1 then
          Exit;
        if PEM_write_bio_PUBKEY(BIOPublicKey, EVPKey) <> 1 then
          Exit;
      end;

      //PendingLen1 := BIO_pending(BIOPrivateKey);  //这个函数是 BIO_ctrl_pending() 的宏
      //PendingLen2 := BIO_pending(BIOPublicKey);
      PendingLen1 := BIO_ctrl_pending(BIOPrivateKey);
      PendingLen2 := BIO_ctrl_pending(BIOPublicKey);

      if (PendingLen1 = 0) or (PendingLen2 = 0) then
        Exit;

      SetLength(APrivateKey, PendingLen1);
      SetLength(APublicKey, PendingLen2);
//      if BIO_read_ex(BIOPrivateKey, @LPrivateKey[0], PendingLen1, @PrivateLen) <> 1 then
//        Exit;
//      if BIO_read_ex(BIOPublicKey, @LPublicKey[0], PendingLen2, @PublicLen) <> 1 then
//        Exit;
      PrivateLen := BIO_read(BIOPrivateKey, @APrivateKey[0], PendingLen1);
      PublicLen := BIO_read(BIOPublicKey, @APublicKey[0], PendingLen2);
      if (PrivateLen = 0) or (PublicLen = 0) then
        Exit;

      SetLength(APrivateKey, PrivateLen);
      SetLength(APublicKey, PublicLen);

      Result := True;
    finally
      if RSA <> nil then
        RSA_free(RSA);
    end;
  finally
    BN_free(Bignum);
    BIO_free(BIOPublicKey);
    BIO_free(BIOPrivateKey);
//  EVPKey 和 RSA 只能 free 一个,释放了一个,同时也释放了另一个,加个 := nil,只是为了好看,
//  (实际是 <> nil, 不过是局部变量,没影响)
//    EVPKey := nil;
  end;
end;

//公钥加密

class function TgoOpenSSLHelper.EncrytByPublicKey_RSA_OAEP(const AData, APublicKey: TBytes;
  out AEnCryptData: TBytes): Boolean;
var
  BIOPublicKey: PBIO;
  RSA: PRSA;
  DataLen: Integer;
begin
  Result := False;
  //加密数据长度不能大于214,大于214的要分块处理,不懂
  if Length(AData) > 214 then
    Exit;

  DataLen := 0;
  //数据最大 256 byte,即 RSA_size(RSA)
  SetLength(AEnCryptData, 256);

//  BIOPublicKey := BIO_new(BIO_s_mem);
//  BIO_write(BIOPublicKey, @APublicKey[0], Length(APublicKey));
  BIOPublicKey := BIO_new_mem_buf(@APublicKey[0], Length(APublicKey));

  try
    //支持pkcs1和pkcs8
    if CompareMem(@PUBLICKEY_FLAG_PKCS1_START[1], @APublicKey[0], Length(PUBLICKEY_FLAG_PKCS1_START)) then
      RSA := PEM_read_bio_RSAPublicKey(BIOPublicKey, nil, nil, nil)
    else
      RSA := PEM_read_bio_RSA_PUBKEY(BIOPublicKey, nil, nil, nil);

    if RSA = nil then
      Exit;

    try
      DataLen := RSA_public_encrypt(Length(AData), @AData[0], @AEnCryptData[0], RSA, RSA_PKCS1_OAEP_PADDING);
      if (DataLen > 0) and (DataLen <= 256) then
      begin
        SetLength(AEnCryptData, DataLen);
        Result := True;
      end;
    finally
      EVP_PKEY_free(RSA);
    end;

  finally
    BIO_free(BIOPublicKey);
  end;
end;

//私钥解密

class function TgoOpenSSLHelper.DecrytByPrivateKey_RSA_OAEP(const AData, APrivateKey: TBytes;
  out ADeCryptData: TBytes): Boolean;
var
  BIOPrivateKey: PBIO;
  RSA: PRSA;
  DataLen: Integer;
begin
  Result := False;
  if Length(AData) < 10 then
    Exit;
  DataLen := 0;
  SetLength(ADeCryptData, 256);

  BIOPrivateKey := BIO_new_mem_buf(@APrivateKey[0], Length(APrivateKey));
  try
    //这个函数可以支持pkcs1和pkcs8
    RSA := PEM_read_bio_RSAPrivateKey(BIOPrivateKey, nil, nil, nil);
    try
      DataLen := RSA_private_decrypt(Length(AData), @AData[0], @ADeCryptData[0], RSA, RSA_PKCS1_OAEP_PADDING);
      if (DataLen > 0) and (DataLen <= 214) then
      begin
        SetLength(ADeCryptData, DataLen);
        Result := True;
      end;
    finally
      EVP_PKEY_free(RSA);
    end;
  finally
    BIO_free(BIOPrivateKey);
  end;
end;
----------------------------------------------
-
作者:
男 janker (janker) ★☆☆☆☆ -
盒子活跃会员
2021/6/18 0:59:58
11楼: 常量
 PUBLICKEY_FLAG_PKCS1_START = '-----BEGIN RSA PUBLIC KEY-----' //PKCS1
 PUBLICKEY_FLAG_PKCS8_START = '-----BEGIN PUBLIC KEY-----' //PKCS8
----------------------------------------------
-
作者:
男 k999 (k999) ★☆☆☆☆ -
盒子活跃会员
2021/6/23 11:16:02
12楼: 顶一下,老姜
----------------------------------------------
-
作者:
男 janker (janker) ★☆☆☆☆ -
盒子活跃会员
2021/6/25 15:05:45
13楼: 补充说明下:
Encrypt_AEAD_AES_256_GCM()和Decrypt_AEAD_AES_256_GCM()的2个Tag参数,在实际使用中一般是附在密文的后面,而不是单独提供,长度为16字节。
(比如微信的PHP的DEMO中,就是手动拼接处理,java,python和.net不用,提供了处理函数)
----------------------------------------------
-
作者:
女 wfymqj (wfymqj) ★☆☆☆☆ -
盒子活跃会员
2021/7/16 15:37:48
14楼: @janker (janker)
你好,我最近做微信扫码支付看到了你的帖子。关于参数对应的问题想咨询下。

微信支付的返回串中,有如下几个参数
ciphertext       //Base64编码后的密文
nonce          //加密使用的随机串初始化向量
associated_data  //附加数据包


请问函数Decrypt_AEAD_AES_256_GCM(const AData, AKey, AIV, AAD, ATag: TBytes; out ADeCryptData: TBytes)中,

我的理解中,
AData为ciphertext进行Base64解码并减去最后16字节的数据;
AKey为APIv3Key;
ATag为ciphertext进行Base64解码并的最后16字节;

那么AIV、AAD同nonce、associated_data是怎么对应的啊?
AIV对应nonce对吗?
----------------------------------------------
-
作者:
男 admiu (古柯树) ★☆☆☆☆ -
普通会员
2021/9/9 10:37:51
15楼: 14楼,你搞定了么?
----------------------------------------------
-
作者:
男 janker (janker) ★☆☆☆☆ -
盒子活跃会员
2021/9/11 13:55:55
16楼: @wfymqj (wfymqj) 
是的
----------------------------------------------
-
信息
登陆以后才能回复
Copyright © 2CCC.Com 盒子论坛 v3.0.1 版权所有 页面执行117.1875毫秒 RSS