Техническа статия

Повторен опит за пароли на криптиран PDF в Delphi с PDFlibPas

Повторният опит за грешна парола в PDFlibPas отхвърля неуспешния TPDFDocument и създава изцяло нов обект за следващия опит чрез OnPassword (TPDFlibPasswordEvent), който работи до шестнадесет опита. Така всяка кандидат-парола се проверява в чист жизнен цикъл без остатъчно състояние от предишния отказ

Защо не можете просто да опитате отново със същия обект на документа?

Повторното използване на TPDFDocument не работи, защото неуспешното отваряне вече е освободило вътрешните reader, cross-reference table и crypt handler. След грешна парола няма паузирано състояние, което да чака нова стойност, затова всеки опит започва с нов документ и избягва трудно проследими остатъци от неуспешния parse

Как callback-ът OnPassword заявява следващата парола?

TPDFlibPasswordEvent се извиква от TPDFlib.LoadFromFile, LoadFromStream и LoadFromString при грешна парола и получава номера на предстоящия опит, параметър Password за следващия кандидат и флага Retry, който по подразбиране е false

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

property OnPassword: TPDFlibPasswordEvent read FOnPassword write FOnPassword;

Паролата от първоначалното извикване на LoadFromFile е опит едно, затова първото извикване на OnPassword получава AttemptNumber 2. Ако Retry остане false, зареждането завършва с LastErrorCode 404, а при true PDFlibPas използва стойността, записана в Password

В цикъла за повторен опит: нов TPDFDocument при всеки опит

При LoadFromFile, LoadFromStream и LoadFromString PDFlibPas създава нов TPDFDocument при всеки опит, изпълнява пълната последователност за отваряне и запазва обекта само ако паролата е потвърдена. Отхвърленият документ се освобождава веднага заедно с неговите reader, cross-reference table и crypt handler

// Simplified excerpt from inside LoadFromFile: every attempt gets a
// document that has never seen a previously rejected password. FileName,
// AttemptNumber and AttemptPassword come from the enclosing method.
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);            // hand the verified document to the
        Doc := nil;                 // caller's collection; skip the Free below
      end;
    Finally
      Doc.Free;                     // a rejected attempt's reader, xref table
    End;                            // and crypt handler are torn down right here
    if Success or (LoadResult <> lrWrongPassword) then
      Break;                        // success, or a non-password failure: stop
    Inc(AttemptNumber);
  Until not RequestPasswordRetry(AttemptNumber, AttemptPassword);
End;

Редът Doc := nil непосредствено преди блока Finally е целият договор за жизнения цикъл. Само потвърденият документ се добавя към FDocs, а отхвърленият не оставя външно видим reader, page count или crypt handler

Колко пъти PDFlibPas повтаря грешна парола?

PDFlibPas разрешава общо шестнадесет опита за едно извикване на LoadFromFile, LoadFromStream или LoadFromString, като първоначалната парола е опит едно. OnPassword се извиква най-много петнадесет пъти, а седемнадесетият опит не се допуска. При изчерпване или отказ LoadFromFile връща 0 и LastErrorCode е 404. Abort от обработчика прекратява цикъла, докато повредена cross-reference table спира още след първия опит с LastErrorCode 401

Работи ли цикълът еднакво за файлове, потоци и низове?

OnPassword и ограничението от шестнадесет опита са еднакви за LoadFromFile, LoadFromStream и LoadFromString. Пътят към файл се отваря отново, низът вече е в паметта, а LoadFromStream копира входа преди първия parse и го връща към позиция нула за всеки следващ TPDFDocument

Вписване на повторния опит за парола в екран за приемане на документи

Този callback естествено принадлежи към работния поток за приемане на документи: файлът идва отвън, паролата не е известна предварително и операторът трябва да може да предложи няколко кандидата без собствен външен цикъл около LoadFromFile

procedure TIntakeForm.SupplyPassword(Sender: TObject; AttemptNumber: Integer;
  var Password: WideString; var Retry: Boolean);
var
  Typed: string;
begin
  // AttemptNumber counts from 2: the password already tried was attempt 1.
  Typed := '';
  Retry := InputQuery('Password required',
    Format('Attempt %d of 16 - enter the document password', [AttemptNumber]), Typed);
  if Retry then
    Password := Typed;
  // Retry is False when the operator cancels, which leaves
  // LastErrorCode at 404 for the caller to report.
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)        // only a verified document reaches here
    else
      LogRejectedIntake('inbound-invoice.pdf', Lib.LastErrorCode);
  finally
    Lib.Free;
  end;
end;

RegisterIntakeDocument получава Lib само след като LoadFromFile върне 1, тоест паролата действително е потвърдена от crypt handler. За следваща проверка на защитата може да се прочете /Encrypt dictionary чрез одит на алгоритъма, ревизията и permission bits Свързана статия

Този механизъм е част от по-широката дисциплина на parser слоя: файлът не получава доверие, преди да се докаже безопасен, независимо дали се проверява парола или подозрително поле за дължина на буфер Свързана статия

OnPassword и цикълът за повторен опит са част от стандартната PDF библиотека PDFlibPas за Delphi и C++Builder и не изискват отделен модул или лиценз Свързана статия