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;
希望这对某人有所帮助,再见。
波兰政府(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;
希望这对某人有所帮助,再见。