Bir Delphi imzalama yordamını SHA3-512'ye geçirdiniz ve OpenSSL sonucu reddetti: CMS SHA3-512 bildiriyor, ama messageDigest özniteliği o algoritmanın 64 ürettiği yerde 32 bayt taşıyor. Evet, Delphi kodu SHA-3 ile imza atabilir: PDFium Component'teki SignPadesForDigest, SHA3-256, SHA3-384 ya da SHA3-512 kabul eder ve ISO/TS 32001'in beklediği gibi, özete bağımlı her CMS alanını o tek seçime göre yazar
ISO/TS 32001 bir PDF 2.0 imzasına ne ekler
ISO/TS 32001, ISO 32000-2'ye kısa bir uzantıdır; bir PDF imzasının bildirebileceği özet fonksiyonu kümesini genişletir. Temel liste ISO 32000-2 §12.8.3.1'in Tablo 260'ında durur ve CMS tabanlı subfilter'lar için SHA-256, SHA-384, SHA-512 ve RIPEMD-160'te kalır; SHA-1 önerilmeyen durumdadır. Teknik şartname, FIPS 202'den gelen SHA-3 ailesini ekler. SHA-3, SHA-2 ile hiçbir iç yapı paylaşmayan bir sünger yapısıdır; SHA-2 bir gün zayıflarsa diye rafta ikinci bir özet ailesi bulundurmak isteyen bir imza politikası için çekici taraf tam olarak budur. CMS'de her varyant, NIST yayı altında bir nesne tanımlayıcısıdır. Birleşik imza algoritması tanımlayıcıları da tam yanlarında durur:
TCmsDigestAlgorithm | Özet OID | Çıktı | RSA PKCS#1 v1.5 | ECDSA |
|---|---|---|---|---|
cdaSha256 | 2.16.840.1.101.3.4.2.1 | 32 bayt | 1.2.840.113549.1.1.11 | 1.2.840.10045.4.3.2 |
cdaSha3_256 | 2.16.840.1.101.3.4.2.8 | 32 bayt | 2.16.840.1.101.3.4.3.14 | 2.16.840.1.101.3.4.3.10 |
cdaSha3_384 | 2.16.840.1.101.3.4.2.9 | 48 bayt | 2.16.840.1.101.3.4.3.15 | 2.16.840.1.101.3.4.3.11 |
cdaSha3_512 | 2.16.840.1.101.3.4.2.10 | 64 bayt | 2.16.840.1.101.3.4.3.16 | 2.16.840.1.101.3.4.3.12 |
PDFium Component'te tüm seçim tek bir enumeration'dır: FPdfCms ünitesinden TCmsDigestAlgorithm. Üyeyi anahtara seçtirin. Bir NIST eğrisi kendi genişliğinde bir özet bekler; dolayısıyla P-256, SHA3-256 ile, P-384, SHA3-384 ile ve P-521, SHA3-512 ile eşleşir ve ISO/TS 32002'nin eğri ve özet politikasını uygulayan bir doğrulayıcı, öbür her eşleştirmeyi geçersiz işaretler. RSA'de böyle bir kural yoktur. Güç argümanı zaten kısadır: RSA-2048 bir anahtar kabaca 112 bit güvenlik, RSA-3072 kabaca 128 bit sunar; dolayısıyla SHA3-256 anahtarı çoktan geçmiştir, RSA-2048 anahtarı üzerindeki SHA3-512 ise CMS'e 32 bayt ekler, güvenliğe hiçbir şey eklemez. Yazılı bir politika daha büyük bir varyant adlandırmadıkça RSA için SHA3-256 öneriyoruz
Özetle birlikte değişen beş CMS alanı
Özet fonksiyonunu değiştirmek işin en küçük parçasıdır. Ayrık (detached) bir CAdES imzası özetini beş yerde adlandırır ve titiz bir doğrulayıcı bunları çapraz denetler. Herhangi biri hâlâ SHA-256 diyorsa RSA ya da ECDSA aritmetiği doğru olsa bile imza düşer:
- SignerInfo içindeki
digestAlgorithm, SignedDatadigestAlgorithmskümesine yansır ve özet OID'sini taşır; SHA3-384 için örneğin 2.16.840.1.101.3.4.2.9 messageDigestimzalı özniteliği (RFC 5652), iki/ByteRangediliminin karmasını taşır; dolayısıyla OCTET STRING'i 32, 48 ya da 64 bayt uzunluğundadırsignatureAlgorithm, yukarıdaki tablodan birleşik şemayı adlandırır. RSASSA-PSS istisnadır: OID'si id-RSASSA-PSS (1.2.840.113549.1.1.10) olarak kalır, özet parametrelerin içine taşınır- RSASSA-PSS parametreleri özeti iki kez yineler —
hashAlgorithmolarak ve MGF1 karması olarak — vesaltLength'i özet uzunluğuna ayarlar. Bunları dışarıda bırakın; doğrulayıcı SHA-1, SHA-1 ile MGF1 ve 20 baytlık bir tuz varsayar, ki bu başka bir algoritmadır. RFC 4055'in RSASSA-PSS-params etiketleme kuralları da burada işin içindedir - CMSAlgorithmProtection imzalı özniteliği (RFC 6211), özet ile imza algoritması tanımlayıcılarını imzalı özniteliklerin içine kopyalar; kimse onları fark ettirmeden değiştiremez. 1 ve 3 numaralı alanlarla bayt bayt eşleşmelidir
Tek bir alan hiç değişmez. İmzalayan-sertifika-v2 özniteliğinin içindeki ESSCertIDv2 (RFC 5035), imzalayan sertifikasını kendi hashAlgorithm'ıyla karmalar; bu, varsayılanı SHA-256'dır ve SignedData özetinden bağımsızdır, dolayısıyla PDFium Component onu varsayılan kodlamada SHA-256'da tutar. Elle yazılmış CMS kodunda en sık görülen hata messageDigest uzunluğudur. SHA-256 için yazılan kod, karmayı sabit 32 baytlık bir kayıtta tutup o kaydı özniteliğe kopyalama eğilimindedir. İçinden bir SHA3-512 sonucu geçirin: öznitelik ilk 32 baytı alır, digestAlgorithm ise .10 der — açılıştaki OpenSSL reddi tam olarak budur. Aynı hata bir katman altında da yaşar: OpenSSL'in EVP_PKEY_sign'i özet uzunluğunu tbslen olarak alır ve bağlama ayarlı ileti özetiyle karşılaştırır; dolayısıyla her seferinde 32 geçen bir imzalayan, SHA-3'ü hiç anmayan bir uzunluk hatası alır. PDFium Component, belge özetini çözümlenen imzalayanın bildirdiği algoritmadan hesaplar; öznitelikleri, PSS parametrelerini ve algoritma koruma özniteliğini aynı bildirimden türetir, böylece hiçbir alan ötekilerden kayamaz
Yerel bir PDF'i SignPadesForDigest ile imzalamak
TPdf bileşeninde SHA-3 giriş noktaları, SignPadesForDigest(const FileName: string; const Options: TPadesSignOptions; DigestAlgorithm: TCmsDigestAlgorithm): Boolean ile onun akış ikizi SignPadesToStreamForDigest'tir. İkisi de SignPades ile aynı TPadesSignOptions kaydını alır ve cdaSha256, var olan overload ile aynı imzayı üretir; böylece tek bir kod yolu iki aileye hizmet eder. Windows'ta bir SHA-1 parmak izi, Current User MY deposunda bir sertifika seçer. Öbür platformlarda OpenSSL arka ucu, ConfigureSslSigningCredentials'e verilen anahtar ve sertifika ile imzalar ve bir parmak izini reddeder. Belge sürümünü de denetleyin. ISO/TS 32001, PDF 2.0'ı uzatır ve imza, özgün başlığı koruyan bir artımlı güncelleme olarak girer; dolayısıyla PDF 2.0 kurallarına göre doğrulama yapan bir güvenen taraf, imzalamadan önce 2.0 bir dosya ister
uses
System.SysUtils, System.Classes, System.IOUtils,
PDFium, FPdfCms, FPdfCrypto, FPdfPades
{$IFDEF MSWINDOWS}, FPdfCryptoWin{$ELSE}, FPdfCryptoSsl{$ENDIF};
function Sha3SigningAvailable: Boolean;
begin
{$IFDEF MSWINDOWS}
Result := WinSha3DigestAvailable; // CNG SHA-3 sağlayıcıları, Windows 11 24H2+
{$ELSE}
Result := SslSha3SigningAvailable; // kimlik bilgileri yapılandırılana kadar False
{$ENDIF}
end;
procedure SignWithSha3(const SourceFile, SignedFile, Thumbprint: string;
const KeyPassword: AnsiString; const Log: TProc<string>);
var
Pdf: TPdf;
Opts: TPadesSignOptions;
Signed: TMemoryStream;
begin
{$IFNDEF MSWINDOWS}
// OpenSSL açıkça yapılandırılmış malzemeyle imzalar; bu taslakta zincir yok
ConfigureSslSigningCredentials(
TFile.ReadAllBytes('/etc/signing/signer-key.pem'),
TFile.ReadAllBytes('/etc/signing/signer.der'), nil, KeyPassword);
{$ENDIF}
if not Sha3SigningAvailable then
raise Exception.Create('No SHA-3 capable signing backend on this machine');
Opts := TPadesSignOptions.Default; // B-B, RSASSA-PSS
{$IFDEF MSWINDOWS}
Opts.CertificateThumbprint := Thumbprint; // Current User MY içindeki sertifika
{$ENDIF}
Opts.Reason := 'Approved for release';
Pdf := TPdf.Create(nil);
Signed := TMemoryStream.Create;
try
Pdf.FileName := SourceFile;
Pdf.Active := True;
if Pdf.PdfVersion <> pv20 then
Log(SourceFile + ' is not a PDF 2.0 file');
try
if Pdf.SignPadesToStreamForDigest(Signed, Opts, cdaSha3_256) then
Signed.SaveToFile(SignedFile);
except
on E: EPadesCryptoUnavailable do
Log('SHA-3 signing unavailable: ' + E.Message);
end;
finally
Signed.Free;
Pdf.Free;
end;
end;
Arka uç SHA-3 üretemediğinde çağrı, SHA-256'ya düşmek yerine EPadesCryptoUnavailable fırlatır. İstenen davranış tam olarak budur. Birisi SHA3-256'yı bir nedenle istemiştir; çoğu zaman güvenen tarafın politika belgesinden. Yerine üretilmiş bir SHA-256 imzası sizin tarafınızdaki her denetimi geçer ve onların tarafında geri çevrilir — ekibinizdeki kimsenin hangi makinenin imzaladığını hatırlamadığı haftalar sonra. İstisnayı, operatöre hâlâ hangi ana makinede algoritmanın eksik olduğunu söyleyebildiğiniz yerde yakalayın
SHA-3 nerede var: Windows CNG ve OpenSSL
Erişilebilirlik makineye aittir. Windows'ta varsayılan arka uç CNG'dir ve CNG, SHA-3 algoritma sağlayıcılarını Windows 11 24H2'den itibaren getirir; daha eski derlemelerde "SHA3-256" için BCryptOpenAlgorithmProvider, STATUS_NOT_FOUND ile başarısız olur. WinSha3DigestAvailable, SHA3-256, SHA3-384 ve SHA3-512 sağlayıcılarını bir kez açar ve yanıtı önbelleğe alır; süreç çalışırken bu değişemez. Her başka yerde OpenSSL arka ucu imzalar ve ne yapabileceği, bulduğu libcrypto'ya bağlıdır:
- OpenSSL 3.x her zaman
EVP_sha3_256,EVP_sha3_384veEVP_sha3_512ihraç eder; dolayısıyla SHA-3 imzalama, arka ucun yüklendiği her platformda çalışır - Yükleyici bir 1.1 serisi kitaplığı da kabul eder (
libcrypto-1_1.dll,libcrypto.so.1.1). SHA-3, OpenSSL 1.1.1'de geldi ve 1.1.0'da yoktur; dolayısıyla eski Linux imajlarındaSslSha3SigningAvailablekarar verir SslSha3SigningAvailableayrıca yapılandırılmış imzalama kimlik bilgileri ister.ConfigureSslSigningCredentials'den önce sorulursa,SslLibraryVersionsapasağlam bir 3.x kitaplığı bildirse bile False yanıtlar. Bu, eksik bir arka uca benzer. Yalnızca bir sıralama problemidir- 32 bitlik bir çalıştırılabilir dosya
libcrypto-3-x64.dll'i yükleyemez. Bu arka uç ayağa kaldırılırken x64 DLL'i işaret eden bir Win32 derlemesi,SslMissingSymbolsyalnızca kitaplık yolunu yankılarkenSslAvailable = Falsebildirdi; bozuk bir sembol bağına benzeyen belirti. Sebep 32 bitlikti ve çalıştırılabilir dosyayla eşleşen bir DLL çözdü
Hâlâ 24H2 öncesi makineler barındıran bir Windows filosu, SHA-3 olan yerde CNG'yi tutabilir ve kalanını bir imzalayan sağlayıcısıyla OpenSSL üzerinden yönlendirebilir. IPadesSignerProvider'ın iki yöntemi vardır. FPdfCryptoSsl'deki ResolveSslSignerForDigest, bildirdiği özeti, imza algoritmasını, özet uzunluğunu ve PSS tuzunu istenen algoritmayı izleyen bir imzalayan döndürür; bir sağlayıcı yapılandırılmışken ResolvePadesSignerForDigest, döndürülen imzalayanın çağrının istediği özeti bildirdiğini denetler ve bildirmiyorsa EPadesCrypto fırlatır. SHA3-256 için kurulmuş bir sağlayıcı, SHA3-384'lük bir isteği damgalayamaz. Ancak sağlayıcı süreç genelsindedir; eşzamanlı imzalayan bir sunucu onu, bu taslaktaki gibi çağrı başına değil, başlangıçta bir kez yapılandırmalıdır. DLL arama yolunda değilken ConfigureSslLibraryPath, yükleyiciyi libcrypto'ya yöneltir
uses
System.SysUtils, System.Classes, System.IOUtils,
PDFium, FPdfCms, FPdfCrypto, FPdfCryptoWin, FPdfCryptoSsl, FPdfPades;
type
TOpenSslSha3Provider = class(TInterfacedObject, IPadesSignerProvider)
private
FDigest: TCmsDigestAlgorithm;
public
constructor Create(ADigest: TCmsDigestAlgorithm);
function BackendName: string;
function Resolve(const Options: TPadesSignerOptions): TPadesResolvedSigner;
end;
constructor TOpenSslSha3Provider.Create(ADigest: TCmsDigestAlgorithm);
begin
inherited Create;
FDigest := ADigest;
end;
function TOpenSslSha3Provider.BackendName: string;
begin
Result := 'OpenSSL';
end;
function TOpenSslSha3Provider.Resolve(
const Options: TPadesSignerOptions): TPadesResolvedSigner;
begin
Result := ResolveSslSignerForDigest(Options, FDigest);
end;
procedure SignSha3OnAnyWindows(Pdf: TPdf; Dest: TStream;
const Opts: TPadesSignOptions; const KeyPassword: AnsiString);
var
Provider: IPadesSignerProvider;
Chain: TPadesChainCertArray;
SslOpts: TPadesSignOptions;
begin
if WinSha3DigestAvailable then
begin
Pdf.SignPadesToStreamForDigest(Dest, Opts, cdaSha3_256); // CNG yolu
Exit;
end;
Chain := nil;
ConfigureSslSigningCredentials(
TFile.ReadAllBytes('C:\Signing\signer-key.pem'),
TFile.ReadAllBytes('C:\Signing\signer.der'), Chain, KeyPassword);
if not SslSha3SigningAvailable then
raise Exception.Create('No SHA-3 in loaded OpenSSL ' + SslLibraryVersion);
SslOpts := Opts;
SslOpts.CertificateThumbprint := ''; // OpenSSL yapılandırılmış sertifikayla imzalar
Provider := TOpenSslSha3Provider.Create(cdaSha3_256);
ConfigurePadesSignerProvider(Provider);
try
Pdf.SignPadesToStreamForDigest(Dest, SslOpts, cdaSha3_256);
finally
ConfigurePadesSignerProvider(nil);
end;
end;
Uzak ya da HSM imzalayan özeti nasıl öğrenir?
Uzak imzalama işi ikiye böler. PreparePadesRemoteSignature, boş bir /Contents yuvası taşıyan bir PDF ile bir TPadesRemoteSigningRequest yazar ve anahtar sahibi ayrık CMS'i daha sonra — çoğu zaman başka bir süreçten ya da makineden — HSM ve bulut anahtarları için kullanılan hazırla ve tamamla oturumuyla döndürür. Hazırlık tarafında TPadesRemoteSignOptions.DigestAlgorithm'i, diyelim ki cdaSha3_384'e ayarlarsınız ve istek seçimi, CMS messageDigest'inin taşıması gereken değer olan DocumentDigest'in yanına, TPadesRemoteSigningRequest.DigestAlgorithm içine işler. Bundan sonrasında yetkili istektir. CompletePadesRemoteSignature, /ByteRange özetini istekte saklanan algoritma ile yeniden hesaplar ve seçeneklere bir daha asla başvurmaz; yoksa hazırlık ile tamamlama arasındaki bir yapılandırma değişikliği, eski bir isteği yeni bir algoritma ile yargılardı. Bileşen üzerine kurulu bir imzalama hizmeti de algoritmayı aynı yerden almalıdır:
uses
System.SysUtils, System.Classes,
FPdfCms, FPdfCrypto, FPdfCryptoSsl, FPdfPades,
FPdfPadesRemote; // Enterprise uygulamasını bağlar
function SignRemoteRequest(const RequestFile: string;
Padding: TPadesPaddingScheme): TBytes;
var
Stream: TFileStream;
Request: TPadesRemoteSigningRequest;
SignerOpts: TPadesSignerOptions;
Resolved: TPadesResolvedSigner;
begin
Stream := TFileStream.Create(RequestFile, fmOpenRead or fmShareDenyWrite);
try
Request := LoadPadesRemoteSigningRequest(Stream);
finally
Stream.Free;
end;
SignerOpts := TPadesSignerOptions.Default; // OpenSSL için boş parmak izi
SignerOpts.PaddingScheme := Padding;
// Özeti istek belirler; bu hizmetin yapılandırması değil
Resolved := ResolveSslSignerForDigest(SignerOpts, Request.DigestAlgorithm);
Result := BuildSignedDataEx(Request.DocumentDigest, Resolved.Signer,
Resolved.SignerCertDer, Resolved.ChainCertDer, TCmsBuildOptions.Default);
end;
İsteğin içinde algoritmanın tek kanıtı özet uzunluğudur; aynı zamanda bir parmak izi gibi de iş görür. Herhangi bir akışı okumadan önce CompletePadesRemoteSignature, Length(DocumentDigest) ile CmsDigestByteLength(DigestAlgorithm)'i karşılaştırır; böylece kırpılmış bir özet ya da yeniden etiketlenmiş bir algoritma tam orada düşer ve hedef akışa hiç dokunulmaz. Uzunluk tek başına SHA3-256 ile SHA-256'yı ayıramaz, ikisi de 32 bayttır; bu yüzden özet değeri, döndürülen CMS'in içindeki messageDigest ile de karşılaştırılır. Buna karşılık hazırlanmış PDF, SHA-256 ile SHA3-384 için bayt bayt aynıdır: yer tutucu yalnızca /ByteRange sayıları taşır, hiçbir özet taşımaz. Aynı dosyalar, ayarın yok sayıldığı sanısına kolayca kapı aralar. Uzak imzalama, FPdfPadesRemote ünitesi üzerinden Enterprise sürümünde gelir
Varyant seyrek olarak zor karardır; bir RSA anahtarı için genellikle cdaSha3_256'dır ve üretimde düşen şey, CNG'si ya da libcrypto'su onu üretemeyen bir ana makinedir. Bu yüzden erişilebilirlik denetimi, Delphi için PDFium Component'in herhangi bir dağıtımında uygulama başlangıcına aittir