RE: 687347 Implementation of the PDF 1.5 encryptionfeature: EncryptMetadata
"Dan Coby" <[email protected]>
| Newsgroups | gmane.comp.printing.ghostscript.patches |
|---|---|
| Message-ID | <[email protected]> |
Igor,
>1. PDF reference 1.5 page 100 reads :
>
> 6.(Revision 3 only)If document metadata is not being encrypted,pass
4 bytes with
> the value 0xFFFFFFFF to the MD5 hash function.
>
>You check for R==4. I guess you do so because PLRM says that
>EncryptMetadata only appears with R==4.
Since receiving your comment, I have done some experimenting using
Acrobat 5.0 and 6.0. I created a file with EncryptMetadata = false.
I then changed the encryption level to 'Adobe 5.0 or later'. This
produced a file with both EncryptMetadata = false and R = 3.
The value for /U indicates that the 0xffffffff was NOT used in the
calculation. I agree that this result is not consistent with the
PDF 1.5 specification's comments about EncryptMetadata being an
undocumented addition to PDF 1.4. Note: Both Acrobat 5.0 and Acrobat
6.0 can read this file. (Acrobat 5.0 does produce a warning message
about the file containing 'advanced features' and suggesting that I
update to a newer version. This message may be due to other aspects
of the test file.) This test does indicate that it is desirable to
include the R >= 4 check which is present in the current code.
I have changed a comment in the code and the log message to reflect
the inconsistencies found between the PDF 1.5 specification and
the actions of Acrobat 5.0 and 6.0.
A final note: I do not know how to actually get Acrobat 6.0 to
actually generate some metadata. Thus my test files do not actually
contain any real metadata. A test file with real metadata may show
more problems. (This comment has been added to the log message.)
>To avoid possible problems, I would not recommend
>to bring a stronger logics over the Adobe's spec.
>A better way is to code exactly what Adobe's specifies :
>
> if (R>=3)
> if (exist(EncryptMetadata) && !EncryptMetadata)
> add(0xffffffff);
See previous comment about testing with Acrobat 5.0 and 6.0.
My logic is also a exactly what the specification says
except for the extra R = 4 check. The spec says that if
EncryptMetadata is not present then use 'true' as a default
value. This is what is done by my version of the code.
If the R = 4 check is added to your logic (it is needed), then
the two expressions are logically equal.
>2.
>> pop pop md5_trunk % Remove Encrypt dict
>
>The comment is confusing. Please replace with :
>
> pop pop % Remove Encrypt dict and R
> md5_trunk
I did some clean up on several comments. However I forgot to
save those changes prior to doing the 'diff' which I sent to
gs-code-review. I apologize for wasting your time.
The line should have read:
pop pop md5_trunk % Remove R, Encrypt dict, finish step 5
A full copy of the changes with the revisions from this email
is included at the end of this document.
>3.
>> % Use loop to provide an exitable context.
>
>IMO this and several similar comments are unuseful.
As you said, this is a matter of personal opinion. I think
the comment is desirable.
A similar comment is present in the PostScript sources where
a 'loop' is used to provide an exitable context. In many cases
the use of a 'loop' allows the avoidance of many nested if and
ifelse statements. Most of these comments were placed there
originally by Peter. Alex has copied this style (with comment)
in at least one patch.
I personally consider the use of a 'loop' statement for this
purpose a curiosity of the PostScript language. There is
absolutely no intent to ever execute the given group of
statements more than once. Thus this is a very curious use
of a 'loop' construct. I am aware that it is documented in
the PLRM. Since it may be confusing to new users of PostScript,
I consider it worth documenting. Since Ghostscript is very
likely to be many programmer's first experience with the
PostScript language, I think that it is very desirable that
the sources be as easy to understand as possible. I would not
explain a 'break' statement to a C programmer. However I do
believe in making it very obvious about what is happening with
'loop' and 'exit' statements in this situation in Postscript.
>4.
>> + % The following only executed for /R equal to 3 or more
>> + Trailer /Encrypt get dup /R oget dup 3 ge {
>
>Another unuseful comment.
>The code says exactly the same as the comment.
It is true that the comment and code say the same thing. However
Postscript is not an easy language to read. The comment allows a
quick examination of what is happening without going into the
details of the code. Try reading the code if you do not know what
the 'oget' procedure does. Try reading the code for the 'oget'
procedure to figure out what it does. (The oget procedure does
an 'exec'. However it does not explain why that is useful.)
>5.
>> + % The following only executed for /R equal to 4
>> + % Step 6. If EncryptMetadata is false, pass 0xFFFFFFFF to md5
>> function
>> + 4 eq {
>
>See (1). I would code :
>
>+ % Step 6. (only R==3) If EncryptMetadata is false, pass 0xFFFFFFFF
to md5 function
>+ 3 ge {
This has already been discussed. The comment has been changed to:
% Step 6. If EncryptMetadata is false, pass 0xFFFFFFFF to md5 function
% The PDF 1.5 Spec says that EncryptMetadata is an undocumented
% feature of PDF 1.4. That implies that this piece of logic should
% be executed if R >= 3. However testing with Acrobat 5.0 and 6.0
shows
% that this step is not executed if R equal to 3. Thus we have a test
for
% R being >= 4.
4 ge {
>6.
>> + not { /Unknown } if % If type not present use fake
name
>> + /Metadata eq % Check if the type is Metadata
>
>Replace /Unknown with //null. No need to put trash into the name table.
I agree. Changed.
>7.
>> + not % Check if false
>
>I'm unhappy of such commenting.
>Do you write a tutorial for Postscript beginners ?
>I would recommend to remove all such comments.
>Instead that a useful thing would be the stack layout.
The comment has been changed to:
+ not % Check if EncryptMetadata = false
>8. The log message :
>
>> Implement 687347: Implementation of the PDF 1.5 encryption feature:
>> EncryptMetadata. Past versions of the PDF encryption have always
>> encrypted metadata. With PDF 1.5, it is possible to NOT encrypt
>> metadata streams. I.e. By adding '/EncryptMetadata false' to the
>> encryption dictionary.
>
>Please move "Implement 687347" to details.
I disagree. I believe that the summary statement should include the
report number. This makes it easier for someone who is scanning the
HistoryX.htm files to easily find more information on a particular
issue.
>Also your log contradicts to what Adobe says on page 92 :
>
> Note:This entry was introduced in PDF 1.3 to support the use of
public-key cryptography
> in PDF files (see Section 3.5.3,"Public-Key Security Handlers
");however,it was not incorporated
> into the PDF Reference until the fourth edition (PDF 1.5).
This has been previously discussed, the log message has been changed
to reflect the inconsistencies between the documentation and how
Acrobat 5.0 and 6.0 actually act. The log message is given below.
Dan
Log message:
Implement 687347: Implementation of the PDF encryption feature:
EncryptMetadata. Past versions of the PDF encryption have always
encrypted metadata. With this feature it is possible to NOT encrypt
metadata streams. I.e. By adding '/EncryptMetadata false' to the
encryption dictionary.
DETAILS:
There are two pieces to the implementation:
1. The calculation of the user password key includes an additional
step of feeding 0xffffffff to the md5 input if EncryptMetadata is
false. (Ask Adobe, not me.) The PDF 1.5 spec. says that
EncryptMetadata is an undocumented feature of PDF 1.4. This
implies that this action needs to be done for values of R in the
Encrypt dictionary of 3 or higher. However testing with Acrobat
5.0 and Acrobat 6.0 shows that this is not done for R = 3. Thus
we check for R >= 4.
2. Streams need to be check to see if the type is Metadata. If so
then value for EncryptMetadata needs to be checked. If false then
the stream does not need to be decrypted.
A final note: I do not know how to actually get Acrobat 6.0 to
actually generate some metadata. Thus my test files do not actually
contain any real metadata. A test file with real metadata may show
more problems.
Index: lib/pdf_sec.ps
===================================================================
RCS file: /cvs/ghostscript/gs/lib/pdf_sec.ps,v
retrieving revision 1.14
diff -u -r1.14 pdf_sec.ps
--- a/lib/pdf_sec.ps 4 Mar 2004 05:06:38 -0000 1.14
+++ b/lib/pdf_sec.ps 9 Mar 2004 06:29:41 -0000
@@ -54,7 +54,7 @@
} bind def
/md5_trunk {
- md5 pdf_key_length 0 exch getinterval
+ md5 0 pdf_key_length getinterval
} bind def
@@ -109,14 +109,37 @@
()
( **** ID key in the trailer is required for encrypted files.\n)
pdfformaterror
} ifelse
- 3 { concatstrings } repeat md5_trunk
+ 3 { concatstrings } repeat
+ % We will finish step 5 after possibly including step 6.
- % Step 6. Only executed for /R equal to 3 or more
- Trailer /Encrypt oget /R oget 3 ge {
+ % The following only executed for /R equal to 3 or more
+ Trailer /Encrypt oget dup /R oget dup 3 ge {
+
+ % Step 6. If EncryptMetadata is false, pass 0xFFFFFFFF to md5
function
+ % The PDF 1.5 Spec says that EncryptMetadata is an undocumented
+ % feature of PDF 1.4. That implies that this piece of logic should
+ % be executed if R >= 3. However testing with Acrobat 5.0 and 6.0
shows
+ % that this step is not executed if R equal to 3. Thus we have a test
for
+ % R being >= 4.
+ 4 ge {
+ /EncryptMetadata knownoget % Get EncryptMetadata (if present)
+ not { true } if % Default is true
+ not { % If EncryptMetadata is false
+ <ff ff ff ff> concatstrings % Add 0xFFFFFFFF to working string
+ } if
+ } {
+ pop % Remove Encrypt dict
+ } ifelse
+ md5_trunk % Finish step 5 and 6.
+
+ % Step 7. Executed as part of step 6
+ % Step 8. (This step is defintely a part of PDF 1.4.)
50 { md5_trunk } repeat
- } if
+ } {
+ pop pop md5_trunk % Remove R, Encrypt dict, finish
step 5
+ } ifelse
- % Step 7 - Done in md5_trunk.
+ % Step 9 - Done in md5_trunk.
} bind def
% Algorithm 3.4
@@ -362,29 +385,46 @@
PDFfile exch resolveopdict .decpdfrun
dup dup dup 5 2 roll
% stack: object object key object object
- xcheck exch type /dicttype eq and % Check if possible stream
- { % Check if we have encrypted streams. R=4 allows for
- % selection of encryption on streams and strings
+ { % Use loop to provide an exitable context.
+ xcheck exch type /dicttype eq and % Check if executable dictionary
+ not { % If object is not ...
+ pop pop % ignore object
+ exit % Exit 'loop' context
+ } if % If not possible stream
+ % PDF 1.5 encryption (R == 4) has some extra features. If this
+ % is not PDF 1.5 encryption (R < 4) then we need to decrypt the
+ % stream
Trailer /Encrypt oget % Get encryption dictionary
dup /R oget 4 lt % only 4 has selectable
{ % R < 4 --> encrypted
streams
pop /StreamKey exch put % Insert StreamKey in
dictionary
- } { % Else R = 4
- % Check for StmF = Identity
- /StmF knownoget % Get StmF (if present)
- { % If StmF is present ...
- /Identity eq % Check if StmF = Identity
- { pop pop % Identity --> no encryption
- } { % Else we have an encrption handler
- /StreamKey exch put % Insert StreamKey into dictionary
- } ifelse % Ifelse StmF = identity
- } { % Else StmF not present, default =
Identity
- pop pop % Identity --> no encryption
- } ifelse % Ifelse StmF is known
- } ifelse % Ifelse R < 4
- } { % Else object is not a
dictionary
- pop pop % ignore object
- } ifelse % Ifelse possible stream
+ exit % Exit 'loop' context
+ } if
+ % Check if we have encrypted streams. R == 4 (PDF 1.5) allows for
+ % selection of encryption on streams and strings. Check for StmF
+ % == Identity
+ dup /StmF knownoget % Get StmF (if present)
+ not { /Identity } if % If StmF not present default =
Identity
+ /Identity eq % Check if StmF == Identity
+ { pop pop pop % Identity --> no encryption
+ exit % Exit 'loop' context
+ } if
+ % PDF 1.5 has a separate flag for encrypting meta data
+ /EncryptMetadata knownoget % Get EncryptMetadata (if present)
+ not { true } if % If not present default = true
+ not % Check if EncryptMetadata = false
+ { % if false we need to check the
stream type
+ 1 index /Type knownoget % Get stream type (if ppresent)
+ not { //null } if % If type not present use fake name
+ /Metadata eq % Check if the type is Metadata
+ { pop pop % Type == Metadata --> no encryption
+ exit % Exit 'loop' context
+ } if
+ } if
+ % If we get here then we need to decrypt the stream.
+ /StreamKey exch put % Insert StreamKey into dictionary
+ exit % Exit 'loop' context, never loop
+ } loop % End of loop exitable context
} { % Else file is not encrypted
PDFfile resolveopdict .pdfrun
} ifelse % Ifelse encrypted
-----Original Message-----
From: Igor V. Melichev [mailto:[email protected]]
Sent: Saturday, March 06, 2004 3:55 PM
To: '[email protected]'
Subject: Re: [gs-code-review] 687347 Implementation of the PDF 1.5
encryptionfeature: EncryptMetadata
Dan,
Here are my remarks.
1. PDF reference 1.5 page 100 reads :
6.(Revision 3 only)If document metadata is not being encrypted,pass
4 bytes with
the value 0xFFFFFFFF to the MD5 hash function.
You check for R==4. I guess you do so because PLRM says that
EncryptMetadata only appears with R==4.
To avoid possible problems, I would not recommend
to bring a stronger logics over the Adobe's spec.
A better way is to code exactly what Adobe's specifies :
if (R>=3)
if (exist(EncryptMetadata) && !EncryptMetadata)
add(0xffffffff);
2.
> pop pop md5_trunk % Remove Encrypt dict
The comment is confusing. Please replace with :
pop pop % Remove Encrypt dict and R
md5_trunk
3.
> % Use loop to provide an exitable context.
IMO this and several similar comments are unuseful.
4.
> + % The following only executed for /R equal to 3 or more
> + Trailer /Encrypt oget dup /R oget dup 3 ge {
Another unuseful comment.
The code says exactly the same as the comment.
5.
> + % The following only executed for /R equal to 4
> + % Step 6. If EncryptMetadata is false, pass 0xFFFFFFFF to md5
> function
> + 4 eq {
See (1). I would code :
+ % Step 6. (only R==3) If EncryptMetadata is false, pass 0xFFFFFFFF to
md5 function
+ 3 ge {
6.
> + not { /Unknown } if % If type not present use fake
name
> + /Metadata eq % Check if the type is Metadata
Replace /Unknown with //null. No need to put trash into the name table.
7.
> + not % Check if false
I'm unhappy of such commenting.
Do you write a tutorial for Postscript beginners ?
I would recommend to remove all such comments.
Instead that a useful thing would be the stack layout.
8. The log message :
> Implement 687347: Implementation of the PDF 1.5 encryption feature:
> EncryptMetadata. Past versions of the PDF encryption have always
> encrypted metadata. With PDF 1.5, it is possible to NOT encrypt
> metadata streams. I.e. By adding '/EncryptMetadata false' to the
> encryption dictionary.
Please move "Implement 687347" to details.
Also your log contradicts to what Adobe says on page 92 :
Note:This entry was introduced in PDF 1.3 to support the use of
public-key cryptography
in PDF files (see Section 3.5.3,"Public-Key Security Handlers
");however,it was not incorporated
into the PDF Reference until the fourth edition (PDF 1.5).
Igor.