JPK - CryptoAPI RSA 256/ECB/PKCS#1 使用证书中的 public 密钥进行一次性密码加密(解决方案)

JPK - CryptoAPI RSA 256/ECB/PKCS#1 one time secret password encryption with public key from certificate (solution)

波兰政府(MF - 财政部)最近实施了 SAF-T(标准审计文件 - 税务/PL:JPK - Jednolity Plik Kontrolny)。我很难以正确的方式实施此解决方案的关键部分之一。这部分用于加密用户生成的用于加密文件的密码,发送到 Azure 云存储,使用来自 MS CryptoAPI 的 RSA 256/ECB/PKCS#1 算法,使用从 MF 提供的证书文件加载的 public 密钥.

我的工作解决方案是(使用 JEDI API Library & Security Code Library):

unit CryptoAPI_RSA;
// The MIT License (MIT)
// 
// Copyright (c) 2016 Grzegorz Molenda
// 
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
// 
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
// 
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
interface

uses
  SysUtils,
  Classes;

function CryptoAPI_Encrypt_RSA(const Input: TBytes; const cert: TMemoryStream): String;

implementation

uses
  Windows,
  StrUtils,
  JwaWinCrypt,
  JwaWinError,
  EncdDecd;

type
  ERSAEncryptionError = class(Exception);

function WinError(const RetVal: BOOL; const FuncName: String): BOOL;
var
  dwResult: Integer;
begin
  Result:=RetVal;
  if not RetVal then begin
    dwResult:=GetLastError();
    raise ERSAEncryptionError.CreateFmt('Error [x%x]: %s failed.'#13#10'%s', [dwResult, FuncName, SysErrorMessage(dwResult)]);
  end;
end;

procedure reverse(var p: TBytes; len: Integer);
var
  i, j: Integer;
  temp: Byte;
begin
  i:=0;
  j:=len - 1;
  while i < j do begin
    temp:=p[i];
    p[i]:=p[j];
    p[j]:=temp;
    Inc(i);
    Dec(j);
  end;
end;

function CryptoAPI_Encrypt_RSA(const Input: TBytes; const cert: TMemoryStream): String;
var
  derCert: AnsiString;
  derCertLen: Cardinal;
  hProv: HCRYPTPROV;
  certContext: PCCERT_CONTEXT;
  certPubKey: HCRYPTKEY;
  len: LongWord;
  rsa: TBytes;
  ins: TMemoryStream;
  ous: TStringStream;
begin
  Result:='';
  if (cert <> Nil) and (cert.Size > 0) then begin
    SetLength(derCert, 4096);
    FillChar(derCert[1], 4096, 0);
    // Convert from PEM format to DER format - removes header and footer and decodes from base64
    WinError(CryptStringToBinaryA(PAnsiChar(cert.Memory), cert.Size, CRYPT_STRING_BASE64HEADER, @derCert[1], derCertLen, Nil), 'CryptStringToBinaryA');
    SetLength(derCert, derCertLen);
    try
      // Get the certificate context structure from a certificate.
      certContext:=CertCreateCertificateContext(X509_ASN_ENCODING or PKCS_7_ASN_ENCODING, @derCert[1], derCertLen);
      WinError(certContext <> Nil, 'CertCreateCertificateContext');
      try
        hProv:=0;
        WinError(CryptAcquireContext(hProv, Nil, Nil, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT), 'CryptAcquireContext'); // flag CRYPT_VERIFYCONTEXT - for backward compatibility with win2003server (and probably with win10pro+)
        try
          // Get the public key information for the certificate.
          certPubKey:=0;
          WinError(CryptImportPublicKeyInfo(hProv, X509_ASN_ENCODING or PKCS_7_ASN_ENCODING,
                                            @certContext.pCertInfo.SubjectPublicKeyInfo, certPubKey), 'CryptImportPublicKeyInfo');
          len:=Length(Input);
          if len > 0 then begin
            SetLength(rsa, len + 512);
            FillChar(rsa, len + 512, 0);
            try
              CopyMemory(@rsa[0], @Input[0], len);
              // encrypt our Input buffer
              WinError(CryptEncrypt(certPubKey, 0, True, 0, @rsa[0], len, len + 512), 'CryptEncrypt');
              SetLength(rsa, len);
              // IMPORTANT !!!
              // .Net RSA algorithm is BIG-ENDIAN,
              // CryptoAPI is LITTLE-ENDIAN, 
              // so reverse output before sending to Azure Cloud Storage
              reverse(rsa, len);
              ins:=TMemoryStream.Create;
              try
                ins.Write(rsa[0], len);
                ins.Position:=0;
                ous:=TStringStream.Create;
                try
                  EncodeStream(ins, ous);
                  ous.Position:=0;
                  Result:=ous.DataString;
                  Result:=ReplaceStr(Result, #13#10, '');
                finally
                  ous.Free;
                end;
              finally
                ins.Free;
              end;
            finally
              SetLength(rsa, 0);
            end;
          end;
        finally
          WinError(CryptReleaseContext(hProv, 0), 'CryptReleaseContext');
        end;
      finally
        CertFreeCertificateContext(certContext);
      end;
    finally
      SetLength(derCert, 0);
    end;
  end;
end;

end.

用法是:

var
  cf: TMemoryStream;
  input: TBytes;
  output: String;
begin
  if Edit1.Text = '' then
    Exit;
  Memo1.Clear;
  cf:=TMemoryStream.Create;
  try
    cf.LoadFromFile('cert.pem'); // certificate with public key
    input:=TEncoding.Default.GetBytes(Edit1.Text);
    try
      output:=CryptoAPI_Encrypt_RSA(input, cf);
    finally
      SetLength(input, 0);
    end;
    Memo1.Lines.Text:=output;
  finally
    cf.Free;
  end;
end;

希望这对某人有所帮助,再见。