A HotPDF konkrét tanúsítványtulajdonosoknak titkosítja a PDF-et az ISO 32000 public-key security handlerén keresztül: az EnablePubKeyEncryption egy 20 bájtos véletlen seedet vesz fel, minden címzett megkapja a saját CMS borítékját, amit RSA kulcsokhoz az AddPubKeyRecipientCertificate épít (RSA-OAEP key transport), elliptikus görbe kulcsokhoz pedig az AddPubKeyAgreementRecipientWithSecret (ECDH P-256-on, P-384-en, P-521-en, X25519-en vagy X448-en). Senki nem oszt meg jelszót; aki birtokolja a megfelelő privát kulcsot, megnyitja a fájlt
A használati eset mindig ugyanannak a történetnek valamilyen változata. Egy negyedéves auditcsomag három külső lektorhoz megy, a jogi osztály azt akarja, hogy mindhárman elolvassák, csak az egyikük nyomtathatja, és senki nem akar jelszót ülni egy e-mail szálban a melléklet mellett. A jelszavas titkosítás ezt nem tudja kifejezni. A tanúsítványos igen, mert minden címzett egy olyan kulccsal oldja fel a dokumentumot, amivel már rendelkezik, és minden címzett a saját borítékjában hordozhat más-más jogosultságkészletet
Miben különbözik a tanúsítványalapú PDF-titkosítás a jelszótól?
Egy public-key titkosítású PDF a fájlkulcsát egy véletlen seedből és minden címzettboríték pontos bájtjaiból származtatja, nem abból, amit egy ember begépel. A handlert az ISO 32000-1 §7.6.4-e írja le (§7.6.5 az ISO 32000-2-ben), a borítékok pedig CMS EnvelopedData struktúrák az RFC 5652 szerint. A HotPDF /Filter /Adobe.PubSec-et ír /SubFilter /adbe.pkcs7.s5-tel; AES-256-nál ez /V 5-öt jelent és egy /DefaultCryptFilter bejegyzést a /CF alatt /CFM /AESV3-mal, a /Recipients tömb pedig azon a crypt filteren belül lakik. Minden boríték 24 bájtot titkosít: a 20 bájtos seedet, majd az adott címzett 32 bites jogosultságszavát. A titkosítási szótár /P értéke csak helyőrző, mert a valódi jogosultságok minden borítékban utaznak. Betöltéskor az olvasó kibont egy borítékot, helyreállítja a seedet, és összehasheli a seedet a /Recipients sorrendjében minden borítékkal (AES-256-nál SHA-256, a régebbi ciphereknél SHA-1), hogy újraépítse a fájlkulcsot. Ha még mindig e modell és a hétköznapi jelszavak közt döntesz, az AES-256 jelszavas titkosítás és jogosultságflag-útmutató lefedi azt a mérlegelés másik oldalát
RSA címzettek írása az EnablePubKeyEncryption-nel
RSA tanúsítványokhoz hívd meg az EnablePubKeyEncryption-t aes256-fal, majd hívd meg az AddPubKeyRecipientCertificate-et DER-kódolt tanúsítványonként egyszer a BeginDoc előtt. A helper folyamaton belül épít RSAES-OAEP borítékot THPDFRSAOAEPHash értékekkel az OAEP digestre és az MGF1 digestre (rohSHA256, rohSHA384 vagy rohSHA512), és a borítéktartalmat AES-256-CBC-vel titkosítja
uses
System.SysUtils, System.IOUtils, HPDFDoc, HPDFCrypt, HPDFRSA;
procedure WriteAuditPack(const OutFile: string);
var
Pdf: THotPDF;
Seed: AnsiString;
begin
SetLength(Seed, 20); // pontosan 20 bájt, AES-256 esetén is
AESGenerateRandomBytes(@Seed[1], Length(Seed));
Pdf := THotPDF.Create(nil);
try
Pdf.AutoLaunch := False;
Pdf.FileName := OutFile;
Pdf.EnablePubKeyEncryption(Seed, aes256, True); // az alapértelmezett kulcstípus az aes128
// A lektor A nyomtathat; a lektor B csak olvashat és kinyerhet
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;
A felsorolásban három részlet tartószerkezet. Először, a seed hossza minden kulcstípusnál 20 bájtra van rögzítve, az AES-256-ot is beleértve; az EnablePubKeyEncryption bármilyen más hossznál kivételt dob. Másodszor, az EnablePubKeyEncryption alapból aes128, és mindkét tanúsítványhelper megtagadja a futást, hacsak a kulcstípus nem aes256, így a második argumentum elfelejtése a „certificate envelopes require aes256" kivételt hozza. Az örökölt cipherek (k40, k128, aes128) továbbra is működnek, de csak az AddPubKeyRecipient-ön át, máshol épített borítékkal. Harmadszor, az AES-256 public-key titkosítás PDF 2.0-s funkció, így a HotPDF automatikusan 2.0-ra emeli a dokumentum verzióját. StrictVersionLock beállítva egy alacsonyabb verzión az EnablePubKeyEncryption úgy tér vissza, hogy semmit nem kapcsol be, és a hiba csak a következő sorban bukkan fel, mint „call EnablePubKeyEncryption first". Titkosításváltás inkrementális frissítés közben rögtön EInvalidOpException-t dob
ECDH címzettek hozzáadása: P-256, P-384, P-521, X25519 és X448
Elliptikus görbe tanúsítványokhoz az AddPubKeyAgreementRecipientWithSecret CMS key-agreement címzettet ír (KeyAgreeRecipientInfo, a KARI struktúra az RFC 5753-ból, az RFC 8418 szerinti X25519 és X448 profillal), és folyamaton belül számolja ki az ECDH megosztott titkot. A görbét egy THPDFPubKeyAgreementScheme értékkel választod: pkasECDHP256, pkasECDHP384, pkasECDHP521, pkasX25519 vagy pkasX448. A sémának egyeznie kell a tanúsítvány kulcsával, vagy a hívás „Certificate key does not match the requested agreement scheme" kivételt dob. A motorháztető alatt minden boríték kap egy friss véletlen 32 bájtos UKM-et, egy stdDH KDF-fel származtatott kulcstitkosító kulcsot (SHA-256 P-256-hoz és X25519-hez, SHA-384 P-384-hez, SHA-512 P-521-hez és X448-hoz), és egy RFC 3394 szerinti AES-256 key wrap-et. Maga a megosztott titok tiszta Pascal görbekódból jön, platform crypto provider nélkül; a tiszta Pascal NIST görbearitmetika cikk elmagyarázza, hogyan épült és hogyan hitelesült az a réteg. A Montgomery-görbéknél a teljes efemer kulcspár lokálisan generálható:
uses
System.SysUtils, System.IOUtils, HPDFDoc, HPDFCrypt, HPDFPubSec,
HPDFKeyAgreement;
procedure AddLegalRecipient(Pdf: THotPDF);
var
Scalar, OriginatorPublic: TBytes;
begin
// Friss efemer skalár borítékonként; a clamping a létrán belül történik
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: csak a NIST görbéknél értelmes
finally
HPDFSecureClearBytes(Scalar);
end;
end;
A NIST görbékre több kell a hívótól. A HotPDF public-key helpereket csak X25519-hez és X448-hoz szállít (HPDFX25519PublicFromScalar, HPDFX448PublicFromScalar), így P-256-hoz, P-384-hez és P-521-hez te generálod az efemer kulcspárt a saját eszközeiddel, és pontosan a testméretű big-endian skalárt adsz át (32, 48 vagy 66 bájt), plusz a hozzáillő tömörítetlen 0x04||X||Y pontot OriginatorPublicKey-ként. A HotPDF a címzett pontját validálja a görbeegyenletre, de nem tudja ellenőrizni, hogy a te originátor publikus kulcsod valóban hozzátartozik-e a skalárodhoz. Az eltérő felek tökéletesen szabályos borítékot gyártanak, amit egyetlen címzett sem tud megnyitni, ezért kell az oda-vissza betöltés a tesztsuite-odba, nem csak egy fájlméret-ellenőrzés
Miért számít a /Recipients sorrendje?
A /Recipients sorrendje azért számít, mert a fájlkulcs a seed és minden boríték digestje tömbösorrendben, így az írónak és az olvasónak ugyanazokat a bájtokat kell hashelnie ugyanabban a sorrendben. A HotPDF a borítékokat abban a sorrendben tartja, ahogy hozzáadod, és változatlanul írja ki őket, ami azt jelenti, hogy a címzetteket tetszőleges sorrendben adhatod hozzá, de semmi a folyamat későbbi szakaszában nem rendezheti át, kódolhatja újra vagy „takaríthatja ki" azt a tömböt. A valódi hibák többsége ezen a területen ennek a témának a variációja volt, ahol a két oldal kicsit más bájtokat hashelt:
- Dinamikus tömbök
TList-ben való tárolásaAdd-del csak nyers pointert őriz, miközben a referenciaszám a lokális változónál marad. A következőSetLengthfelszabadítja a buffert, és újrahasznosíthatja, így minden slot az utolsó borítékot aliasolta, és a többcímzettes fájlok rossz kulcsot származtattak. A javítás, hogy birtokolt másolatot tárolsz:List.Add(Pointer(System.Copy(Bytes))) - A borítékkibontás helyben elemzi a DER-t, és a kulcs-helyreállítási menet eredetileg ugyanazokat az élő tömböket hashelte. Az olvasó mostantól minden boríték tiszta másolatáról pillanatképet készít, mielőtt bármilyen kibontás hozzányúlna, és a digest a pillanatképeken fut
- Egy Unicode
TStringList-en átengedett bináris DER$80-os vagy afölötti bájtjai a kódlap szerint újrakódolódnak, ezért a HotPDF a borítékokat hex szövegként tárolja belül - A titkosított és bináris stringeket hex stringként kell írni. Egy literál string end-of-line normalizáción esik át, ahol CR, LF és CRLF egyaránt egyetlen LF lesz (ISO 32000-1 §7.3.4.2), és az csendben átírja a ciphertextet. A HotPDF minden
/Recipientsbejegyzést hex stringként bocsát ki, és kivonja a stringtitkosítás alól, mert minden olvasónak szüksége van a borítékokra, mielőtt bármilyen kulcsa lenne - Egy DER
BIT STRINGelső bájtja a fel nem használt biteket számolja, és bájt-igazított kulcsnál nullának kell lennie.SetLengthután inicializálatlanul hagyva azt írta be, ami épp a stacken volt, és egy szigorú kibontó elutasította az originátor kulcsot, így egy fájl alkalmanként éppen azzal a kulccsal nem tudott megnyílni, amire írták - Amikor ugyanaz a kulcs továbbra sem tud dekódolni, hasonlíts rétegről rétegre: a fájlkulcsot, aztán a ciphertext prefixet (az IV-et), aztán az objektumkulcsot, végül a plaintextet. A hiba ott lakik, közvetlenül az első, eltérést mutató réteg után
Hogyan nyitsz meg tanúsítvánnyal titkosított PDF-et privát kulccsal?
Egy tanúsítvánnyal titkosított PDF megnyitásához regisztráld a privát kulcsanyagot, mielőtt meghívnád a LoadFromFile-t, mert a HotPDF a szerkezeti menet alatt állítja helyre a fájlkulcsot. Egy HPDFParsePFX-szel feldolgozott RSA vagy EC kulcsot rendeld a PubSecKeyMaterial-hoz, további RSA kulcsokat vegyél fel az AddPubSecKeyMaterial-lal, nyers ECDH skálárokat pedig regisztrálj az AddPubSecAgreementKeyMaterial(CurveOID, PrivateScalar, OwnPublicPoint)-tal, a HPDFOIDX25519, HPDFOIDX448, HPDFOIDECP256, HPDFOIDECP384 vagy HPDFOIDECP521 konstansokkal. A NIST görbék megkövetelik a címzett saját tömörítetlen publikus pontját; a Montgomery-görbék figyelmen kívül hagyják
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);
// Opcionális: válaszd ki közvetlenül a borítékot az összes kipróbálása helyett
Reader.PubSecRecipientQuery :=
function(Context: Pointer; RecipientCount: Integer): Integer
begin
Result := -1; // -1 = próbáld végig minden borítékot sorrendben
end;
Reader.LoadFromFile('audit-pack.pdf', '');
Writeln('Pages: ', Reader.GetLoadedPageCount);
finally
Reader.Free;
end;
end;
Callback nélkül a HotPDF minden borítékot kipróbál minden regisztrált kulccsal: először az elsődleges kulcsot, majd minden további RSA kulcsot, végül az EC anyagot. A PubSecRecipientQuery megkapja a borítékok számát, és nullától induló indexet vagy -1-et ad vissza, a tömbön kívüli index pedig kivételt dob ahelyett, hogy beszorulna. Ne feledd, hogy az AddPubSecKeyMaterial csak RSA anyagot fogad el ( modulusra és privát exponensre ragaszkodik), az EC kulcsok tehát a PubSecKeyMaterial-ba vagy az AddPubSecAgreementKeyMaterial-ba tartoznak. Amikor egyetlen kulcs sem bont fel borítékot, a helyreállítási lépés fájlkulcs nélkül tér vissza kivétel helyett, ezért ellenőrizd, hogy a várt tartalom tényleg dekódolódott-e, ahelyett hogy megbíznál abban, hogy a betöltési hívás visszatért
Mit nem garantál a HotPDF
A HotPDF garantálja, hogy a saját írója és olvasója bájtról bájtra egyetértenek, és olyan borítékokat épít, amik követik a fent hivatkozott CMS struktúrákat. Nem garantálja viszont, hogy minden PDF-megjelenítő minden kombinációt megnyit. Az RSA-OAEP key transport és az X25519 vagy X448 címzettek támogatása olvasónként és verziónként változik, és azokhoz a kombinációkhoz nem publikáltunk kompatibilitási eredményeket. Ha egy dokumentumnak egy konkrét megjelenítőben kell megnyílnia, titkosíts egy tesztfájlt azonos kulcstípusú teszttanúsítványra, és ott nyisd meg, mielőtt elkötelezed magad egy séma mellett. A borítékban utazó jogosultságok szabályzatot jelentenek, amit a szabványmegfelelő szoftver tiszteletben tart, pontosanúgy, mint a jelszavas titkosítás alatt. A seed minősége is a te felelősséged: az AESGenerateRandomBytes pont erre való, és a HotPDF kitörli a seed saját másolatát, amint a fájlkulcs származtatódott. Ha egy stringhez, streamhez vagy melléklethez is más crypt filter kell, a crypt filter szabályzat-útmutató az StmF-hez, StrF-hez és EFF-hez megmutatja, melyik szűrneveket fogadja el a public-key handler
A tanúsítványos titkosítás, az RSA-OAEP és ECDH címzettborítékok és a privátkulcs-betöltés mind a HotPDF Delphi PDF komponensben érkezik, a jelszavas titkosítás, a digitális aláírások és az ISO 32000 eszközkészlet többi része mellett Delphihez és C++Builderhez