1 % Copyright (C) 1996-1998 Geoffrey Keating.
2 % Copyright (C) 2001 Artifex Software, Inc.
3 % This file may be freely distributed with or without modifications,
4 % so long as modified versions are marked as such and copyright notices are
7 % $Id: pdf_sec.ps,v 1.15 2004/03/12 01:55:58 dan Exp $
8 % Implementation of security hooks for PDF reader.
10 % This file contains the procedures that have to take encryption into
11 % account when reading a PDF file. It replaces the stub version of this
12 % file that is shipped with GhostScript. It requires GhostScript 7.01
15 % Documentation for using this file is available at
16 % http://www.ozemail.com.au/%7Egeoffk/pdfencrypt/
18 % Modified by Alex Cherepanov to work with GS 6.60 and higher.
19 % New versions of GS require explicit checks for /true , /false, and /null
20 % in .decpdfrun . This fix is backward-compatible.
22 % Modified by Raph Levien and Ralph Giles to use the new C
23 % implementations of md5 and arcfour in ghostscript 7.01, and to
24 % be compatible with PDF 1.4 128-bit encryption.
26 /.setlanguagelevel where { pop 2 .setlanguagelevel } if
27 .currentglobal true .setglobal
28 /pdfdict where { pop } { /pdfdict 100 dict def } ifelse
31 % Older ghostscript versions do not have .pdftoken, so we use 'token' instead.
32 /.pdftoken where { pop } { /.pdftoken /token load def } ifelse
34 % take a stream and arc4 decrypt it.
35 % <stream> <key> arc4decodefilter <stream>
39 currentdict end /ArcfourDecode filter
45 currentdict end /AESDecode filter
48 % <ciphertext> <key> arc4decode <plaintext>
50 %(key: ) print dup == (ct: ) print 1 index ==
54 1 index length string 3 1 roll arc4decodefilter exch readstring pop
62 1 index length string 3 1 roll
64 % If our second argument is a dictionary, it's the full set
65 % of decoding options (including the key); pass it directly
66 % to the AESDecode filter. Otherwise, it's just the key, so
67 % call aesdecodefilter to construct the dictionary.
68 dup type /dicttype eq { /AESDecode filter } { aesdecodefilter } ifelse
75 16 string dup /MD5Encode filter dup 4 3 roll writestring closefile
79 md5 0 pdf_key_length getinterval
84 <28bf4e5e4e758a41 64004e56fffa0108
85 2e2e00b6d0683e80 2f0ca9fe6453697a>
88 % Pad a key out to 32 bytes.
89 /pdf_pad_key { % <key> pdf_pad_key <padded key>
90 dup length 32 gt { 0 32 getinterval } if
92 0 32 3 index length sub getinterval
96 /pdf_xorbytes { % <iter-num> <key> pdf_xorbytes <xored-key>
98 exch 1 sub 0 1 3 2 roll {
99 % <iter-num> <key> <new-key> <byte-num>
100 dup 3 index exch get 4 index xor
101 % <iter-num> <key> <new-key> <byte-num> <byte>
107 % Get length of encryption key in bytes
108 /pdf_key_length { % pdf_key_length <key_length>
109 Trailer /Encrypt oget /Length knownoget { -3 bitshift } { 5 } ifelse
113 /pdf_compute_encryption_key { % <password> pdf_compute_encryption_key <key>
118 Trailer /Encrypt oget dup /O oget
119 % <padded-key> <encrypt> <O>
122 exch /P oget 4 string exch
123 2 copy 255 and 0 exch put
124 2 copy -8 bitshift 255 and 1 exch put
125 2 copy -16 bitshift 255 and 2 exch put
126 2 copy -24 bitshift 255 and 3 exch put pop
127 % <padded-key> <O> <P>
130 Trailer /ID knownoget { 0 oget } {
132 ( **** ID key in the trailer is required for encrypted files.\n) pdfformaterror
134 3 { concatstrings } repeat
135 % We will finish step 5 after possibly including step 6.
137 % The following only executed for /R equal to 3 or more
138 Trailer /Encrypt oget dup /R oget dup 3 ge {
140 % Step 6. If EncryptMetadata is false, pass 0xFFFFFFFF to md5 function
141 % The PDF 1.5 Spec says that EncryptMetadata is an undocumented
142 % feature of PDF 1.4. That implies that this piece of logic should
143 % be executed if R >= 3. However testing with Acrobat 5.0 and 6.0 shows
144 % that this step is not executed if R equal to 3. Thus we have a test for
147 /EncryptMetadata knownoget % Get EncryptMetadata (if present)
148 not { true } if % Default is true
149 not { % If EncryptMetadata is false
150 <ff ff ff ff> concatstrings % Add 0xFFFFFFFF to working string
153 pop % Remove Encrypt dict
155 md5_trunk % Finish step 5 and 6.
157 % Step 7. Executed as part of step 6
158 % Step 8. (This step is defintely a part of PDF 1.4.)
159 50 { md5_trunk } repeat
161 pop pop md5_trunk % Remove R, Encrypt dict, finish step 5
164 % Step 9 - Done in md5_trunk.
168 /pdf_gen_user_password_R2 { % <filekey> pdf_gen_user_password_R2 <U>
171 pdf_padding_string exch arc4decode
175 /pdf_gen_user_password_R3 { % <filekey> pdf_gen_user_password_R3 <U>
181 Trailer /ID knownoget { 0 oget } {
183 ( **** ID key in the trailer is required for encrypted files.\n) pdfformaterror
192 2 index pdf_xorbytes arc4decode
198 /pdf_gen_user_password { % <password> pdf_gen_user_password <filekey> <U>
199 % common Step 1 of Algorithms 3.4 and 3.5.
200 pdf_compute_encryption_key dup
202 Trailer /Encrypt oget
205 pop pdf_gen_user_password_R2
208 pop pdf_gen_user_password_R3
210 dup 4 eq { % 4 uses the algorithm as 3
211 pop pdf_gen_user_password_R3
213 ( **** This file uses an unknown standard security handler revision: )
214 exch =string cvs concatstrings pdfformaterror printProducer
215 /pdf_check_user_password cvx /undefined signalerror
222 /pdf_check_user_password { % <password> pdf_check_user_password <filekey> true
223 % <password> pdf_check_user_password false
224 pdf_gen_user_password
226 Trailer /Encrypt oget /U oget
228 0 2 index length getinterval eq {
235 % Compute an owner key, ie the result of step 4 of Algorithm 3.3
236 /pdf_owner_key % <password> pdf_owner_key <owner-key>
244 % 3.3 Step 3. Only executed for /R equal to 3 or more
245 Trailer /Encrypt oget /R oget 3 ge {
246 50 { md5_trunk } repeat
249 % Step 4 - Done in md5_trunk.
253 /pdf_check_owner_password { % <password> pdf_check_owner_password <filekey> true
254 % <password> pdf_check_owner_password false
259 Trailer /Encrypt oget dup /O oget 2 index arc4decode
260 % <encryption-key> <encrypt-dict> <decrypted-O>
262 % Step 3. Only executed for /R equal to 3 or more
265 2 index pdf_xorbytes arc4decode
271 pdf_check_user_password
274 % Process the encryption information in the Trailer.
275 /pdf_process_Encrypt {
276 Trailer /Encrypt oget
277 /Filter oget /Standard eq not {
278 ( **** This file uses an unknown security handler.\n) pdfformaterror
280 /pdf_process_Encrypt cvx /undefined signalerror
282 () pdf_check_user_password
287 pop PDFPassword pdf_check_user_password
291 PDFPassword pdf_check_owner_password
295 ( **** Password did not work.\n) pdfformaterror
297 /pdf_process_Encrypt cvx /invalidfileaccess signalerror
301 ( **** This file requires a password for access.\n) pdfformaterror
303 /pdf_process_Encrypt cvx /invalidfileaccess signalerror
307 % Trailer /Encrypt oget /P oget 4 and 0 eq #? and
308 % { ( ****This owner of this file has requested you do not print it.\n)
309 % pdfformaterror printProducer
310 % /pdf_process_Encrypt cvx /invalidfileaccess signalerror
315 % Calculate the key used to decrypt an object (to pass to .decpdfrun or
316 % put into a stream dictionary).
317 /computeobjkey % <object#> <generation#> computeobjkey <keystring>
319 Trailer /Encrypt oget /V oget 5 eq {
320 % Encrypt version 5 doesn't use object keys; everything is
321 % encrypted with the file key.
325 FileKey length 5 add string
326 dup 0 FileKey putinterval
328 % stack: gen# string obj#
329 2 copy 255 and FileKey length exch put
330 2 copy -8 bitshift 255 and FileKey length 1 add exch put
331 2 copy -16 bitshift 255 and FileKey length 2 add exch put
333 2 copy 255 and FileKey length 3 add exch put
334 2 copy -8 bitshift 255 and FileKey length 4 add exch put
336 % this step is for the AES cipher only
337 Trailer /Encrypt oget
338 dup /StmF knownoget {
340 exch oget /CFM oget /AESV2 eq {
349 md5 0 FileKey length 5 add 2 index length .min getinterval
353 % As .pdfrun, but decrypt strings with key <key>.
354 /.decpdfrun % <file> <keystring> <opdict> .decpdfrun -
355 { % Construct a procedure with the file, opdict and key bound into it.
356 2 index cvlit mark mark 5 2 roll
357 { .pdftoken not { (%%EOF) cvn cvx } if
359 { PDFDEBUG { dup == flush } if
362 { exch pop exch pop exec
374 { ( **** Unknown operator: )
375 exch =string cvs concatstrings (\n) concatstrings
386 { exch pop PDFDEBUG { dup ==only ( ) print flush } if
387 dup type /stringtype eq
389 % Check if we have encrypted strings R=4 allows for
390 % selection of encryption on streams and strings
391 Trailer /Encrypt oget % Get encryption dictionary
392 dup /R oget 4 lt % only 4 has selectable
393 { % R < 4 --> encrypted strings
394 pop 1 index arc4decode % Decrypt string
395 PDFDEBUG { (%Decrypted: ) print dup == flush } if
397 /StrF knownoget % Get StrF (if present)
398 { % If StrF is present ...
399 dup /Identity eq not % Check if StrF != Identity
401 { Trailer /Encrypt oget /CF knownoget {
402 /StdCF oget /CFM oget
403 dup /AESV2 eq exch /AESV3 eq or
406 } ifelse { % Decrypt string
412 { 1 index arc4decode }
413 ifelse % If StrF != StdCF
414 PDFDEBUG { (%Decrypted: ) print dup //== exec flush } if
417 ifelse % If StrF != identity
419 if % If StrF is known
421 ifelse % Ifelse R < 4
428 aload pop .packtomark cvx
429 /loop cvx 2 packedarray cvx
430 { stopped /PDFsource } aload pop
432 { store { stop } if } aload pop .packtomark cvx
433 /PDFsource 3 -1 roll store exec
436 % Run the code to resolve an object reference.
438 { /FileKey where % Check if the file is encrypted
439 { pop % File is encrypted
440 2 copy computeobjkey dup 4 1 roll
441 PDFfile exch resolveopdict .decpdfrun
443 % stack: object object key object object
444 { % Use loop to provide an exitable context.
445 xcheck exch type /dicttype eq and % Check if executable dictionary
446 not { % If object is not ...
447 pop pop % ignore object
448 exit % Exit 'loop' context
449 } if % If not possible stream
450 % Starting with PDF 1.4 (R = 3), there are some extra features
451 % which control encryption of streams. The EncryptMetadata entry
452 % in the Encrypt dict controls the encryption of metadata streams.
453 Trailer /Encrypt oget % Get encryption dictionary
454 dup /R oget dup 3 lt % Only PDF 1.4 and higher has options
455 { % R < 3 --> all streams encrypted
456 pop pop /StreamKey exch put % Insert StreamKey in dictionary
457 exit % Exit 'loop' context
459 % Check EncryptMeta. stack: object object key Encrypt R
460 exch dup /EncryptMetadata knownoget % Get EncryptMetadata (if present)
461 not { //true } if % If not present default = true
462 not % Check if EncryptMetadata = false
463 { % if false we need to check the stream type
464 3 index /Type knownoget % Get stream type (if present)
465 not { //null } if % If type not present use fake name
466 /Metadata eq % Check if the type is Metadata
467 { pop pop pop pop % Type == Metadata --> no encryption
468 exit % Exit 'loop' context
471 % PDF 1.5 encryption (R == 4) has selectable encryption handlers. If
472 % this is not PDF 1.5 encryption (R < 4) then we are done checking and
473 % we need to decrypt the stream. stack: object object key R Encrypt
474 exch 4 lt % Check for less than PDF 1.5
475 { pop /StreamKey exch put % Insert StreamKey in dictionary
476 exit % Exit 'loop' context
478 % Check if the stream encryption handler (StmF) == Identity.
480 Trailer /Encrypt oget /CF knownoget {
481 /StdCF oget /CFM oget
482 (Encrypt StmF is StdCF with CFM ) print =
485 /StmF knownoget % Get StmF (if present)
486 not { /Identity } if % If StmF not present default = Identity
487 /Identity eq % Check if StmF == Identity
488 { pop pop % Identity --> no encryption
489 exit % Exit 'loop' context
491 % If we get here then we need to decrypt the stream.
492 /StreamKey exch put % Insert StreamKey into dictionary
493 exit % Exit 'loop' context, never loop
494 } loop % End of loop exitable context
495 } { % Else file is not encrypted
496 PDFfile resolveopdict .pdfrun
497 } ifelse % Ifelse encrypted
500 % Prefix a decryption filter to a stream if needed.
501 % Stack: readdata? dict parms file/string filternames
502 % (both before and after).
504 { 3 index /StreamKey known % Check if the file is encrypted
507 % Stack: readdata? dict parms filternames file/string
508 3 index /StreamKey get
509 Trailer /Encrypt oget
511 { % stack: key Encrypt StmF
513 exch oget /CFM oget % stack: key StmF-CFM
514 dup /AESV2 eq exch /AESV3 eq or
515 } { pop //false } ifelse
516 { aesdecodefilter } % install the requested filter
520 { pop arc4decodefilter } % fallback for no StmF