Tekninen artikkeli

Salatun PDF:n salasanan uudelleenyrittäminen Delphissä PDF Library for Delphi:lla

PDF Library for Delphi yrittää väärää salasanaa uudelleen salatussa PDF:ssä hylkäämällä TPDFDocument-olion, joka juuri epäonnistui, ja luomalla täysin uuden seuraavaa yritystä varten, ohjattuna OnPassword-takaisinkutsulla (TPDFlibPasswordEvent), joka ajetaan jopa kuudentoista yrityksen ajan ennen luovuttamista. Se on tarkoituksellinen poikkeama vaistosta, johon useimmat Delphi-kehittäjät turvautuvat ensin: pidä muistissa jo istuva asiakirjaolio, syötä sille korjattu salasana, ja lataa uudelleen paikallaan alusta aloittamisen sijaan. PDF Library for Delphi:n uudelleenyrityssilmukka, lisätty v3.245.0:ssa, ottaa vastakkaisen kannan, syistä, jotka ovat spesifisiä sille, mitä epäonnistunut salasanayritys jättää jälkeensä. Sen takana oleva skenaario on riittävän tavallinen, että useimmat asiakirjapainotteiset Delphi-sovellukset osuvat siihen lopulta: vastaanottonäyttö hyväksyy PDF:n, salattu jälki pakottaa salasanadialogin, operaattori näppäilee merkkijonon väärin, ja dialogi ilmestyy uudelleen toista yritystä varten. Mikään tuossa käyttäjäkokemuksessa ei ole epätavallista, joten sen takana olevan koodin on hyväksyttävä useampi kuin yksi ehdokassalasana samalle tiedostolle, ja sen on tehtävä se turvallisesti, vuotamatta tilaa hylätystä yrityksestä seuraavaan

Mikset voi vain yrittää uudelleen samalla asiakirjaoliolla?

TPDFDocument-olion uudelleenkäyttö salasanayritysten yli ei toimi, koska epäonnistunut yritys on jo purkanut tuon olion sisäisesti sen sijaan, että jättäisi sen johonkin keskeytettyyn, jatkettavissa olevaan tilaan. Salatun PDF:n avaaminen tarkoittaa ristiviittaustaulun jäsentämistä, lukijan rakentamista taustalla olevan lähteen yli, ja salauskäsittelijän rakentamista mistä tahansa toimitetusta salasanasta, kaikki ennen kuin PDF Library for Delphi voi edes testata, onko tuo salasana oikea. Kun salasana osoittautuu vääräksi, asiakirjan sisäinen latausrutiini siivoaa lukijan, ristiviittaustaulun ja salauskäsittelijän osana epäonnistumista, täsmälleen kuten sen pitäisi, mikä tarkoittaa, ettei siellä ole mitään puoliksi rakennettua jäsennintä odottamassa korjattua salasanaa toisella kutsulla. Aja sama olio silti läpi toisen latausyrityksen, ja vikatila on juuri sellainen, joka on kurja debugata: virhe paljastuu sisäisestä tilasta, joka on rakennettu eri, jo epäonnistuneelle jäsennykselle, ilman mitään ilmeisesti osoittamassa takaisin salasanaan kolme kutsua ylävirtaan. PDF Library for Delphi välttää koko ongelmaluokan yrittämättä koskaan palauttaa asiakirjaoliota, kun se on kerran epäonnistunut avautumaan; jokainen yritys saa asiakirjan, joka ei ole koskaan nähnyt väärää salasanaa, lukija ja ristiviittaustaulu mukaan lukien

Miten OnPassword-takaisinkutsu pyytää seuraavaa salasanaa?

TPDFlibPasswordEvent on takaisinkutsutyyppi, jonka PDF Library for Delphi kutsuu TPDFlib.LoadFromFile-, LoadFromStream- ja LoadFromString-metodien kautta aina, kun juuri kokeiltu salasana osoittautuu vääräksi, ja se antaa käsittelijälle kolme asiaa: mikä yritys on menossa käynnistymään, Password-parametrin ylikirjoitettavaksi seuraavalla ehdokkaalla, ja Retry-lipun, joka oletusarvoistuu falseksi

TPDFlibPasswordEvent = procedure(Sender: TObject; AttemptNumber: Integer;
  var Password: WideString; var Retry: Boolean) of object;

property OnPassword: TPDFlibPasswordEvent read FOnPassword write FOnPassword;

Alkuperäiseen LoadFromFile-kutsuun välitetty salasana lasketaan yritykseksi yksi, joten ensimmäisellä kerralla, kun OnPassword laukeaa lainkaan, AttemptNumber saapuu arvolla 2. Jätä Retry asettamatta, ja lataus epäonnistuu puhtaasti LastErrorCode-arvolla 404; aseta se todeksi, ja PDF Library for Delphi yrittää uudelleen millä tahansa, mitä käsittelijä juuri kirjoitti Password-kenttään

Uudelleenyrityssilmukan sisällä: uusi TPDFDocument jokaiselle yritykselle

Sisäisesti PDF Library for Delphi vastaa oliokiertokysymykseen samalla tavalla LoadFromFile-, LoadFromStream- ja LoadFromString-metodeille: jokainen yritys, ensimmäinen mukaan lukien, rakentaa tuoreen TPDFDocument-olion, ajaa sen läpi täyden avaussekvenssin millä tahansa salasanalla, jota tuo yritys käyttää, ja pitää olion vain, jos salasana todentuu. Hylätyn yrityksen TPDFDocument vapautetaan välittömästi, viedessään mukanaan lukijansa, ristiviittaustaulunsa ja salauskäsittelijänsä, ja seuraava yritys alkaa alusta oliolla, jolla ei ole lainkaan historiaa

PDF Library for Delphi -vuokaavio salasanan uudelleenyrityssilmukasta, jossa jokainen yritys luo tuoreen TPDFDocumentin, hylätty yritys vapauttaa sen lukijan, xref-taulun ja salauskäsittelijän, ja OnPassword-callback päättää, jatketaanko uudella yrityksellä vai epäonnistuuko lataus koodilla LastErrorCode 404
Epäonnistunut kandidaatti ei koskaan selviä silmukasta hengissä: sen dokumentti vapautetaan, vaikka jäsennystila on vasta puolivalmis. Retry:n asettaminen kirjoittaa seuraavan kandidaatin Password-kenttään ja toistaa kutsun objektilla, joka ei ole koskaan nähnyt väärää salasanaa
// Yksinkertaistettu ote LoadFromFile-metodin sisältä: jokainen yritys saa
// asiakirjan, joka ei ole koskaan nähnyt aiemmin hylättyä salasanaa. FileName,
// AttemptNumber ja AttemptPassword tulevat ympäröivästä metodista.
Var
  Doc: TPDFDocument;
  LoadResult: TPLLoadResult;
  Success: Boolean;
Begin
  Success := False;
  Repeat
    Doc := TPDFDocument.Create;
    Doc.DecodeMode := FDefaultDecodeMode;
    Try
      LoadResult := Doc.LoadFromFile(FileName, AttemptPassword);
      Success := LoadResult = lrOkay;
      if Success then
      begin
        FDocs.Add(Doc);            // luovuta vahvistettu asiakirja
        Doc := nil;                 // kutsujan kokoelmaan; ohita alla oleva Free
      end;
    Finally
      Doc.Free;                     // hylätyn yrityksen lukija, xref-taulukko
    End;                            // ja salausobjekti puretaan juuri tässä
    if Success or (LoadResult <> lrWrongPassword) then
      Break;                        // onnistuminen tai muu kuin salasanavirhe: pysäytä
    Inc(AttemptNumber);
  Until not RequestPasswordRetry(AttemptNumber, AttemptPassword);
End;

Tuo Doc := nil -rivi juuri ennen Finally-lohkoa on koko oliokiertosopimus yhdessä lauseessa. Asiakirja, joka epäonnistuu, kantaa puoliksi rakennetun jäsennintilansa hautaan mukanaan, suunnitellusti, ja asiakirja, joka onnistuu, on ainoa koskaan lisätty FDocs:iin, kokoelmaan, jota TPDFlib pitää jokaiselle asiakirjalle, jonka kutsuja pitää auki. Mikään hylätystä yrityksestä ei ole näkyvissä uudelleenyrityssilmukan ulkopuolelta: ei puoliksi alustettu lukija, ei vanhentunut sivumäärä, ei väärästä avaimesta rakennettu salauskäsittelijä

Kuinka monta kertaa PDF Library for Delphi yrittää väärää salasanaa uudelleen?

PDF Library for Delphi sallii yhteensä kuusitoista yritystä yhtä LoadFromFile-, LoadFromStream- tai LoadFromString-kutsua vastaan, laskien itse kutsuun välitetyn salasanan yritykseksi yksi. OnPassword laukeaa vain koskaan yrityksille kaksi kuuteentoista, mikä rajaa takaisinkutsun viiteentoista kutsuun; pyydä seitsemättätoista yritystä, ja PDF Library for Delphi kieltäytyy jopa kutsumatta käsittelijää. Jätä Retry oletusarvoonsa false missä tahansa kohdassa, tai kuluta kaikki kuusitoista yritystä ilman oikeaa salasanaa, ja LoadFromFile palauttaa 0:n LastErrorCode-arvolla 404, PDF Library for Delphi:n koodilla hylätylle salasanalle. Katto on olemassa muistakin syistä kuin siisteydestä: rajoittamaton uudelleenyrityssilmukka on helppo tapa muuttaa yksi väärin kirjoitettu salasana vahingossa tapahtuvaksi palvelunestohyökkäykseksi mitä tahansa säiettä vastaan, joka ajaa latausta, varsinkin heti kun käsittelijä on kytketty johonkin automatisoituun, kuten listaan aiemmin nähtyjä salasanoja, ihmisen klikkaillessa läpi dialogia sen sijaan. PDF Library for Delphi kunnioittaa myös Abort-kutsua, joka tehdään TPDFlib-instanssille käsittelijän sisältä, koska Sender saapuu samana oliona, hyödyllinen Cancel-painikkeen takana salasanadialogissa, ja pysäyttää uudelleenyrityssilmukan seuraavassa tarkistuksessa riippumatta siitä, mihin Retry oli asetettu. Lataus, joka epäonnistuu muusta syystä kuin väärästä salasanasta, vioittuneesta ristiviittaustaulusta esimerkiksi, ei koskaan astu uudelleenyrityssilmukkaan lainkaan: PDF Library for Delphi raportoi LastErrorCode 401:n ja pysähtyy ensimmäisen yrityksen jälkeen, koska mikään määrä salasana-arvauksia ei korjaa rakenteellisesti rikkinäistä tiedostoa

PDF Library for Delphi: yritysura yhdestä kuuteentoista, jossa OnPassword laukeaa toisesta yrityksestä alkaen, seitsemästoista yritys hylätään kutsumatta käsittelijää, ja erilliset lopputulokset ovat onnistuminen, LastErrorCode 404 ja LastErrorCode 401
Vain itse latauskutsulle annettu salasana lasketaan ensimmäiseksi yritykseksi, eikä callback suoritu koskaan yli viiteätoista kertaa per lataus. Rakenteeltaan rikkonainen tiedosto ohittaa silmukan kokonaan ja raportoi 401 yhden yrityksen jälkeen

Toimiiko uudelleenyrityssilmukka samalla tavalla tiedostoille, virroille ja merkkijonoille?

OnPassword-takaisinkutsu ja kuudentoista yrityksen katto käyttäytyvät identtisesti LoadFromFile-, LoadFromStream- ja LoadFromString-metodeissa, vaikka nämä kolme sisääntulopistettä pitävät lähteestään kiinni eri tavoin yritysten välillä. Tiedostopolku on halpa vierailla uudelleen, koska jokainen yritys yksinkertaisesti avaa nimetyn tiedoston uudelleen, ja merkkijonolähde jo istuu muistissa kutsujan omana kopiona, joten kumpikaan ei tarvitse mitään apua kutsujalta yritysten välillä. Kutsujan toimittama virta on se yksi tapaus, joka kannattaa pysähtyä miettimään: LoadFromStream hakeutuu tuon virran takaisin nollapositioon ja kopioi sen sisäisesti ennen ensimmäistä jäsennysyritystä, joten jokainen sitä seuraava yritys, ja sen takana oleva juuri rakennettu TPDFDocument, toistaa tuosta sisäisestä kopiosta eikä siitä, mihin epäonnistunut jäsennys jätti virran position. Anna PDF Library for Delphi:lle TFileStream tai TMemoryStream salasanasuojatulle asiakirjalle, eikä sitä tarvitse kelata takaisin uudelleenyritysten välillä; PDF Library for Delphi ottaa jo huomioon position, jonka ensimmäinen, epäonnistunut yritys on saattanut siirtää

Salasanan uudelleenyrityksen sovittaminen asiakirjan vastaanottonäyttöön

Asiakirjan vastaanottotyönkulku on luonnollinen koti tälle takaisinkutsulle, koska se on juuri sellainen ongelmamuoto, jota varten OnPassword rakennettiin: tiedosto saapuu sovelluksen ulkopuolelta, sen salasanaa ei tiedetä varmuudella etukäteen, ja ehdokkaita toimittavan henkilön on saatava useampi kuin yksi arvaus ilman että ympäröivä koodi kirjoittaa oman uudelleenyrityssilmukkansa LoadFromFile:n ympärille

procedure TIntakeForm.SupplyPassword(Sender: TObject; AttemptNumber: Integer;
  var Password: WideString; var Retry: Boolean);
var
  Typed: string;
begin
  // AttemptNumber laskee alkaen luvusta 2: jo kokeiltu salasana oli yritys 1.
  Typed := '';
  Retry := InputQuery('Password required',
    Format('Attempt %d of 16 - enter the document password', [AttemptNumber]), Typed);
  if Retry then
    Password := Typed;
  // Retry on False, kun käyttäjä peruuttaa, jolloin
  // LastErrorCode jää arvoon 404 kutsujan raportoitavaksi.
end;
procedure TIntakeForm.LoadInboundDocument;
var
  Lib: TPDFlib;
begin
  Lib := TPDFlib.Create;
  try
    Lib.OnPassword := SupplyPassword;
    if Lib.LoadFromFile('inbound-invoice.pdf', '') = 1 then
      RegisterIntakeDocument(Lib)        // vain vahvistettu asiakirja pääsee tänne asti
    else
      LogRejectedIntake('inbound-invoice.pdf', Lib.LastErrorCode);
  finally
    Lib.Free;
  end;
end;

RegisterIntakeDocument vastaanottaa Lib:n vain koskaan sen jälkeen, kun LoadFromFile on palauttanut 1:n, mikä tarkoittaa, että jokin salasana tuossa vaihdossa todella todentui tiedoston salauskäsittelijää vasten; hylätty yritys ei koskaan saavuta tuota riviä, eikä myöskään puoliksi avoin asiakirja. Se, mitä seuraavaksi tulee, heti kun tällainen asiakirja on vahvistettu auki, kannattaa vielä toinen katsaus sen suojausasetuksiin sen sijaan, että oletettaisiin salasanan, joka toimi, olevan koko tietoturvatarina: asiakirjan /Encrypt-sanakirjan todellisen ilmoituksen tarkastaminen käsittelee algoritmin, revision ja käyttöoikeusbittien lukemisen, jotka PDF Library for Delphi paljastaa heti, kun tällainen tiedosto latautuu

Salasanan uudelleenyritys on myös kapea esimerkki laajemmasta kurista, jota PDF Library for Delphi soveltaa koko jäsennyskerroksessaan: tiedosto, joka ei ole vielä todistanut itseään, ei saa epäilyksen etua, olipa kysymys siitä, mikä salasana avaa sen, tai siitä, valehteleeko sen sisällä oleva pituuskenttä puskurin koosta, jota se tarvitsee. Pascal-PDF-jäsentimen kovettaminen haitallisia tiedostoja vastaan käsittelee tuon kurin toisen puoliskon, dekooderit, jotka kohtelevat jokaista fonttiohjelmaa ja kuvavirtaa saapuvassa PDF:ssä vihamielisenä syötteenä eikä hyvin muodostettuna asiakirjana, joka vain unohti salasanansa

OnPassword ja sen takana oleva uudelleenyrityssilmukka ovat osa vakiomuotoista PDF Library for Delphi PDF-kirjastoa Delphille ja C++Builderille, saatavilla missä tahansa, missä LoadFromFile, LoadFromStream tai LoadFromString jo ovat, ilman erillistä moduulia tai lisenssitasoa vaadittuna asiakirjalle, joka vain tarvitsee toisen arvauksen salasanaansa