PDFlibPas, Delphi ve C++Builder için PDF kütüphanesi, Çin ticari kriptografi setini saf Object Pascal içinde uygular: SM3 özeti (GB/T 32905), SM4 blok şifrelemesi (GB/T 32907) ve SM2 eliptik eğri imzaları (GB/T 32918). Bunlarla PDF'leri SetSignProcessSM2Identity üzerinden SM2-with-SM3 CMS imzalarıyla imzalar ve strength 6 ile Encrypt üzerinden belgeleri bir SM4 crypt filtresiyle şifreler; işin içinde ne bir OS sağlayıcısı ne de harici bir DLL vardır
Bunu istemenin olağan nedeni merak değil, satın alma şartıdır. Çin devletine, finansına ve sağlık sektörüne satılan sistemlerden ulusal algoritmaları kullanmaları giderek beklenir ve Windows CryptoAPI ile CNG, SM2 ile imzalamaz ya da SM3 ile özetlemez. İmzalama yolu platformdan geçiyorsa yanıt kısaca hayırdır. Saf Pascal bir uygulama aynı yanıtı Win32, Win64 ve FPC hedeflerinde de verir ve bunu belge hattının altında gezinen bir yerel bağımlılık olmadan verir
GB/T seti neden saf Object Pascal ile kuruldu?
PDFlibPas, SM2, SM3 ve SM4'ü Pascal'da kurar çünkü zaten hedeflediği hiçbir platform sağlayıcısı bu işi yapamaz. CryptoAPI ve CNG'nin SM3 imza desteği yoktur; SM2 kimliği var olmadan önce kütüphane, özet SM3'e ayarlandığında imzalamayı bilinçli olarak reddediyordu. Alternatif, bir özet OID bildirip imzayı başka bir şeyin üzerinden taşıyan bir SignerInfo'ydu; bu da görünür bir hata değil sessiz bir yanlış imzadır
Üç algoritmanın doğal bir kuruluş sırası vardır ve kütüphane onu izledi. Önce SM3 geldi, çünkü SM2 hem ZA kimlik özeti hem ileti özeti için ona ihtiyaç duyar. SM4 bağımsız bir ünite olarak ikinci geldi. SM2 en son geldi: önce spesifikasyon ekindeki yayımlanmış örnek eğriye karşı doğrulanmış ham ilkel fonksiyonlar olarak, sonra da Ed25519, Ed448 ve ML-DSA'yı hâlihazırda yöneten aynı CMS kurucusuna bağlanmış bir imzalama kimliği olarak. Aynı yaklaşım saf Pascal'da Ed448 ve Brainpool ECDSA eğrileri ile FIPS 204 ML-DSA post-quantum imzalayıcısı için anlatılır; SM2 de bir anahtar türü daha olarak o çerçeveye takılır
İlkel fonksiyonlar genel amaçlı işlevlerdir; PDF imzalamasının dışında da kullanabilirsiniz. PDFlibDigest içindeki SM3String ve SM3ByteArray ham 32 baytlık özeti döndürür (hex değil, karakter başına bir bayt) ve PDFlibSM4, iki biçimde ECB ve CBC yardımcıları dışa aktarır: yanlış uzunlukta boş dizge döndüren blok hizalı sürümler ve SM4EncryptCBCPKCS7 gibi, değişken uzunluklu veri için PKCS#7 sürümleri
uses
SysUtils, PDFlibDigest, PDFlibSM4;
function RawToHex(const S: AnsiString): string;
var
I: Integer;
begin
Result := '';
for I := 1 to Length(S) do
Result := Result + IntToHex(Byte(S[I]), 2);
end;
var
Digest, Key, IV, Cipher, Plain: AnsiString;
begin
// GB/T 32905 örneği: SM3("abc")
Digest := SM3String('abc');
Assert(LowerCase(RawToHex(Digest)) =
'66c7f0f462eeedd9d1f2d46bdc10e4e24167c4875cf2f7a2297da02b8f4ba8e0');
// SM4 16 baytlık anahtar ister; CBC 16 baytlık IV ister
Key := 'SixteenByteKey!!';
IV := 'InitVector16byte';
Cipher := SM4EncryptCBCPKCS7(Key, IV, 'Invoice 2026-0042 approved');
Plain := SM4DecryptCBCPKCS7(Key, IV, Cipher);
Assert(Plain = 'Invoice 2026-0042 approved');
end;
O 16 baytlık dizgeleri elle saymak riskinize olur. SM4 test setinde 16 bayt olması tasarlanan iki test dizgesi 17 bayt çıktı ve blok hizalı yardımcılar tasarım gereği yanlış uzunlukta boş dizge döndürdüğü için hata, bir istisna yerine kafa karıştırıcı bir assertion olarak ortaya çıktı
SM2 kimliğiyle bir PDF imzalamak
Bir PDFlibPas imzalama süreci, SetSignProcessSM2Identity ya da SetSignProcessSM2IdentityFromFile ona bir sertifika ve bir özel anahtar eklediğinde SM2 anahtarı kullanır. Sertifika DER'dir; özel anahtar ham bir 32 baytlık skaler, şifresiz bir PKCS#8 kapsayıcısı ya da bir SEC 1 ECPrivateKey olabilir. Bir SM2 kimliği eklemek, aynı süreçteki herhangi bir Ed25519, Ed448, ML-DSA ya da Brainpool kimliğini temizler ve sertifika deposu ile CNG imzalamayı kapatır; böylece tek bir imzalama süreci asla birbirine rakip iki anahtar taşımaz
var
Pdf: TPDFlib;
SignId: Integer;
begin
Pdf := TPDFlib.Create;
try
SignId := Pdf.NewSignProcessFromFile('contract.pdf', '');
if SignId = 0 then
raise Exception.Create('cannot open source PDF');
Pdf.SetSignProcessField(SignId, 'Signature1');
if Pdf.SetSignProcessSM2IdentityFromFile(SignId,
'signer-sm2.cer', 'signer-sm2.key') <> 1 then
raise Exception.Create('SM2 certificate or key file missing');
Pdf.SetSignProcessInfo(SignId, 'Approved', 'Shanghai', '[email protected]');
Pdf.SetSignProcessCustomSubFilter(SignId, 'ETSI.CAdES.detached');
Pdf.SetSignProcessDigestAlgorithm(SignId, 8); // 8 = SM3
Pdf.EndSignProcessToFile(SignId, 'contract-signed.pdf');
if Pdf.GetSignProcessResult(SignId) <> 1 then
raise Exception.CreateFmt('SM2 signing failed, code %d',
[Pdf.GetSignProcessResult(SignId)]);
Pdf.ReleaseSignProcess(SignId);
finally
Pdf.Free;
end;
end;
SetSignProcessSM2IdentityFromFile dönüş değerinin ne söylediğine ve ne söylemediğine dikkat edin. 1 döndürmesi, iki dosyanın da okunup imzalama sürecine saklandığı anlamına gelir. Gerçek kontrol — özel skalerden genel anahtarı türetmek ve onu sertifikayla karşılaştırmak — imza uygulandığında çalışır; uyumsuz bir anahtar çifti bu yüzden kimlik çağrısında değil, 1 olmayan bir GetSignProcessResult olarak ortaya çıkar
SetSignProcessDigestAlgorithm içindeki 8 kodu SM3'ü seçer ve bir SM2 kimliğiyle fiilen zorunludur: CMS kurucusu, süreç ne istemiş olursa olsun bir SM2 anahtarı için SM3'ü zorlar; tıpkı bir Ed25519 anahtarının SHA-512'ye zorlanması gibi. Ters kombinasyon da engellenir. adbe.pkcs7.detached ya da ETSI.CAdES.detached ile SM3'e ayarlı ama SM2 kimliği taşımayan bir imzalama süreci, imzalamadan çıkar; çünkü kütüphanedeki başka hiçbir imzalayıcı, bildirilen OID'e uyan SM3 tabanlı bir imza üretemez
Bir SM2-with-SM3 SignerInfo'ya ne girer?
PDFlibPas'taki bir SM2 imzası, DER kodlu imzalı özniteliklerin — standart CMS kurulumunun — üzerinden hesaplanır, ama SM2, ECDSA'nın olmayan bir adım ekler. GB/T 32918.2, iletiyi ZA ile birlikte özetler; ZA, ZA = SM3(ENTL || ID || a || b || Gx || Gy || xA || yA) olarak tanımlı bir kimlik özetidir; burada eğrinin her parçası 32 baytlık big-endian bir değerdir ve ID, imzalayanın ayırt edici kimliğidir. İmza ardından r = (e + x1) mod n ve s = ((1 + d)-1(k - r·d)) mod n olur; k, kütüphane CSPRNG'sinden taze bir nonce alır ve bozuk turlarda yeniden deneme yapılır
PDFlibPas, spesifikasyonun varsayılan ayırt edici kimliğini — 16 karakterlik 1234567812345678 dizgesini — hem imzalama hem doğrulama tarafında kullanır. Onu değiştirecek bir API yoktur. Bu, sahendeki SM2 sertifikalarının ezici çoğunluğuyla uyuşur; ama bir karşı taraf özel bir ID ile imzalıyorsa ZA değerleri farklılaşır ve başka her bayt doğru olsa bile imza doğrulanmaz
(r, s) çifti, her DSA ailesi SignerInfo'nun kullandığı düzende, iki INTEGER değerinden oluşan bir DER SEQUENCE olarak CMS'e girer. PDFlibPas, GM/T 0006'da kayıtlı algoritma tanımlayıcılarını yazar: SM2 anahtarı için 1.2.156.10197.1.301, SM3 için 1.2.156.10197.1.401 ve SM2-with-SM3 için 1.2.156.10197.1.501. SM3 ile oluşturulan RFC 3161 zaman damgası istekleri (SetTimeStampOptions özeti 8) aynı SM3 OID'ini taşır. 3.589.1 öncesi sürümler kayıt dışı bir 1.2.156.101.1 kolu yazıyordu; doğrulama onu hâlâ kabul eder, yani o sürümlerle imzalanan belgeler PDFlibPas içinde geçerliliğini korur, ama üçüncü taraf doğrulayıcılar onları tanımaz. Kendi hattınızdan çıkacak her şeyi yeniden imzalamak için iyi bir neden
SM2 doğrulamasının kırıldığı yer: DER imza baytı ve kaybolan elde
SM2 kodundaki mühendislik emeğinin çoğu, inandırıcı görünen çıktı üreten iki kusura gitti. İkisi de elle yazılmış büyük tamsayılı kriptografinin tipik kusurlarıdır ve bir kez tanıdığınızda ikisinden de ucuza kaçılır
İlki bir DER imza baytıydı. ASN.1 INTEGER değerleri işaretlidir; kodlayıcı, üst biti ayarlı her r ya da s değerinin başına doğru bir şekilde 0x00 ekler ve 33 baytlık bir değer ortaya çıkar. Doğrulayıcı ise her tamsayıyı, o baytı önce söküp atmadan sabit 32 bayt genişliğe dönüştürüyordu. r ve s değerlerinin yaklaşık yarısının üst biti ayarlıdır; yeni imzalanan belgelerin büyük bir kesimi bu yüzden doğrulamadan geçemiyordu. İlk deneme turu tesadüfen kısa değerler çekti ve geçti; rastgele nonce ile imzalayan Delphi Win32 seti sorunu anında ortaya çıkardı. Doğrulayıcı artık herhangi bir 33 baytlık tamsayıyı 32 bayta doldurmadan önce baştaki sıfırı siler ve daha uzun olanı reddeder
İkincisi 256 bitlik toplamada kaybolan bir eldeydi. Alan elemanları, sekiz küçük-endian UInt32 limb'idir ve modüler toplama "topla, sonuç p'nin altında değilse p'yi çıkar" biçiminde yazılmıştı. İndirgenmiş girdilerde toplam 2p'nin altında kalır ama 2256'yı aşabilir; aştığında atılan elde, değeri düşük 256 bite geri katlıyor ve p ile karşılaştırma her şeyin yolunda olduğunu söylüyordu. Büyük koordinatlı bir noktayı ikiye katlamak yanlış gidiyordu; küçük katlar gitmiyordu. Görünür belirti şu oldu: 1G ile 8G referansla uyuşurken 9G, 10G ve 11G uyuşmuyordu. Düzeltme, 1280 toplama girdisi ile çıktısını bağımsız hesaplanmış değerlerle karşılaştırıp ilk sapan çifte inerek bulundu
PDFlibSM2 içindeki düzeltilmiş toplama, ham limb toplamasının döndürdüğü eldeyi tutar ve elde ayarlıysa ya da sonuç p'nin altında değilse p'yi bir kez çıkarır; tek çıkarma yeterlidir, çünkü p 2255'in üzerindedir. Desen, ikinin gücünün hemen üzerindeki herhangi bir asal alana uyar. Toplama yardımcınız son eldeni atıyorsa, yalnızca 1G, 2G ve 3G değil, ikiye katlamanın sözcük sınırını ilk aştığı noktayı çok geçen katları da test edin
SM3 doğru olmadan önce neden iki kez yanlıştı?
İlk SM3 uygulaması her standart test vektöründe başarısız oldu; onun yanında yazılan bağımsız bir referans uygulaması da öyle. Birbiriyle uyuşup standardla uyuşmayan iki uygulama, yazım hatasını değil spesifikasyonun paylaşılan bir yanlış anlaşılmasını işaret eder. Formülleri yayımlanmış SM3 metniyle satır satır karşılaştırmak, kodun bellekten yazıldığı üç yer buldu
- İleti genişletmesi 7 bitlik döndürmeyi W[j-13]'e uygular; W[j-6] düz bir XOR terimidir
- SS1, rotl(A, 12), E ve döndürülmüş tur sabitini XOR ile değil 232 modunda toplamayla birleştirir
- 16 ile 63 arası turlarda FF çoğunluk fonksiyonudur: (X AND Y) OR (X AND Z) OR (Y AND Z); SHA tarzı seçim fonksiyonu GG'ye aittir
O üç düzeltmenin ardından boş dizge, abc ve 64 baytlık iki bloklu vektörün hepsi eşleşti. SM4 ilk denemede yerini buldu, çünkü S-box ve test vektörleri hiç kod yazılmadan spesifikasyon metninden alınmıştı: tur anahtarı rk0 = F12186F9, tek bloklu vektör 681EDF34D206965E86B3E94F536E4246 ve bir milyon iterasyonlu vektör 595298C7C6FD271F0402F804C33D3F66. SM2 çalışması ilgili bir kural ekledi: uzun hex sabitleri elle yazılmaz, gruplanıp üretilir ve uzunluk kontrolünden geçirilir; çünkü elle kopyalanmış bir taban noktası koordinatında eksik bir basamak, var olmayan uygulama hatalarının peşine bir hata ayıklama turu sürükledi
SM4 crypt filtresiyle bir PDF şifrelemek
TPDFlib.Encrypt, strength 6 ile seçili belgeyi ISO 32000-1 §7.6.5'in crypt filtresi çerçevesi üzerinden SM4-128 ile şifreler. Şifreleme sözlüğü /V 4, /R 4 ve /Length 128 ile standart güvenlik işleyicisini kullanır ve /StdCF crypt filtresi /CFM /SM4 taşır. Strength 6 ile kaydetmek dosyayı en az PDF 1.6'ya yükseltir; GetEncryptionLevel böyle bir dosya için 7 raporlar ve genel anahtarlı şifreleme, strength 4 ile EncryptPublicKey üzerinden aynı seçeneği alır
var
Pdf: TPDFlib;
Perm: Integer;
begin
Pdf := TPDFlib.Create;
try
if Pdf.LoadFromFile('report.pdf', '') <> 1 then
raise Exception.Create('cannot load PDF');
// yazdırma, kopyalama ve form doldurma serbest; düzenleme ve birleştirme yok
Perm := Pdf.EncodePermissions(1, 1, 0, 0, 1, 1, 0, 1);
if Pdf.Encrypt('owner-secret', 'reader-pass', 6, Perm) <> 1 then
raise Exception.Create('SM4 encryption failed');
Pdf.SaveToFile('report-sm4.pdf');
finally
Pdf.Free;
end;
end;
Her dizge ve akış, 16 baytlık rastgele bir IV ve ardından PKCS#7 dolgulu SM4-CBC şifreli metni olarak şifrelenir; AESV2'nin kullandığı düzenin aynısıdır. Nesne başına anahtar, ISO 32000-1 §7.6.2'nin 1 numaralı algoritmasını izler: dosya anahtarı, nesne numarasının üç düşük baytı ve üretim numarasının iki baytı, artı sAlT eki; MD5 ile özetlenir ve 16 bayta kırpılır. Paroladan anahtara türetim revizyon 4 olanıdır; strength 6 bu yüzden onun sınırlarını miras alır: parolalar SASLprep işleminden geçirilmez (yalnızca AES-256 olan 3, 4 ve 5 geçirilir) ve anahtar türetimi AESV2'ninkinden daha güçlü değildir
Dağıtmadan önce bilmeniz gereken birlikte çalışabilirlik sınırları
Dürüst özet şu: SM2 imzalaması yukarıda anlatılan OID'ler ve varsayılan ID sınırları içinde birlikte çalışır; SM4 şifrelemesi ise yalnızca aynı kuralı bilen yazılımlarla birlikte çalışır. /SM4, ISO 32000-1 Tablo 25'te ya da ISO 32000-2'de tanımlı /CFM değerlerinden biri değildir; o tablolar None, V2, AESV2 ve AESV3'ü tanımlar. /CFM /SM4 ile karşılaşan ana akış bir PDF okuyucusu dosyanın şifresini çözmez. PDFlibPas ise kendi güvenlik işleyicisi üzerinden /SM4 ile /SM4V1 varyantını okur; yani PDFlibPas'ın yazıp okuduğu kapalı bir hat çalışır. Geneli hedefleyen bir dosya AES-256 kullanmalıdır
İmzalarda sınır, sertifika düzeni ve karşı taraftaki doğrulayıcıdır. İmzalama sürecinin arkasındaki kontrol olan PLValidateSM2Identity, SubjectPublicKeyInfo'nun SM2 eğrisi 1.2.156.10197.1.301'i parametre olarak taşıyan id-ecPublicKey içerdiği GM/T 0015 düzenini kabul eder; SM2 OID'ini algoritma alanının kendisine koyan sertifikaları da kabul eder ve türetilen genel noktası özel skalerle uyuşmayan her şeyi reddeder. Üretimde SM2 imzalarına güvenmeden önce, gerçekten kullanacağınız CA'dan bir sertifikayla bir belge imzalayın ve karşı tarafın çalıştırdığı doğrulayıcıyla doğrulayın. Kimlik kontrolünü bir imzalama süreci başlatmadan önce kendiniz de çalıştırabilirsiniz:
uses
SysUtils, PDFlibModernSign;
var
CertDER, KeyDER, Scalar: AnsiString;
begin
// CertDER ve KeyDER diskten ya da bir HSM dışa aktarımından yüklendi
// Out parametresini kendi değişkeninde tutun: KeyDER'i hem Const girdi
// hem Out çıktı olarak vermek girdiyi önce boşaltır
if not PLValidateSM2Identity(CertDER, KeyDER, Scalar) then
raise Exception.Create('certificate and SM2 private key do not match');
FillChar(Pointer(Scalar)^, Length(Scalar), 0);
end;
O yorum, entegrasyon çalışmasından gerçek bir kusuru kaydeder. Aynı AnsiString değişkeni hem Const girdi hem Out çıktı olarak verildiğinde derleyici Out değişkenini girişte sonlandırdı, girdi rutin onu okumadan boşaldı ve tam sağlam bir anahtarda kimlik kontrolü başarısız oldu. Anahtarı Const olarak alıp türetilmiş bir anahtarı Out olarak döndüren her API, çağıran tarafında ayrı değişkenler ister
SSS
PDFlibPas, SM2, SM3 ve SM4 için Windows CNG ya da harici bir DLL gerektirir mi?
Hayır. PDFlibPas üç algoritmayı da Object Pascal içinde, PDFlibDigest, PDFlibSM4 ve PDFlibSM2'de uygular ve SM2 imzalarını kütüphanenin CMS kodu kurar ve doğrular. SM2 imzasının üretilmesinde Windows CryptoAPI ve CNG'nin payı yoktur
SM3 yerine SM2'yi SHA-256 ile kullanabilir miyim?
Hayır. Bir imzalama süreci SM2 kimliği taşıdığında PDFlibPas CMS kurucusu daima SM3 kullanır, çünkü GB/T 32918.2, ZA kimlik özetini içeren bir SM3 özeti üzerinden SM2 imzası tanımlar. SM2 imzalama sürecinde özeti SHA-256'ya ayarlamak sonucu değiştirmez
Adobe Acrobat, PDFlibPas ile SM4 ile şifrelenmiş bir PDF açar mı?
Beklemeyin. /CFM /SM4 crypt filtresi ISO 32000'ün tanımladığı değerlerin dışındadır; ana akış okuyucular onu tanımaz. SM4 şifrelemesini dosyaları PDFlibPas ile ya da uyumlu bir uygulamayla okuyan kapalı sistemler için kullanın; genel dağıtıma gidecek belgeler için AES-256 kullanın
SM2 imzalaması, SM3 özetlemesi, SM4 şifrelemesi ve PAdES imzalama API'lerinin tamamı PDFlibPas, the Delphi PDF Library ile gelir; kütüphane Windows ve Unix hedeflerinde Delphi, C++Builder ve FPC/Lazarus ile derlenir