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
42 % <ciphertext> <key> arc4decode <plaintext>
44 %(key: ) print dup == (ct: ) print 1 index ==
48 1 index length string 3 1 roll arc4decodefilter exch readstring pop
53 16 string dup /MD5Encode filter dup 4 3 roll writestring closefile
57 md5 0 pdf_key_length getinterval
62 <28bf4e5e4e758a41 64004e56fffa0108
63 2e2e00b6d0683e80 2f0ca9fe6453697a>
66 % Pad a key out to 32 bytes.
67 /pdf_pad_key { % <key> pdf_pad_key <padded key>
68 dup length 32 gt { 0 32 getinterval } if
70 0 32 3 index length sub getinterval
74 /pdf_xorbytes { % <iter-num> <key> pdf_xorbytes <xored-key>
76 exch 1 sub 0 1 3 2 roll {
77 % <iter-num> <key> <new-key> <byte-num>
78 dup 3 index exch get 4 index xor
79 % <iter-num> <key> <new-key> <byte-num> <byte>
85 % Get length of encryption key in bytes
86 /pdf_key_length { % pdf_key_length <key_length>
87 Trailer /Encrypt oget /Length knownoget { -3 bitshift } { 5 } ifelse
91 /pdf_compute_encryption_key { % <password> pdf_compute_encryption_key <key>
96 Trailer /Encrypt oget dup /O oget
97 % <padded-key> <encrypt> <O>
100 exch /P oget 4 string exch
101 2 copy 255 and 0 exch put
102 2 copy -8 bitshift 255 and 1 exch put
103 2 copy -16 bitshift 255 and 2 exch put
104 2 copy -24 bitshift 255 and 3 exch put pop
105 % <padded-key> <O> <P>
108 Trailer /ID knownoget { 0 oget } {
110 ( **** ID key in the trailer is required for encrypted files.\n) pdfformaterror
112 3 { concatstrings } repeat
113 % We will finish step 5 after possibly including step 6.
115 % The following only executed for /R equal to 3 or more
116 Trailer /Encrypt oget dup /R oget dup 3 ge {
118 % Step 6. If EncryptMetadata is false, pass 0xFFFFFFFF to md5 function
119 % The PDF 1.5 Spec says that EncryptMetadata is an undocumented
120 % feature of PDF 1.4. That implies that this piece of logic should
121 % be executed if R >= 3. However testing with Acrobat 5.0 and 6.0 shows
122 % that this step is not executed if R equal to 3. Thus we have a test for
125 /EncryptMetadata knownoget % Get EncryptMetadata (if present)
126 not { true } if % Default is true
127 not { % If EncryptMetadata is false
128 <ff ff ff ff> concatstrings % Add 0xFFFFFFFF to working string
131 pop % Remove Encrypt dict
133 md5_trunk % Finish step 5 and 6.
135 % Step 7. Executed as part of step 6
136 % Step 8. (This step is defintely a part of PDF 1.4.)
137 50 { md5_trunk } repeat
139 pop pop md5_trunk % Remove R, Encrypt dict, finish step 5
142 % Step 9 - Done in md5_trunk.
146 /pdf_gen_user_password_R2 { % <filekey> pdf_gen_user_password_R2 <U>
149 pdf_padding_string exch arc4decode
153 /pdf_gen_user_password_R3 { % <filekey> pdf_gen_user_password_R3 <U>
159 Trailer /ID knownoget { 0 oget } {
161 ( **** ID key in the trailer is required for encrypted files.\n) pdfformaterror
170 2 index pdf_xorbytes arc4decode
176 /pdf_gen_user_password { % <password> pdf_gen_user_password <filekey> <U>
177 % common Step 1 of Algorithms 3.4 and 3.5.
178 pdf_compute_encryption_key dup
180 Trailer /Encrypt oget
183 pop pdf_gen_user_password_R2
186 pop pdf_gen_user_password_R3
188 dup 4 eq { % 4 uses the algorithm as 3
189 pop pdf_gen_user_password_R3
191 ( **** This file uses an unknown standard security handler revision: )
192 exch =string cvs concatstrings pdfformaterror printProducer
193 /pdf_check_user_password cvx /undefined signalerror
200 /pdf_check_user_password { % <password> pdf_check_user_password <filekey> true
201 % <password> pdf_check_user_password false
202 pdf_gen_user_password
204 Trailer /Encrypt oget /U oget
206 0 2 index length getinterval eq {
213 % Compute an owner key, ie the result of step 4 of Algorithm 3.3
214 /pdf_owner_key % <password> pdf_owner_key <owner-key>
222 % 3.3 Step 3. Only executed for /R equal to 3 or more
223 Trailer /Encrypt oget /R oget 3 ge {
224 50 { md5_trunk } repeat
227 % Step 4 - Done in md5_trunk.
231 /pdf_check_owner_password { % <password> pdf_check_owner_password <filekey> true
232 % <password> pdf_check_owner_password false
237 Trailer /Encrypt oget dup /O oget 2 index arc4decode
238 % <encryption-key> <encrypt-dict> <decrypted-O>
240 % Step 3. Only executed for /R equal to 3 or more
243 2 index pdf_xorbytes arc4decode
249 pdf_check_user_password
252 % Process the encryption information in the Trailer.
253 /pdf_process_Encrypt {
254 Trailer /Encrypt oget
255 /Filter oget /Standard eq not {
256 ( **** This file uses an unknown security handler.\n) pdfformaterror
258 /pdf_process_Encrypt cvx /undefined signalerror
260 () pdf_check_user_password
265 pop PDFPassword pdf_check_user_password
269 PDFPassword pdf_check_owner_password
273 ( **** Password did not work.\n) pdfformaterror
275 /pdf_process_Encrypt cvx /invalidfileaccess signalerror
279 ( **** This file requires a password for access.\n) pdfformaterror
281 /pdf_process_Encrypt cvx /invalidfileaccess signalerror
285 % Trailer /Encrypt oget /P oget 4 and 0 eq #? and
286 % { ( ****This owner of this file has requested you do not print it.\n)
287 % pdfformaterror printProducer
288 % /pdf_process_Encrypt cvx /invalidfileaccess signalerror
293 % Calculate the key used to decrypt an object (to pass to .decpdfrun or
294 % put into a stream dictionary).
295 /computeobjkey % <object#> <generation#> computeobjkey <keystring>
298 FileKey length 5 add string
299 dup 0 FileKey putinterval
301 % stack: gen# string obj#
302 2 copy 255 and FileKey length exch put
303 2 copy -8 bitshift 255 and FileKey length 1 add exch put
304 2 copy -16 bitshift 255 and FileKey length 2 add exch put
306 2 copy 255 and FileKey length 3 add exch put
307 2 copy -8 bitshift 255 and FileKey length 4 add exch put
308 pop md5 0 FileKey length 5 add 2 index length .min getinterval
311 % As .pdfrun, but decrypt strings with key <key>.
312 /.decpdfrun % <file> <keystring> <opdict> .decpdfrun -
313 { % Construct a procedure with the file, opdict and key bound into it.
314 2 index cvlit mark mark 5 2 roll
315 { .pdftoken not { (%%EOF) cvn cvx } if
317 { PDFDEBUG { dup == flush } if
320 { exch pop exch pop exec
332 { ( **** Unknown operator: )
333 exch =string cvs concatstrings (\n) concatstrings
344 { exch pop PDFDEBUG { dup ==only ( ) print flush } if
345 dup type /stringtype eq
347 % Check if we have encrypted strings R=4 allows for
348 % selection of encryption on streams and strings
349 Trailer /Encrypt oget % Get encryption dictionary
350 dup /R oget 4 lt % only 4 has selectable
351 { % R < 4 --> encrypted strings
352 pop 1 index arc4decode % Decrypt string
353 PDFDEBUG { (%Decrypted: ) print dup == flush } if
355 /StrF knownoget % Get StrF (if present)
356 { % If StrF is present ...
357 /Identity eq not % Check if StrF != Identity
358 { 1 index arc4decode % Decrypt string
359 PDFDEBUG { (%Decrypted: ) print dup == flush } if
361 if % If StrF != identity
363 if % If StrF is known
365 ifelse % Ifelse R < 4
372 aload pop .packtomark cvx
373 /loop cvx 2 packedarray cvx
374 { stopped /PDFsource } aload pop
376 { store { stop } if } aload pop .packtomark cvx
377 /PDFsource 3 -1 roll store exec
380 % Run the code to resolve an object reference.
382 { /FileKey where % Check if the file is encrypted
383 { pop % File is encrypted
384 2 copy computeobjkey dup 4 1 roll
385 PDFfile exch resolveopdict .decpdfrun
387 % stack: object object key object object
388 { % Use loop to provide an exitable context.
389 xcheck exch type /dicttype eq and % Check if executable dictionary
390 not { % If object is not ...
391 pop pop % ignore object
392 exit % Exit 'loop' context
393 } if % If not possible stream
394 % Starting with PDF 1.4 (R = 3), there are some extra features
395 % which control encryption of streams. The EncryptMetadata entry
396 % in the Encrypt dict controls the encryption of metadata streams.
397 Trailer /Encrypt oget % Get encryption dictionary
398 dup /R oget dup 3 lt % Only PDF 1.4 and higher has options
399 { % R < 3 --> all streams encrypted
400 pop pop /StreamKey exch put % Insert StreamKey in dictionary
401 exit % Exit 'loop' context
403 % Check EncryptMeta. stack: object object key Encrypt R
404 exch dup /EncryptMetadata knownoget % Get EncryptMetadata (if present)
405 not { true } if % If not present default = true
406 not % Check if EncryptMetadata = false
407 { % if false we need to check the stream type
408 3 index /Type knownoget % Get stream type (if present)
409 not { //null } if % If type not present use fake name
410 /Metadata eq % Check if the type is Metadata
411 { pop pop pop pop % Type == Metadata --> no encryption
412 exit % Exit 'loop' context
415 % PDF 1.5 encryption (R == 4) has selectable encryption handlers. If
416 % this is not PDF 1.5 encryption (R < 4) then we are done checking and
417 % we need to decrypt the stream. stack: object object key R Encrypt
418 exch 4 lt % Check for less than PDF 1.5
419 { pop /StreamKey exch put % Insert StreamKey in dictionary
420 exit % Exit 'loop' context
422 % Check if the stream encryption handler (StmF) == Identity.
423 /StmF knownoget % Get StmF (if present)
424 not { /Identity } if % If StmF not present default = Identity
425 /Identity eq % Check if StmF == Identity
426 { pop pop % Identity --> no encryption
427 exit % Exit 'loop' context
429 % If we get here then we need to decrypt the stream.
430 /StreamKey exch put % Insert StreamKey into dictionary
431 exit % Exit 'loop' context, never loop
432 } loop % End of loop exitable context
433 } { % Else file is not encrypted
434 PDFfile resolveopdict .pdfrun
435 } ifelse % Ifelse encrypted
438 % Prefix a decryption filter to a stream if needed.
439 % Stack: readdata? dict parms file/string filternames
440 % (both before and after).
442 { 3 index /StreamKey known % Check if the file is encrypted
445 % Stack: readdata? dict parms filternames file/string
448 % Handle Length=0 case specially to avoid SubFileDecode semantics
451 () /SubFileDecode filter
453 3 index /StreamKey get arc4decodefilter