Prawn::Document::Security

Implements PDF encryption (password protection and permissions) as specified in the PDF Reference, version 1.3, section 3.5 “Encryption”.

Constants

PermissionsBits

Flags in the permissions word, numbered as LSB = 1

FullPermissions
PasswordPadding

Public Class Methods

encrypt_string(str, key, id, gen) click to toggle source

Encrypts the given string under the given key, also requiring the object ID and generation number of the reference. See Algorithm 3.1.

     # File lib/prawn/security.rb, line 109
109:       def self.encrypt_string(str, key, id, gen)
110:         # Convert ID and Gen number into little-endian truncated byte strings
111:         id = [id].pack('V')[0,3]
112:         gen = [gen].pack('V')[0,2]
113:         extended_key = "#{key}#{id}#{gen}"
114: 
115:         # Compute the RC4 key from the extended key and perform the encryption
116:         rc4_key = Digest::MD5.digest(extended_key)[0, 10]
117:         Arcfour.new(rc4_key).encrypt(str)
118:       end

Public Instance Methods

encrypt_document(options={}) click to toggle source

Encrypts the document, to protect confidential data or control modifications to the document. The encryption algorithm used is detailed in the PDF Reference 1.3, section 3.5 “Encryption”, and it is implemented by all major PDF readers.

options can contain the following:

:user_password

Password required to open the document. If this is omitted or empty, no password will be required. The document will still be encrypted, but anyone can read it.

:owner_password

Password required to make modifications to the document or change or override its permissions. If this is set to :random, a random password will be used; this can be useful if you never want users to be able to override the document permissions.

:permissions

A hash mapping permission symbols (see below) to true or false. True means “permitted”, and false means “not permitted”. All permissions default to true.

The following permissions can be specified:

:print_document

Print document.

:modify_document

Modify contents of document (other than text annotations and interactive form fields).

:copy_contents

Copy text and graphics from document.

:modify_annotations

Add or modify text annotations and interactive form fields.

Examples

Deny printing to everyone, but allow anyone to open without a password:

  encrypt_document :permissions => { :print_document => false },
                   :owner_password => :random

Set a user and owner password on the document, with full permissions for both the user and the owner:

  encrypt_document :user_password => 'foo', :owner_password => 'bar'

Set no passwords, grant all permissions (This is useful because the default in some readers, if no permissions are specified, is “deny”):

  encrypt_document

Caveats

  • The encryption used is weak; the key is password-derived and is limited to 40 bits, due to US export controls in effect at the time the PDF standard was written.

  • There is nothing technologically requiring PDF readers to respect the permissions embedded in a document. Many PDF readers do not.

  • In short, you have no security at all against a moderately motivated person. Don’t use this for anything super-serious. This is not a limitation of Prawn, but is rather a built-in limitation of the PDF format.

     # File lib/prawn/security.rb, line 88
 88:       def encrypt_document(options={})
 89:         Prawn.verify_options [:user_password, :owner_password, :permissions],
 90:           options
 91:         @user_password = options.delete(:user_password) || ""
 92: 
 93:         @owner_password = options.delete(:owner_password) || @user_password
 94:         if @owner_password == :random
 95:           # Generate a completely ridiculous password
 96:           @owner_password = (1..32).map{ rand(256) }.pack("c*")
 97:         end
 98: 
 99:         self.permissions = options.delete(:permissions) || {}
100: 
101:         # Shove the necessary entries in the trailer.
102:         @trailer[:Encrypt] = encryption_dictionary
103:         @encrypted = true
104:       end

Private Instance Methods

encryption_dictionary() click to toggle source

Provides the values for the trailer encryption dictionary.

     # File lib/prawn/security.rb, line 123
123:       def encryption_dictionary
124:         { :Filter => :Standard, # default PDF security handler
125:           :V      => 1,         # "Algorithm 3.1", PDF reference 1.3
126:           :R      => 2,         # Revision 2 of the algorithm
127:           :O      => ByteString.new(owner_password_hash),
128:           :U      => ByteString.new(user_password_hash),
129:           :P      => permissions_value }
130:       end
owner_password_hash() click to toggle source

The O (owner) value in the encryption dictionary. Algorithm 3.3.

     # File lib/prawn/security.rb, line 179
179:       def owner_password_hash
180:         @owner_password_hash ||= begin
181:           key = Digest::MD5.digest(pad_password(@owner_password))[0, 5]
182:           Arcfour.new(key).encrypt(pad_password(@user_password))
183:         end
184:       end
pad_password(password) click to toggle source

Pads or truncates a password to 32 bytes as per Alg 3.2.

     # File lib/prawn/security.rb, line 163
163:       def pad_password(password)
164:         password = password[0, 32]
165:         password + PasswordPadding[0, 32 - password.length]
166:       end
permissions=(perms={}) click to toggle source
     # File lib/prawn/security.rb, line 140
140:       def permissions=(perms={})
141:         @permissions ||= FullPermissions
142:         perms.each do |key, value|
143:           # 0-based bit number, from LSB
144:           bit_position = PermissionsBits[key] - 1
145: 
146:           if value # set bit
147:             @permissions |= (1 << bit_position)
148:           else # clear bit
149:             @permissions &= ~(1 << bit_position)
150:           end
151:         end
152:       end
permissions_value() click to toggle source
     # File lib/prawn/security.rb, line 154
154:       def permissions_value
155:         @permissions || FullPermissions
156:       end
user_encryption_key() click to toggle source
     # File lib/prawn/security.rb, line 168
168:       def user_encryption_key
169:         @user_encryption_key ||= begin
170:           md5 = Digest::MD5.new
171:           md5 << pad_password(@user_password)
172:           md5 << owner_password_hash
173:           md5 << [permissions_value].pack("V")
174:           md5.digest[0, 5]
175:         end
176:       end
user_password_hash() click to toggle source

The U (user) value in the encryption dictionary. Algorithm 3.4.

     # File lib/prawn/security.rb, line 187
187:       def user_password_hash
188:         Arcfour.new(user_encryption_key).encrypt(PasswordPadding)
189:       end

Disabled; run with --debug to generate this.

[Validate]

Generated with the Darkfish Rdoc Generator 1.1.6.