HotPDF užšifruoja PDF konkretiems sertifikatų savininkams per ISO 32000 viešojo rakto saugos apdorojimo mechanizmą: EnablePubKeyEncryption paima 20 baitų atsitiktinę seed, ir kiekvienas gavėjas gauna savą CMS voke, kurį RSA raktams sukonstruoja AddPubKeyRecipientCertificate (RSA-OAEP rakto perdavimas), o elipsinės kreivės raktams – AddPubKeyAgreementRecipientWithSecret (ECDH ant P-256, P-384, P-521, X25519 arba X448). Niekas nesidalina slaptažodžiu; failą atveria tas, kas turi atitinkantį privatų raktą
Naudojimo atvejis visada yra kažkuri tos pačios istorijos versija. Ketvirtinis audito paketas keliauja trims išoriniams peržiūrėtojams, juristai nori, kad kiekvienas jų jį perskaitytų, spausdinti leidžiama tik vienam, o niekam nereikia slaptažodžio, gulinčio el. pašto gijoje šalia priedo. Slaptažodžio šifravimas to neišreiškia. Sertifikatų šifravimas – gali, nes kiekvienas gavėjas dokumentą atrakina raktu, kurį jau turi, ir kiekvienas gavėjas savo voke gali nešti kitokį leidimų rinkinį
Kuo sertifikatų pagrįstas PDF šifravimas skiriasi nuo slaptažodžio?
Viešuoju raktu užšifruotas PDF savo failo raktą išveda iš atsitiktinės seed plius tikslių kiekvieno gavėjo voko baitų, o ne iš nieko, ką žmogus surinka. Apdorojimo mechanizmas aprašytas ISO 32000-1 §7.6.4 (§7.6.5 ISO 32000-2), o vokai yra RFC 5652 apibrėžtos CMS EnvelopedData struktūros. HotPDF rašo /Filter /Adobe.PubSec su /SubFilter /adbe.pkcs7.s5; AES-256 atveju tai reiškia /V 5 ir /DefaultCryptFilter įrašą po /CF su /CFM /AESV3, o /Recipients masyvas gyvena tame crypt filtre. Kiekvienas voke užšifruoja 24 baitus: 20 baitų seed, po kurio seka to gavėjo 32 bitų leidimų žodis. /P reikšmė šifravimo žodyne yra tik vietos ženklas, nes tikri leidimai keliauja kiekvieno voko viduje. Įkėlimo metu skaitytuvas išvynioja vieną voką, atgauna seed ir hašuoja seed kartu su kiekvienu voku /Recipients tvarka (SHA-256 AES-256, SHA-1 senesniems šiframs), kad atstatytų failo raktą. Jei vis dar svėrinate šį modelį ir paprastus slaptažodžius, AES-256 slaptažodžio šifravimo ir leidimų vėliavėlių vadovas aprėpia kitą to kompromiso pusę
RSA gavėjų rašymas su EnablePubKeyEncryption
RSA sertifikatams iškvieskite EnablePubKeyEncryption su aes256, tada AddPubKeyRecipientCertificate po kartą kiekvienam DER koduotam sertifikatui prieš BeginDoc. Pagalbinė funkcija RSAES-OAEP voke sukonstruoja procese su THPDFRSAOAEPHash reikšmėmis OAEP hašui ir MGF1 hašui (rohSHA256, rohSHA384 ar rohSHA512), o voko turinį užšifruoja AES-256-CBC
uses
System.SysUtils, System.IOUtils, HPDFDoc, HPDFCrypt, HPDFRSA;
procedure WriteAuditPack(const OutFile: string);
var
Pdf: THotPDF;
Seed: AnsiString;
begin
SetLength(Seed, 20); // lygiai 20 baitų, netgi AES-256
AESGenerateRandomBytes(@Seed[1], Length(Seed));
Pdf := THotPDF.Create(nil);
try
Pdf.AutoLaunch := False;
Pdf.FileName := OutFile;
Pdf.EnablePubKeyEncryption(Seed, aes256, True); // numatytasis rakto tipas aes128
// Peržiūrėtojas A gali spausdinti; peržiūrėtojas B gali tik skaityti ir ištraukti
Pdf.AddPubKeyRecipientCertificate(TFile.ReadAllBytes('reviewer-a.cer'),
[prPrint, prPrint12bit, prExtractContent], rohSHA256, rohSHA256);
Pdf.AddPubKeyRecipientCertificate(TFile.ReadAllBytes('reviewer-b.cer'),
[prExtractContent]);
Pdf.BeginDoc;
Pdf.CurrentPage.SetFont('Arial', [], 11);
Pdf.CurrentPage.TextOut(72, 720, 0, 'Q3 audit pack');
Pdf.EndDoc;
finally
Pdf.Free;
end;
end;
Trys to sąrašo detalės yra laikančiosios. Pirma, seed ilgis fiksuotas – 20 baitų kiekvienam rakto tipui, įskaitant AES-256; EnablePubKeyEncryption kelia išimtį bet kokiam kitam ilgiui. Antra, EnablePubKeyEncryption pagal numatymą naudoja aes128, ir abu sertifikatų pagalbininkai atsisako veikti, kol rakto tipas nėra aes256, tad užmiršus antrąjį argumentą gaunama išimtis „certificate envelopes require aes256“. Senieji šifrai (k40, k128, aes128) vis tiek veikia, bet tik pro AddPubKeyRecipient su kitur sukurtu voke. Trečia, AES-256 viešojo rakto šifravimas yra PDF 2.0 funkcija, tad HotPDF automatiškai pakelia dokumento versiją iki 2.0. Nustačius StrictVersionLock žemesnei versijai, EnablePubKeyEncryption grįžta nieko neįjungęs, o gedimas pasirodo tik kitoje eilutėje kaip „call EnablePubKeyEncryption first“. Šifravimo keitimas inkrementinio atnaujinimo metu iš karto kelia EInvalidOpException
ECDH gavėjų pridėjimas: P-256, P-384, P-521, X25519 ir X448
Elipsinės kreivės sertifikatams AddPubKeyAgreementRecipientWithSecret rašo CMS rakto sutarimo gavėją (KeyAgreeRecipientInfo, KARI struktūrą iš RFC 5753, su X25519 ir X448 profiliu iš RFC 8418) ir ECDH bendrą paslaptį apskaičiuoja procese. Kreivę pasirenkate THPDFPubKeyAgreementScheme reikšme: pkasECDHP256, pkasECDHP384, pkasECDHP521, pkasX25519 ar pkasX448. Schema turi sutapti su sertifikato raktu, kitaip iškvietimas kelia „Certificate key does not match the requested agreement scheme“. Po gaubtu kiekvienas voke gauna šviežią atsitiktinę 32 baitų UKM, rakto šifravimo raktą, išvestą stdDH KDF (SHA-256 P-256 ir X25519, SHA-384 P-384, SHA-512 P-521 ir X448), ir RFC 3394 apibrėžtą AES-256 rakto apvyniojimą. Pati bendra paslaptis ateina iš gryno Pascal kreivių kodo, be jokio platformos crypto tiekėjo; gryno Pascal NIST kreivių aritmetikos straipsnis paaiškina, kaip tas sluoksnis buvo pastatytas ir patikrintas. Montgomery kreivėms visą efemerinę rakto porą galima sugeneruoti lokaliai:
uses
System.SysUtils, System.IOUtils, HPDFDoc, HPDFCrypt, HPDFPubSec,
HPDFKeyAgreement;
procedure AddLegalRecipient(Pdf: THotPDF);
var
Scalar, OriginatorPublic: TBytes;
begin
// Šviežias efemerinis skaliaras kiekvienam vokui; clamping vyksta kopėčiose
SetLength(Scalar, 32);
AESGenerateRandomBytes(@Scalar[0], Length(Scalar));
try
OriginatorPublic := HPDFX25519PublicFromScalar(Scalar);
Pdf.AddPubKeyAgreementRecipientWithSecret(
TFile.ReadAllBytes('legal-x25519.cer'),
[prPrint, prExtractContent], pkasX25519,
OriginatorPublic, Scalar,
[]); // OwnPublicPoint: prasminga tik NIST kreivėms
finally
HPDFSecureClearBytes(Scalar);
end;
end;
NIST kreivės iš kviečiančiojo reikalauja daugiau. HotPDF viešojo rakto pagalbininkus tiekia tik X25519 ir X448 (HPDFX25519PublicFromScalar, HPDFX448PublicFromScalar), tad P-256, P-384 ir P-521 atveju efemerinę rakto porą sugeneruojate savais įrankiais ir perduodate big-endian skaliarą, lygiai lauko dydžio (32, 48 ar 66 baitai), plius atitinkantį nesuspaustą 0x04||X||Y tašką kaip OriginatorPublicKey. HotPDF gavėjo tašką patikrina pagal kreivės lygtį, bet negali patikrinti, ar jūsų originaučio viešasis raktas iš tikrųjų priklauso jūsų skaliarui. Nesutampantys pusės vis tiek duoda visiškai tvarkingą voką, kurio negali atverti nė vienas gavėjas – todėl apsukimas įkėlimu pirmyn ir atgal priklauso jūsų testų rinkiniui, o ne vien failo dydžio patikrinimui
Kodėl /Recipients tvarka svarbi?
/Recipients tvarka svarbi, nes failo raktas yra hašas per seed ir kiekvieną voką masyvo tvarka, tad rašytojas ir skaitytuvas turi hašuoti tuos pačius baitus ta pačia seka. HotPDF vokus laiko jų pridėjimo tvarka ir rašo nepakeistus, tad gavėjus galite pridėti bet kuria jums patinkančia tvarka, bet niekas toliau grandinėje negali to masyvo supertvarkyti, pakartotinai užkoduoti ar „sutvarkyti“. Dauguma tikrų šios srities bugų buvo tos temos variacijos, kur dvi pusės hašavo kiek kitokius baitus:
- Dinaminių masyvų saugojimas
TListperAddpalieka tik žalią rodyklę, o nuorodų skaitiklis lieka pas vietinį kintamąjį. KitasSetLengthatlaisvina buferį ir gali jį panaudoti pakartotinai, tad kiekvienas langas gale slėpė paskutinįjį voką, ir daugelio gavėjų failai išvedė neteisingą raktą. Pataisa – saugoti nuosavą kopiją suList.Add(Pointer(System.Copy(Bytes))) - Vokų išvyniojimas DER išanalizuoja vietoje, o rakto atkūrimo perėjimas iš pradžių hašavo tuos pačius gyvus masyvus. Skaitytuvas dabar padaro pirmines kiekvieno voko kopijas, dar prieš bet kuriam išvyniojimui juos lietiant, o hašas bėga per kopijas
- Dvejetainis DER, praėjęs pro Unicode
TStringList, baitus nuo$80ir aukščiau perkoduoja pagal kodų puslapį, tad HotPDF vokus viduje saugo kaip hex tekstą - Užšifruotos ir dvejetainės eilutės turi būti rašomos kaip hex eilutės. Literalinė eilutė paklūsta eilutės pabaigos normalizacijai, kur CR, LF ir CRLF visi tampa vienu LF (ISO 32000-1 §7.3.4.2), ir tai tyliai perrašo šifratą. HotPDF kiekvieną
/Recipientsįrašą išveda kaip hex eilutę ir atleidžia jį nuo eilučių šifravimo, nes kiekvienam skaitytuvui vokai reikalingi dar prieš turint bet kokį raktą - Pirmasis DER
BIT STRINGbaitas skaičiuoja nepanaudotus bitus ir turi būti nulis baitais lygiuotiems raktams. Palikus jo neinicializuotą poSetLength, ten atsidurdavo tai, kas gulėjo stoke, ir griežtas išvyniotojas atmesdavo originaučio raktą, tad failas retkarčiais galėdavo neatverti su pačiu raktu, kuriam jis buvo rašytas - Kai tas pats raktas vis tiek nesugeba iššifruoti, lyginkite sluoksnis po sluoksnio: failo raktą, tada šifrato priešdėlį (IV), tada objekto raktą, tada griežtąjį tekstą. Bugas gyvena iškart po pirmojo sluoksnio, kuris nesutampa
Kaip atverti sertifikatu užšifruotą PDF su privatuoju raktu?
Norėdami atverti sertifikatu užšifruotą PDF, užregistruokite privataus rakto medžiagą dar prieš kviesdami LoadFromFile, nes HotPDF failo raktą atgauna struktūrinės perėjimo metu. RSA arba EC raktą, išanalizuotą HPDFParsePFX, priskirkite PubSecKeyMaterial, papildomus RSA raktus pridėkite AddPubSecKeyMaterial, o žalius ECDH skaliarus užregistruokite AddPubSecAgreementKeyMaterial(CurveOID, PrivateScalar, OwnPublicPoint), naudodami HPDFOIDX25519, HPDFOIDX448, HPDFOIDECP256, HPDFOIDECP384 ar HPDFOIDECP521 konstantas. NIST kreivės reikalauja gavėjo paties nesuspausto viešojo taško; Montgomery kreivės jo nepaiso
uses
System.SysUtils, System.IOUtils, HPDFDoc, HPDFPFX, HPDFKeyAgreement;
procedure OpenAuditPack(const LegalScalar: TBytes);
var
Reader: THotPDF;
begin
Reader := THotPDF.Create(nil);
try
Reader.AutoLaunch := False;
Reader.PubSecKeyMaterial :=
HPDFParsePFX(TFile.ReadAllBytes('reviewer-a.pfx'), 'pfx-password');
Reader.AddPubSecAgreementKeyMaterial(HPDFOIDX25519, LegalScalar, nil);
// Pasirinktinai: imkite voką tiesiai, vietoj to, kad bandytumėte visus
Reader.PubSecRecipientQuery :=
function(Context: Pointer; RecipientCount: Integer): Integer
begin
Result := -1; // -1 = bandyti kiekvieną voką eilės tvarka
end;
Reader.LoadFromFile('audit-pack.pdf', '');
Writeln('Pages: ', Reader.GetLoadedPageCount);
finally
Reader.Free;
end;
end;
Be callback HotPDF bandys kiekvieną voką prieš kiekvieną užregistruotą raktą: pirmiausia pirminį raktą, tada kiekvieną papildomą RSA raktą, tada EC medžiagą. PubSecRecipientQuery gauna vokų skaičių ir grąžina 0-based indeksą arba -1, o indeksas už masyvo ribų kelia išimtį, o ne būna nukirptas. Atkreipkite dėmesį, kad AddPubSecKeyMaterial priima tik RSA medžiagą (reikalauja modulio ir privataus eksponento), tad EC raktai priklauso PubSecKeyMaterial arba AddPubSecAgreementKeyMaterial. Kai nė vienas raktas neišvynioja nė vieno voko, atkūrimo žingsnis grįžta be failo rakto, o ne keldamas išimtį, tad patikrinkite, ar tikrai iššifruotas jūsų laukiamas turinys, o ne pasitikėkite tuo, kad įkėlimo iškvietimas grįžo
Ko HotPDF negarantuoja
HotPDF garantuoja, kad jo paties rašytojas ir skaitytuvas sutampa baitas į baitą, ir sukonstruoja vokus, laikančius anksčiau cituotas CMS struktūras. Jis negarantuoja, kad kiekviena PDF peržiūros programa atvers kiekvieną kombinaciją. RSA-OAEP rakto perdavimo ir X25519 arba X448 gavėjų palaikymas skiriasi tarp skaitytuvų ir versijų, o mes neskylijome suderinamumo rezultatų tiems kombinacijoms. Jei dokumentas privalo atsiverti konkrečioje peržiūros programoje, užšifruokite testinį failą testiniam to paties rakto tipo sertifikatui ir ten jį atverkite, dar prieš apsispręsdami dėl schemos. Leidimai, nešami voko viduje, lieka politika, kurią atitinkanti programinė įranga gerbia – lygiai taip pat, kaip ir slaptažodžio šifravime. Seed kokybė taip pat jūsų atsakomybė: AESGenerateRandomBytes tam ir yra, o HotPDF savo seed kopiją ištrina, kai tik failo raktas išvestas. Jei eilutei, srautui ar priedui taip pat reikia kito crypt filtro, crypt filtrų politikos vadovas StmF, StrF ir EFF parodo, kuriuos filtrų vardus priima viešojo rakto apdorojimo mechanizmas
Sertifikatų šifravimas, RSA-OAEP ir ECDH gavėjų vokai bei privataus rakto įkėlimas visi atkeliauja su HotPDF Delphi PDF komponentu, greta slaptažodžio šifravimo, skaitmeninių parašų ir likusios ISO 32000 priemonių dalies Delphi ir C++Builder aplinkose