2016-09-10 11 views
0

Недавно правительство Франции (Министерство финансов - Министерство финансов) внедрило SAF-T (Standard Audit File-Tax/pl: JPK - Jednolity Plik Kontrolny). У меня было много проблем с правильной реализацией одной из ключевых частей этого решения. Эта часть предназначена для шифрования сгенерированного пользователем пароля, который используется для шифрования файлов, отнесенных к Azure Cloud Storage, с алгоритмом RSA 256/ECB/PKCS # 1 от MS CryptoAPI, используя открытый ключ, загруженный из файла сертификата, обслуживаемого MF.JPK - CryptoAPI RSA 256/ECB/PKCS # 1 однократное шифрование секретного пароля с открытым ключом из сертификата (решения)

ответ

2

Мой рабочий раствора это (использует 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; 

Надеется, что это может помочь кому-то, увидится.

+0

Необходимо было изменить 'FillChar (rsa, len + 512, 0)' в 'FillChar (rsa [0], len + 512, 0)', чтобы заставить его работать. Я использую его для шифрования строк, которые я могу расшифровать в SQL Server. Сертификат выполнен в TSQL с «CREATE CERTIFICATE» и экспортируется с помощью «BACKUP CERTIFICATE ... TO FILE». Файл имеет формат DER, поэтому я заменил 'CryptStringToBinaryA()' на 'Move (cert.Memory ^, derCert [1], cert.Size)'. 'reverse()' не требуется. – crk