Tekninen artikkeli

PDF-salauksen ja oikeuksien tarkistus Delphissä PDF Library for Delphi-kirjastolla

Käyttöoikeuslippu ei ole turvamekanismi. "Kopiointi kielletty" -bitti sijaitsee samassa /Encrypt-sanakirjassa kuin kryptografia, mikä antaa sille näennäisen pakottavuuden, jota sillä ei ole, ja heti kun kohtelet näitä kahta yhtenä asiana, auditointisi alkaa tuottaa vääriä vastauksia. PDF:stä ei kannata kysyä vain "onko se salattu". Kysymys on tarkempi ja vaikeampi: mikä algoritmi, mikä suojauskäsittelijän revisio, kumpi kahdesta salasanasta asetettiin, mitä käyttöoikeusbittiä väitetään ja mihin tiedoston osiin salaus todellisuudessa vaikuttaa. Tiedosto voi olla muodollisesti salattu ja käytännössä avoin. Se voi kieltäytyä avautumasta mutta jättää metatietonsa selväkieliseksi. Se voi rajoittaa tulostuksen lipulla, jonka mikä tahansa katselin voi vapaasti sivuuttaa. PDF:n auditointi tarkoittaa näiden kaikkien ratkaisemista erikseen, ja PDF Library for Delphi, losLabin PDF-moottori Delphille ja C++Builderille, tuo jokaisen niistä saataville sekä litteän kokonaislukukahva-API:n että tyypitetyn luokkakerroksen kautta

Mitä /Encrypt-sanakirjaan todella tallennetaan

ISO 32000-1:n §7.6 määrittelee asiakirjaturvan kourallisella sanakirjamerkintöjä, ja PDF Library for Delphi peilaa ne yksi yhteen TPDFEncryption-tietueessa. Suodatinversio V ja revisio R valitsevat algoritmiperheen. Length sisältää avainkoon. Käyttöoikeusbitit ovat kohdassa P, omistaja- ja käyttäjäsalasanan varmennusmerkkijonot kohdissa O ja U (AES-256:ssa lisätään OE ja UE), niiden vieressä kulkee EncryptMetadata-lippu, ja kolme muuta kenttää nimeää merkkijonoihin, virtoihin ja upotettuihin tiedostoihin sovellettavat kryptosuodattimet

Tietueen arvo on siinä, ettei se tulkitse mitään puolestasi. Se palauttaa raa'an sanakirjan ja jättää johtopäätökset sinulle, mikä on juuri auditoinnin tarpeisiin oikein. Salatun tiedoston sisäinen selväkielisyys näkyy kohdissa StringFilterIdentity ja StreamFilterIdentity: kun jompikumpi on tosi, vastaava data kulkee Identity-suodattimen läpi koskemattomana riippumatta siitä, mitä asiakirjan salattu tila ilmoittaa. Skanneri, joka pysähtyy kohtaan "/Encrypt-sanakirja on olemassa", pitää tällaista tiedostoa suojattuna, vaikka sen merkkijonot ja virrat ovat selväkielisiä. Sama hienovaraisuus koskee metatietoja. Kun EncryptMetadata on epätosi, XMP-paketti on luettavissa mille tahansa indeksoijalle, vaikka sivusisältö ei ole, mikä kannattaa tietää heti, kun reitityssäännöt tukeutuvat otsikko- tai tekijäkenttään

PDF Library for Delphi -kaavio PDF:n /Encrypt-sanakirjan kentistä, jotka on kuvattu TPDFEncryption-seurantaominaisuuksiin mukaan lukien Identity-salaussuodattimen sudenkuopat
Jokainen /Encrypt-kenttä yhdistyy TPDFEncryption-kenttään, ja Identity-suodatinliput paljastavat, mitkä merkkijonot, streamit tai metatiedot pysyvät luettavissa riippumatta salauksen tilasta

Lyhyt suojauskoe litteällä API:lla

Useimmille putkille neljä litteää kutsua vastaa arkisiin kysymyksiin. LoadFromFile palauttaa onnistumisesta arvon 1, ja kun asiakirja on avattu, salauksen tarkastimet raportoivat sen puretusta tilasta:

var
  PDF: TPDFlib;
begin
  PDF := TPDFlib.Create;
  try
    if PDF.LoadFromFile('contract.pdf', UserPassword) <> 1 then
      raise Exception.Create('Open failed: wrong password or damaged file');
    Writeln('status    : ', PDF.EncryptionStatus);     // decrypted / encrypted / unknown
    Writeln('algorithm : ', PDF.EncryptionAlgorithm);  // RC4 vs AES family
    Writeln('strength  : ', PDF.EncryptionStrength);   // avaimen pituusluokka
    Writeln('owner pw? : ', PDF.CheckPassword(CandidatePassword));
  finally
    PDF.Free;
  end;
end;

CheckPassword on tärkeämpi kuin sen yksirivinen allekirjoitus antaa ymmärtää. PDF määrittelee kaksi eritehoista salasanaa. Käyttäjäsalasana vaaditaan tiedoston avaamiseen ylipäätään. Omistajasalasana antaa täydet oikeudet ja ohittaa jokaisen käyttöoikeusbitin. Levyn tavut ovat kummassakin tapauksessa samat, mutta omistajasanalla avattu istunto voi tehdä asioita, joita käyttäjäsalasanaistunto ei voi, joten auditointi, joka ei kirjaa esitettyä tunnistetta, kirjaa vain puolet totuudesta. Luokkakerros tekee eron kyseltäväksi. TPDFDocument.HasUserPassword ja HasOwnerPassword ilmoittavat, mitä tiedosto vaatii, kun taas IsUserPassword ja IsOwnerPassword ilmoittavat, millä salasanalla nykyinen istunto todella avattiin. Kirjaa tämä tieto. Älä koskaan kirjaa itse salasana-arvoja

Strength-asteikko, jossa "AES-256" tarkoittaa kahta asiaa

Litteät Encrypt- ja EncryptFile-funktiot vastaanottavat kokonaislukuarvoisen Strength-parametrin, jolla on viisi merkityksellistä arvoa: 0 40-bittiselle RC4:lle, 1 128-bittiselle RC4:lle, 2 Acrobat 7:stä alkaen luettavalle 128-bittiselle AES:lle, 3 Acrobat 9:n mukana esitellylle 256-bittiselle AES:lle ja 4 Acrobat X:n ja uudempien vaatimalla 256-bittiselle AES:lle

Mielenkiintoista on, että 3 ja 4 on molemmat nimetty AES-256:ksi, mutta ne eivät ole sama järjestelmä. Strength 3 vastaa suojauskäsittelijän revisiota 5, välivaiheen suunnitelmaa, jonka Acrobat 9 toimitti mutta jota ISO ei koskaan hyväksynyt. Strength 4 vastaa revisiota 6, jonka avaintenjohdantafunktio vahvistettiin ja standardoitiin ISO 32000-2:ssa. Tänään luotavalle asiakirjalle ei ole syytä valita 3:a 4:n sijaan. Auditoinnissa ero on ratkaiseva: käytäntö, jossa lukee "AES-256 ISO 32000-2:n mukaisesti", täyttyy vain R6:lla, ja itseään AES-256:ksi kutsuva R5-tiedosto epäonnistuu siinä käytännössä, vaikka se läpäisisi naiiviin vahvuustarkistuksen. Luokkakerros pitää nämä erillään nimillä esAES256Bit R5:lle ja esAES256BitAcroX R6:lle, ja EncryptionAcroX-ominaisuus vastaa revisiokysymykseen yhdellä totuusarvolla

PDF-salauksen vahvuustikapuu 40-bittisestä RC4:stä AES-256:een, revisio 5 vastoin revisiota 6, Delphi-auditointeja varten
Vahvuudet 3 ja 4 kutsuvat molemmat itseään AES-256:ksi, mutta vain revisio 6 täyttää ISO 32000-2 -käytännön, joten auditointien on kirjattava käsittelijän revisio, ei pelkkää nimeä

Käyttöoikeusbitit ja niiden avainpituutta koskeva pienellä painettu teksti

EncodePermissions pakkaa kahdeksan lippua kokonaisluvuksi, jota Encrypt ja EncryptFile odottavat. Tulostus, kopiointi, muokkaus ja muistiinpanojen lisäys muodostavat perusjoukon; kenttien täyttö, saavutettavuuskopiointi, kokoaminen ja täyden laadun tulostus muodostavat laajennetun joukon. Pienellä painettu teksti, jonka kirjaston oma salausesimerkki sanoo suoraan, on se, että laajennetut neljä tulevat voimaan vasta 128-bittisellä vahvuudella ja sen yläpuolella. Täyden laadun tulostuslippu noudattaa samaa sääntöä: poista se pakottaaksesi pieniresoluutioisen tulostuksen, ja 40-bittinen asiakirja sivuuttaa sinut, koska myös tämä heikennys vaatii 128-bittisen tai vahvemman salauksen. Jos koodaat "vain pieniresoluutioinen tulostus" -käytännön 40-bittiseen tiedostoon, jokainen katselin tulostaa siitä silti täydellä laadulla

Syvempi kysymys on, kuka näitä bittejä panee täytäntöön, eikä vastaus ole kukaan, johon voit luottaa. Käyttöoikeudet ovat ohjeita vaatimustenmukaisille lukijoille, eivät kryptografisia rajoituksia. Purkuavain on sama riippumatta siitä, sallitaanko kopiointi vai ei, joten rajoitettu käyttöoikeusjoukko pitää rehelliset katselimet rehellisinä. Lukija, joka päättää sivuuttaa bitit, ei kohtaa lainkaan kryptografista estettä. Jos velvoitteena on estää purkaminen eikä vain vähentää sitä, tiedosto tarvitsee käyttäjäsalasanan ja työnkulku tarvitsee sitä ympäröivät prosessitason kontrollit, ja auditointiraportin pitää nimetä, kumman järjestelmän alainen kukin tiedosto todella on, sen sijaan että käyttöoikeuslippua pidettäisiin lukkona

Käytännön asettaminen ja sen pysymisen todistaminen

Salauksen lisääminen olemassa oleviin tiedostoihin ei vaadi niiden lataamista oliopuuhun. EncryptFile käsittelee syötteen tulosteeksi yhdellä kutsulla, ja auditointisilmukka avaa tuloksen uudelleen varmistaakseen, mitä levylle päätyi. Toimitettu salausesimerkki noudattaa samaa kirjoita-sitten-lue-takaisin -muotoa:

var
  PDF: TPDFlib;
  R: Integer;
begin
  PDF := TPDFlib.Create;
  try
    R := PDF.EncryptFile('in.pdf', 'out.pdf', 'owner-secret', 'user-secret', 4,
      PDF.EncodePermissions(1, 0, 0, 0,    // print allowed; copy/change/notes denied
                            0, 0, 0, 1));  // laajennettu joukko: vain täysilaatuiseen tulostukseen
    if (R = 1) and (PDF.LoadFromFile('out.pdf', 'user-secret') = 1) then
    begin
      Writeln('algorithm = ', PDF.EncryptionAlgorithm);
      Writeln('strength  = ', PDF.EncryptionStrength);
      Writeln('owner pw accepted: ', PDF.CheckPassword('owner-secret'));
    end;
  finally
    PDF.Free;
  end;
end;

Asiakirjakerroksessa työskentelevät tiimit saavat saman toiminnon tyypitetyillä joukoilla bittien pakkaamisen sijaan, mikä kestää koodikatselmoinnin huomattavasti vähemmällä siristelyllä:

if not Doc.Encrypt('owner-secret', 'user-secret', esAES256BitAcroX,
  [ppCanPrint], [ppCanPrintFull]) then
  raise Exception.Create('Encryption failed');

Kummassakin tapauksessa takaisinlukuvaihe ei ole valinnainen seremonia. Se havaitsee käyttöönoton virheet, jotka muuten tulevat esiin asiakkaan koneessa vasta kuukausia myöhemmin: vanhan kirjastoversion, joka alentaa pyydetyn vahvuuden hiljaa, tulostuspolun, jota ei koskaan kirjoitettu, koska hakemisto oli vain luku -tilassa, tai käyttöoikeuskokonaisluvun, jonka argumentit annettiin väärässä järjestyksessä. Kaikki kolme läpäisevät paikallisen savutestin ja epäonnistuvat kentällä, ja tulosteen uudelleen avaaminen muuttaa ne poikkeukseksi, jonka näet tiedoston luoneen ajon aikana. GetEncryptionFingerprint palauttaa tiiviin arvon, jonka voit tallentaa työmerkinnän yhteyteen, joten myöhempi vertailu kertoo, jakavatko kaksi tulostetta saman salausmäärityksen ilman kummankaan uudelleen avaamista

Auditoinnin väärät positiiviset, joihin kannattaa varautua koodissa

Muutama kuvio johtaa tietoturvaskannerit luotettavasti väärään päätelmään, ja jokainen niistä syntyy, kun moniosainen kysymys kutistetaan kyllä-ei-vastaukseksi. Identity-kryptosuodatin on selkein esimerkki. /Encrypt-sanakirja on olemassa, tiedosto ilmoittaa olevansa salattu, ja silti merkkijonot ja virrat kulkevat Identity-suodattimen läpi muuttumattomina, joten todellinen sisältö on selväkielistä. Ratkaisu on lukea StringFilterIdentity ja StreamFilterIdentity ennen minkään julistamista suojatuksi

Metatietojen jako on hienovaraisempi. EncryptMetadata voi erota muusta asiakirjasta kumpaankin suuntaan ja jättää salattuun tiedostoon luettavan XMP-paketin tai harvemmin päinvastaisen tilanteen. "Tiedosto on salattu" ei kerro mitään siitä, ovatko sen metatiedot salattuja, mikä merkitsee heti, kun indeksoija tai reitityssääntö käyttää otsikkoa. Upotetut tiedostot lisäävät kolmannen ulottuvuuden: PDF sallii erillisen kryptosuodattimen vain liitteille, joten liitteet voivat olla muuten avoimen asiakirjan ainoa salattu osa tai salatun asiakirjan ainoa selväkielinen osa. Tallenna kolme suodatinmääritystä erillisinä kenttinä merkkijonoille, virroille ja upotetuille tiedostoille, eikä mikään näistä ansoista voi yllättää sinua. Tallenna yksi totuusarvo, ja väärä päätelmä on vain ajan kysymys

PDF Library for Delphi: auditointivirta, joka tarkistaa StringFilterIdentityn, StreamFilterIdentityn, EncryptMetadatan ja upotettujen tiedostojen salaussuodattimen ennen kuin PDF julistetaan suojatuksi
Neljä toisistaan riippumatonta ulottuvuutta ratkaisevat, onko salatun näköinen tiedosto todella sinetöity, ja niiden kutistaminen yhdeksi boolean-arvoksi luokittelee ennemmin tai myöhemmin tiedoston väärin

Salauksen poistaminen ja sen valitseminen uusille tiedostoille

Auditointi päättyy usein päätökseen poistaa suojaus, eivätkä mekanismit ole siinä este. DecryptFile(InputFileName, OutputFileName, Password) kirjoittaa puretun kopion ilman täyttä latausta, ja ladatun asiakirjan Decrypt tekee saman muistissa, kun tiedosto on jo avattu. Molemmat vaativat kelvollisen salasanan; kumpikaan ei ohita kryptografiaa. Todellinen portti on käytäntö eikä koodi, joten määritä vastaanottosäännöissäsi selvästi, milloin poistaminen on sallittua, ja kirjaa siihen oikeuttanut salasanaluokka, sillä tekninen vaihe ei jätä itsessään jälkeä

Uusien tulosteiden valinta on kapeampi kuin viisi Strength-arvoa antaa ymmärtää. Käytä Strength 4:ää, AES-256-revisiota 6, ellei tiedostoja ole pakko avata Acrobat X:ää vanhemmissa katselimissa. Strength 2, AES-128, on käytännöllinen alaraja vanhentuvalle katselinkannalle, jota ei voida päivittää. RC4-vaihtoehdot 0 ja 1 ovat olemassa, jotta voit lukea ja auditoida historiallisia arkistoja, eivät siksi, että niiden avulla pitäisi tuottaa mitään uutta; niiden valitseminen vuoden 2026 suunnitelmaan on merkki siitä, että ylävirran vaatimus on vanhentunut

Salaustila vaikuttaa suoraan allekirjoituspäätöksiin, koska asiakirjoja validoiva ja allekirjoittava työpöytä tarvitsee saman takaisinlukukurinalaisuuden, johon tämä auditointi luottaa. Tätä aihetta käsittelee vaatimustenmukaisuuden ja allekirjoittamisen työpöytää käsittelevä artikkeli. Kun eräajo käyttää EncryptFile-kutsua tuhansiin suuriin asiakirjoihin, suurten PDF-tiedostojen suorasaantiopas näyttää, miten muistin käyttö pidetään tasaisena ajon aikana. Täydellinen salauksen API-viite löytyy PDF Library for Delphi-tuotesivulta