PDF Library for Delphiは、passwordの代わりにraw file encryption keyから暗号化PDFを開けます。DAOpenFileWithEncryptionKeyはkeyをhexadecimal textとして受け取り、encryption dictionaryに保存されたverifierと照合し、read-onlyのDirect Access handleを返します。DAOpenFromStreamWithEncryptionKeyはcallerが所有するTStreamに対して同じことを行います。どちらもv3.496.0で追加されました
この状況は狭いものですが、実際にあります。forensicsの案件ではmemory imageから復元したkeyだけが渡され、passwordはありません。大規模なarchival runでは、元のDRM systemが何年も前にpasswordの発行を止めたため、1万件のdocumentのfile keyがescrow databaseに残っています。廃止したrights-management productから移行すると、key materialだけが残り、それ以外はありません。いずれも手元のcredentialはkey derivationの入力ではなく出力であり、APIのどのpassword parameterにも渡せません
file encryption keyがpasswordではない理由
passwordとfile encryption keyは、PDF standard security handler(ISO 32000-1 §7.6.3)のkey derivationを挟んで反対側にあります。handlerはpasswordを受け取り、/O、/P、file ID、revision固有のhashと混ぜてfile keyを生成します。file keyをpassword slotへ入れると、別の値へhashされて意味を失います。そのためDAOpenFileのflagではなく、専用のentry pointが必要です
raw keyを注入できる場所はrevisionで決まっています。revision 2から4では、file key、object number、generation number、そしてAESV2ではAES saltから、objectごとに異なるkeyをなおderiveします。つまりfile keyを持っていてもobject-level derivationを飛ばせません。revision 5から7では、per-object stepなしで32-byteのfile keyをAES-256に直接使います。両者に共通する層はfile keyそのものなので、PDF Library for Delphiが外部から供給されたkeyを受け入れる場所もそこだけです。passwordをまだ持っているなら通常の経路を使い、誤った初回入力をpassword retry lifecycleで処理するほうがよいでしょう。raw-key entryはpassword経路に残っている便利な機能を意図的にいくつか手放しているためです
DAOpenFileWithEncryptionKeyが受け付ける入力
prefixなし、whitespaceなし、偶数長のASCII hexだけで、decode後のbyte数はencryption revisionと正確に一致しなければなりません。revision 2から4では、encryption dictionaryの/Lengthから期待長を求めます。8 bitの倍数で5から16 byteの範囲に入り、/Lengthがなければ40 bitがdefaultです。revision 5から7はちょうど32 byteで、交渉はありません。0x prefix、奇数桁、64 hex character超過、Options内の未知のbit、またはそもそも暗号化されていないdocumentはすべて同じ結果になります。handleは0で、LastErrorCodeはPDFLIB_ERROR_RAW_ENCRYPTION_KEY_INVALID、つまり425です。この厳密さが目的です。whitespaceをtrimして短い入力をzero-padする寛容なparserは、切れたclipboard pasteをcredentialとして受け入れ、もっと分かりにくい場所で失敗させます
Var
Lib: TPDFlib;
FileHandle, PageRef: Integer;
Begin
Lib:= TPDFlib.Create;
Try
// KeyHexはAES-128 R4なら32 hex characters、AES-256 R6なら64
FileHandle:= Lib.DAOpenFileWithEncryptionKey(CaseFile, KeyHex);
If FileHandle= 0 Then
Raise Exception.CreateFmt('raw key refused (error %d)', [Lib.LastErrorCode]);
Try
PageRef:= Lib.DAFindPage(FileHandle, 1);
WriteLn(Lib.DAExtractPageText(FileHandle, PageRef, 0));
Finally
Lib.DACloseFile(FileHandle);
End;
Finally
Lib.Free;
End;
End;
その他のfailure codeは区別されたままなので、batch jobはoperator errorとevidence problemを判別できます。fileが存在しなければ411、read用に開けなければ401、cross-reference structureが壊れていれば409です。key関連は意図的にすべて425へ集約されます。raw-key entry pointがkeyのどの部分が間違ったかを報告すると、oracleになってしまうためです
verificationが実際に証明するもの
PDF Library for Delphiは、encryption dictionaryがすでに持つverifierを使って、供給されたkeyがこのdocumentに属することを証明します。checkの内容はrevisionで異なります。revision 2では32-byteのstandard padding stringをRC4で暗号化し直し、全32 byteを/Uと比較します。revision 3と4ではpaddingをfile IDと合わせてhashし、RC4 passとXORから得た19回のroundを実行し、/Uの先頭16 byteを比較します。revision 5から7では16-byteの/Perms stringをzero IVでdecryptし、4つを独立に同時確認します。little-endianのpermission wordと/P、5から8 byte位置の4つのFF byte、TまたはFとしてのencrypt-metadata flag、10から12 byte位置のadb markerです
verifierが存在するのに一致しなければ、openは無条件に拒否されます。これは機能全体が依存する保証なので、明確にしておく価値があります。verificationが証明しないことも覚えておいてください。keyがこのfileをdecryptできることは示しますが、誰かがあなたに使用を許可したことは示しません。/Permsから復元したpermission wordはkeyについての証拠であってgrantではありません。documentが実際に何を許可すると主張しているかを知りたい場合は、encryptionとpermissionsのaudit passという別の仕事になります。非ASCII AES-256 passwordのSASLprep処理のようなpassword側のnormalization問題も、ここではstringがhashに到達しないため発生しません
PDF_RAW_KEY_ALLOW_UNVERIFIEDが適用される場合
PDF_RAW_KEY_ALLOW_UNVERIFIEDが対象にするのは1つの状況だけです。/Permsが欠けているか16 byteでない、または/Uが比較には短すぎるため、documentが使用可能なverifierを持たない場合です。失敗する証拠を上書きすることはできません。/Perms内のhex digitを1つ壊し、optionを設定して正しいkeyを渡しても、PDF Library for Delphiは0と425を返します。完全なverifierに対して32 byteのzero keyをoption付きで渡しても同じです。optionが緩めるのは証明の不在だけで、矛盾を緩めることはありません。recovery openとverified openは異なる認識状態なので、return valueへ折り込まず別々に報告します。DAGetEncryptionKeyValidationはopen handleを受け、次の3つのconstantのいずれかを返します
PDF_RAW_KEY_VALIDATION_VERIFIED(1)はverifierが存在して一致したことを意味しますPDF_RAW_KEY_VALIDATION_UNVERIFIED(2)はverifierを評価できず、callerが明示的にそのpolicyを要求したためにだけkeyを受け入れたことを意味しますPDF_RAW_KEY_VALIDATION_NONE(0)は通常のpassword-opened handleが報告する値です
FileHandle:= Lib.DAOpenFileWithEncryptionKey(CaseFile, KeyHex);
If (FileHandle= 0)And
(Lib.LastErrorCode= PDFLIB_ERROR_RAW_ENCRYPTION_KEY_INVALID) Then
// このfileにverifierがない場合は、明示的なrecovery policyでretryする
FileHandle:= Lib.DAOpenFileWithEncryptionKey(CaseFile, KeyHex,
PDF_RAW_KEY_ALLOW_UNVERIFIED);
If FileHandle<> 0 Then
Begin
Case Lib.DAGetEncryptionKeyValidation(FileHandle) Of
PDF_RAW_KEY_VALIDATION_VERIFIED:
Chain.Note('key verified against the encryption dictionary');
PDF_RAW_KEY_VALIDATION_UNVERIFIED:
Chain.Note('no verifier available: extraction is unattested');
End;
End;
構造上read-onlyで、streamを所有するのは誰か
raw-key file entryは常にfmOpenRead or fmShareDenyWriteでsourceを開き、Direct Access chain全体をread-onlyとしてマークします。そのためDAAppendFileはin-place writeを拒否し、incremental updateを試みず2を返します。これはpolicyとしてhandleを説得して変えられるものではなく、fileがparseされる前のconstructorで設定されます。evidence作業で欲しい性質は、handleを閉じた後のsource byteがbyte-identicalであることです。regression suiteはAES-128 revision 4 fixtureとAES-256 revision 6 fixtureの両方でまさにそれをassertします。stream entryもwriteに対して同じ動作をし、もう1つのruleを加えます。DAOpenFromStreamWithEncryptionKeyはownershipを取らないため、DACloseFile後もあなたのTStreamは生きたままで、自分でfreeします
Source:= TMemoryStream.Create;
Try
Source.LoadFromFile(ArchivePath);
Source.Position:= 0;
FileHandle:= Lib.DAOpenFromStreamWithEncryptionKey(Source, KeyHex);
If FileHandle<> 0 Then
Try
// 2 = read-only handle。evidenceにはappendせず、別の場所へexportする
Assert(Lib.DAAppendFile(FileHandle)= 2);
Harvest(Lib, FileHandle);
Finally
Lib.DACloseFile(FileHandle);
End;
Finally
Source.Free; // handleはこのstreamを所有していない
End;
key hygieneとDLLがexportするもの
chainを閉じるとfile key、password cache、derived object keyが上書きされ、decode済みkeyもopenのentry point自身のFinally blockで、openが成功したかどうかにかかわらずwipeされます。ここには実際のdebugging時間を消費した細部があります。生存させる必要があるcopyは、代入ではなくSetLengthとMoveで明示的にcloneします。Delphiで1つのAnsiStringを別のものへ代入すると、copy-on-writeの下で両方の名前が1つのbufferを共有します。caller側をwipeするとcrypt handlerがまだ使っているkeyまでzeroになり、stack traceでは説明できない理由でdocumentがgarbageへdecryptされます。DLL boundaryを越えるのはfile-based entry pointだけで、wide formとANSI form、およびvalidation status accessorです。TStream variantはDelphi-onlyのままです。Delphi object lifetimeとreference semanticsに依存し、flat C ABIで正直に表現できないためです。recovery toolingがDLL clientなら、temporary fileへstageし、keyに適用するものと同じcontrolsの下で削除する計画にしてください
hex keyはdocument passwordに与えるものと同じ取り扱い規則を持つcredential materialとして扱い、chain of custodyが作るlogにはvalidation statusを残してください。後から読む人がverified extractionとunattested extractionを区別できるようにするためです。forensics、大規模archival、DRM migration向けにDelphi PDF componentを評価しているなら、raw-key entry point、read-only guarantee、encryption audit surfaceはすべて同じlibraryの一部です。全機能一覧はPDF Library for Delphi製品ページで確認できます