123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776 |
- #copyright ReportLab Europe Limited. 2000-2016
- #see license.txt for license details
- __version__='3.3.0'
- """helpers for pdf encryption/decryption"""
- import sys, os, tempfile
- from binascii import hexlify, unhexlify
- from reportlab.lib.utils import getBytesIO, md5, asBytes, int2Byte, char2int, rawUnicode, rawBytes, asNative
- from reportlab.pdfgen.canvas import Canvas
- from reportlab.pdfbase import pdfutils
- from reportlab.pdfbase.pdfdoc import PDFObject
- from reportlab.platypus.flowables import Flowable
- from reportlab import rl_config
- try:
- import pyaes
- from hashlib import sha256
- except ImportError:
- pyaes = None
- def xorKey(num,key):
- "xor's each byte of the key with the number, which is <256"
- if num==0: return key
- return bytes(num^k for k in key)
- bytes3 = bytes
- #AR debug hooks - leaving in for now
- CLOBBERID = 0 # set a constant Doc ID to allow comparison with other software like iText
- CLOBBERPERMISSIONS = 0
- DEBUG = rl_config.debug # print stuff to trace calculations
- # permission bits
- reserved1 = 1 # bit 1 must be 0
- reserved2 = 1<<1 # bit 2 must be 0
- printable = 1<<2
- modifiable = 1<<3
- copypastable = 1<<4
- annotatable = 1<<5
- # others [7..32] are reserved, must be 1
- higherbits = 0
- for i in range(6,31):
- higherbits = higherbits | (1<<i)
- if rl_config.invariant:
- _os_random_x=0
- _os_random_b = b'\xbd\x8f\xdc\xabovp\xe8\x15\xec\\C\x9d\x92B~\xb8\xf4\xdeEg8\xb2f\x80Sj\'y\xcfG\xcaY\xb9\xdc-\xc4Q\x17\x88\xaf\xd1\xf7\x7f\xa1L>\x99\x89i\xf7\xc4\xb4\'\xe9k\xc9\xfa\xa6p\x80\xcd\xaa\xaf|\x97\xf7\xcc \xc1\xef\xc7\x97\xd2;\xaf\xe1\xfc\x16,\xd3\x0b\x19\xa1\x02\xe6\x01\xcb\x1c\xd8\xe6\\H}\r\xdc\x85\xe1\xbc\xc4\x02>|\xc5\x97\xb5T\xad\x0cT\x95\xb1\xdc!\xb6+E#\xa1\xa4O\xf3j\x98"\xc2\x1a\xcb\x8cHB\xd8B~\xa7\x7f7\xd2\xe8\x131.\xd7\xa9\x0b\r\xdd2\x0b}\xc0\xffm\x9e3\xe2/\xea\x84W\x82\xbd\xc8K\xc2;?\xbe#\x84`W\xf3\xe0\xec\x9e\x85\x9c\xcb\xc7\xc9#\x19\xff\xde\x17\xea\xb2\xd4\x0e\x9a\xbd\xbaz\xbd\x87O\xd4\xf4\xac\xb3(z\x92\xfc\xbc\x85i\x8d\x1f\xfb!\t|w,\x8bI\xc9_D`A\xbc}\x0e+r\x1b-%F(@\xc8\\cL\x172(\x9c\x95BM\xa1\x89UG\x9d\xfd\xed\xce\xd8\x1f\xb1'
- def os_urandom(n):
- global _os_random_x
- b = [_os_random_b[(i+_os_random_x)%256] for i in range(n)]
- b = bytes(b)
- _os_random_x = (_os_random_x + n) % 256
- return b
- else:
- os_urandom = os.urandom
- # no encryption
- class StandardEncryption:
- prepared = 0
- def __init__(self, userPassword, ownerPassword=None, canPrint=1, canModify=1, canCopy=1, canAnnotate=1, strength=None):
- '''
- This class defines the encryption properties to be used while creating a pdf document.
- Once initiated, a StandardEncryption object can be applied to a Canvas or a BaseDocTemplate.
- The userPassword parameter sets the user password on the encrypted pdf.
- The ownerPassword parameter sets the owner password on the encrypted pdf.
- The boolean flags canPrint, canModify, canCopy, canAnnotate determine wether a user can
- perform the corresponding actions on the pdf when only a user password has been supplied.
- If the user supplies the owner password while opening the pdf, all actions can be performed regardless
- of the flags.
- Note that the security provided by these encryption settings (and even more so for the flags) is very weak.
- '''
- self.userPassword = userPassword
- if ownerPassword:
- self.ownerPassword = ownerPassword
- else:
- self.ownerPassword = userPassword
- if strength is None:
- strength = rl_config.encryptionStrength
- if strength == 40:
- self.revision = 2
- elif strength == 128:
- self.revision = 3
- elif strength == 256:
- if not pyaes:
- raise ValueError('strength==256 is not supported as package pyaes is not importable')
- self.revision = 5
- else:
- raise ValueError('Unknown encryption strength=%s' % repr(strength))
- self.canPrint = canPrint
- self.canModify = canModify
- self.canCopy = canCopy
- self.canAnnotate = canAnnotate
- self.O = self.U = self.P = self.key = self.OE = self.UE = self.Perms = None
- def setAllPermissions(self, value):
- self.canPrint = \
- self.canModify = \
- self.canCopy = \
- self.canAnnotate = value
- def permissionBits(self):
- p = 0
- if self.canPrint: p = p | printable
- if self.canModify: p = p | modifiable
- if self.canCopy: p = p | copypastable
- if self.canAnnotate: p = p | annotatable
- p = p | higherbits
- return p
- def encode(self, t):
- "encode a string, stream, text"
- if not self.prepared:
- raise ValueError("encryption not prepared!")
- if self.objnum is None:
- raise ValueError("not registered in PDF object")
- return encodePDF(self.key, self.objnum, self.version, t, revision=self.revision)
- def prepare(self, document, overrideID=None):
- # get ready to do encryption
- if DEBUG: print('StandardEncryption.prepare(...) - revision %d' % self.revision)
- if self.prepared:
- raise ValueError("encryption already prepared!")
- # get the unescaped string value of the document id (first array element).
- # we allow one to be passed in instead to permit reproducible tests
- # of our algorithm, but in real life overrideID will always be None
- if overrideID:
- internalID = overrideID
- else:
- externalID = document.ID() # initialize it...
- internalID = document.signature.digest()
- #AR debugging
- if CLOBBERID:
- internalID = "xxxxxxxxxxxxxxxx"
- if DEBUG:
- print('userPassword = %r' % self.userPassword)
- print('ownerPassword = %r' % self.ownerPassword)
- print('internalID = %r' % internalID)
- self.P = int(self.permissionBits() - 2**31)
- if CLOBBERPERMISSIONS: self.P = -44 # AR hack
- if DEBUG:
- print("self.P = %s" % repr(self.P))
- if self.revision == 5:
-
- # Init vectro for AES cipher (should be 16 bytes null array)
- iv = b'\x00' * 16
- # Random User salts
- uvs = os_urandom(8)
- uks = os_urandom(8)
-
- # the main encryption key
- self.key = asBytes(os_urandom(32))
-
- if DEBUG:
- print("uvs (hex) = %s" % hexText(uvs))
- print("uks (hex) = %s" % hexText(uks))
- print("self.key (hex) = %s" % hexText(self.key))
- # Calculate the sha-256 hash of the User password (U)
- md = sha256(asBytes(self.userPassword[:127]) + uvs)
- self.U = md.digest() + uvs + uks
- if DEBUG:
- print("self.U (hex) = %s" % hexText(self.U))
- # Calculate the User encryption key (UE)
- md = sha256(asBytes(self.userPassword[:127]) + uks)
-
- encrypter = pyaes.Encrypter(pyaes.AESModeOfOperationCBC(md.digest(), iv=iv))
- self.UE = encrypter.feed(self.key)
- self.UE += encrypter.feed()
- if DEBUG:
- print("self.UE (hex) = %s" % hexText(self.UE))
- # Random Owner salts
- ovs = os_urandom(8)
- oks = os_urandom(8)
- # Calculate the hash of the Owner password (U)
- md = sha256(asBytes(self.ownerPassword[:127]) + ovs + self.U )
- self.O = md.digest() + ovs + oks
- if DEBUG:
- print("self.O (hex) = %s" % hexText(self.O))
- # Calculate the User encryption key (OE)
- md = sha256(asBytes(self.ownerPassword[:127]) + oks + self.U)
- encrypter = pyaes.Encrypter(pyaes.AESModeOfOperationCBC(md.digest(), iv=iv))
- self.OE = encrypter.feed(self.key)
- self.OE += encrypter.feed()
- if DEBUG:
- print("self.OE (hex) = %s" % hexText(self.OE))
- # Compute permissions array
- permsarr = [
- self.P & 0xFF, # store the permission value in the first 32-bits
- self.P >> 8 & 0xFF,
- self.P >> 16 & 0xFF,
- self.P >> 24 & 0xFF,
- 0xFF,
- 0xFF,
- 0xFF,
- 0xFF,
- ord('T'), # 'T' if EncryptMetaData is True (default), 'F' otherwise
- ord('a'), # a, d, b are magic values
- ord('d'),
- ord('b'),
- 0x01, # trailing zeros will be ignored
- 0x01,
- 0x01,
- 0x01
- ]
- # the permission array should be enrypted in the Perms field
- encrypter = pyaes.Encrypter(pyaes.AESModeOfOperationCBC(self.key, iv=iv))
- self.Perms = encrypter.feed(bytes3(permsarr))
- self.Perms += encrypter.feed()
-
- if DEBUG:
- print("self.Perms (hex) = %s" % hexText(self.Perms))
- elif self.revision in (2, 3):
- self.O = computeO(self.userPassword, self.ownerPassword, self.revision)
- if DEBUG:
- print("self.O (as hex) = %s" % hexText(self.O))
- #print "\nself.O", self.O, repr(self.O)
- self.key = encryptionkey(self.userPassword, self.O, self.P, internalID, revision=self.revision)
- if DEBUG:
- print("self.key (hex) = %s" % hexText(self.key))
- self.U = computeU(self.key, revision=self.revision, documentId=internalID)
- if DEBUG:
- print("self.U (as hex) = %s" % hexText(self.U))
- self.objnum = self.version = None
- self.prepared = 1
- def register(self, objnum, version):
- # enter a new direct object
- if not self.prepared:
- raise ValueError("encryption not prepared!")
- self.objnum = objnum
- self.version = version
- def info(self):
- # the representation of self in file if any (should be None or PDFDict)
- if not self.prepared:
- raise ValueError("encryption not prepared!")
- return StandardEncryptionDictionary(O=self.O, OE=self.OE, U=self.U, UE=self.UE, P=self.P, Perms=self.Perms, revision=self.revision)
- class StandardEncryptionDictionary(PDFObject):
- __RefOnly__ = 1
- def __init__(self, O, OE, U, UE, P, Perms, revision):
- self.O, self.OE, self.U, self.UE, self.P, self.Perms = O, OE, U, UE, P, Perms
- self.revision = revision
- def format(self, document):
- # use a dummy document to bypass encryption
- from reportlab.pdfbase.pdfdoc import DummyDoc, PDFDictionary, PDFString, PDFName
- dummy = DummyDoc()
- dict = {"Filter": PDFName("Standard"),
- "O": hexText(self.O),
- "U": hexText(self.U),
- "P": self.P}
- if self.revision == 5:
- dict['Length'] = 256
- dict['R'] = 5
- dict['V'] = 5
- dict['O'] = hexText(self.O)
- dict['U'] = hexText(self.U)
- dict['OE'] = hexText(self.OE)
- dict['UE'] = hexText(self.UE)
- dict['Perms'] = hexText(self.Perms)
- dict['StrF'] = PDFName("StdCF")
- dict['StmF'] = PDFName("StdCF")
- stdcf = {
- "Length": 32,
- "AuthEvent": PDFName("DocOpen"),
- "CFM": PDFName("AESV3")
- }
- cf = {
- "StdCF": PDFDictionary(stdcf)
- }
- dict['CF'] = PDFDictionary(cf)
- elif self.revision == 3:
- dict['Length'] = 128
- dict['R'] = 3
- dict['V'] = 2
- else:
- dict['R'] = 2
- dict['V'] = 1
- pdfdict = PDFDictionary(dict)
- return pdfdict.format(dummy)
- # from pdf spec
- padding = """
- 28 BF 4E 5E 4E 75 8A 41 64 00 4E 56 FF FA 01 08
- 2E 2E 00 B6 D0 68 3E 80 2F 0C A9 FE 64 53 69 7A
- """
- def hexText(text):
- "a legitimate way to show strings in PDF"
- return '<%s>' % asNative(hexlify(rawBytes(text))).upper()
- def unHexText(hexText):
- equalityCheck(hexText[0], '<', 'bad hex text')
- equalityCheck(hexText[-1], '>', 'bad hex text')
- return unhexlify(hexText[1:-1])
- PadString = rawBytes(''.join(chr(int(c, 16)) for c in padding.strip().split()))
- def checkRevision(revision):
- if revision is None:
- strength = rl_config.encryptionStrength
- if strength == 40:
- revision = 2
- elif strength == 128:
- revision = 3
- elif strength == 256:
- if not pyaes:
- raise ValueError('strength==256 is not supported as package pyaes is not importable')
- revision = 5
- else:
- raise ValueError('Unknown encryption strength=%s' % repr(strength))
- return revision
- def encryptionkey(password, OwnerKey, Permissions, FileId1, revision=None):
- revision = checkRevision(revision)
- # FileId1 is first string of the fileid array
- # add padding string
- #AR force same as iText example
- #Permissions = -1836 #int(Permissions - 2**31)
- password = asBytes(password) + PadString
- # truncate to 32 bytes
- password = password[:32]
- # translate permissions to string, low order byte first
- p = Permissions# + 2**32L
- permissionsString = b""
- for i in range(4):
- byte = (p & 0xff) # seems to match what iText does
- p = p>>8
- permissionsString += int2Byte(byte % 256)
- hash = md5(asBytes(password))
- hash.update(asBytes(OwnerKey))
- hash.update(asBytes(permissionsString))
- hash.update(asBytes(FileId1))
- md5output = hash.digest()
- if revision==2:
- key = md5output[:5]
- elif revision==3: #revision 3 algorithm - loop 50 times
- for x in range(50):
- md5output = md5(md5output).digest()
- key = md5output[:16]
- if DEBUG: print('encryptionkey(%s,%s,%s,%s,%s)==>%s' % tuple([hexText(str(x)) for x in (password, OwnerKey, Permissions, FileId1, revision, key)]))
- return key
- def computeO(userPassword, ownerPassword, revision):
- from reportlab.lib.arciv import ArcIV
- #print 'digest of hello is %s' % md5('hello').digest()
- assert revision in (2,3), 'Unknown algorithm revision %s' % revision
- if not ownerPassword:
- ownerPassword = userPassword
- ownerPad = asBytes(ownerPassword) + PadString
- ownerPad = ownerPad[0:32]
- password = asBytes(userPassword) + PadString
- userPad = password[:32]
- digest = md5(ownerPad).digest()
- if DEBUG: print('PadString=%s\nownerPad=%s\npassword=%s\nuserPad=%s\ndigest=%s\nrevision=%s' % (ascii(PadString),ascii(ownerPad),ascii(password),ascii(userPad),ascii(digest),revision))
- if revision == 2:
- O = ArcIV(digest[:5]).encode(userPad)
- elif revision == 3:
- for i in range(50):
- digest = md5(digest).digest()
- digest = digest[:16]
- O = userPad
- for i in range(20):
- thisKey = xorKey(i, digest)
- O = ArcIV(thisKey).encode(O)
- if DEBUG: print('computeO(%s,%s,%s)==>%s' % tuple([hexText(str(x)) for x in (userPassword, ownerPassword, revision,O)]))
- return O
- def computeU(encryptionkey, encodestring=PadString,revision=None,documentId=None):
- revision = checkRevision(revision)
- from reportlab.lib.arciv import ArcIV
- if revision == 2:
- result = ArcIV(encryptionkey).encode(encodestring)
- elif revision == 3:
- assert documentId is not None, "Revision 3 algorithm needs the document ID!"
- h = md5(PadString)
- h.update(rawBytes(documentId))
- tmp = h.digest()
- tmp = ArcIV(encryptionkey).encode(tmp)
- for n in range(1,20):
- thisKey = xorKey(n, encryptionkey)
- tmp = ArcIV(thisKey).encode(tmp)
- while len(tmp) < 32:
- tmp += b'\0'
- result = tmp
- if DEBUG: print('computeU(%s,%s,%s,%s)==>%s' % tuple([hexText(str(x)) for x in (encryptionkey, encodestring,revision,documentId,result)]))
- return result
- def checkU(encryptionkey, U):
- decoded = computeU(encryptionkey, U)
- #print len(decoded), len(U), len(PadString)
- if decoded!=PadString:
- if len(decoded)!=len(PadString):
- raise ValueError("lengths don't match! (password failed)")
- raise ValueError("decode of U doesn't match fixed padstring (password failed)")
- def encodePDF(key, objectNumber, generationNumber, string, revision=None):
- "Encodes a string or stream"
- revision = checkRevision(revision)
- #print 'encodePDF (%s, %d, %d, %s)' % (hexText(key), objectNumber, generationNumber, string)
- # extend 3 bytes of the object Number, low byte first
- if revision in (2,3):
- newkey = key
- n = objectNumber
- for i in range(3):
- newkey += int2Byte(n & 0xff)
- n = n>>8
- # extend 2 bytes of the generationNumber
- n = generationNumber
- for i in range(2):
- newkey += int2Byte(n & 0xff)
- n = n>>8
- md5output = md5(newkey).digest()
- if revision == 2:
- key = md5output[:10]
- elif revision == 3:
- key = md5output #all 16 bytes
- from reportlab.lib.arciv import ArcIV
- encrypted = ArcIV(key).encode(string)
- #print 'encrypted=', hexText(encrypted)
- elif revision == 5:
- iv = os_urandom(16)
- encrypter = pyaes.Encrypter(pyaes.AESModeOfOperationCBC(key, iv=iv))
-
- # pkcs7 style padding so that the size of the encrypted block is multiple of 16
- string_len = len(string)
- padding = ""
- padding_len = (16 - (string_len % 16)) if string_len > 16 else (16 - string_len)
- if padding_len > 0:
- padding = chr(padding_len) * padding_len
-
- if isinstance(string, str):
- string = (string + padding).encode("utf-8")
- else:
- string += asBytes(padding)
-
- encrypted = iv + encrypter.feed(string)
- encrypted += encrypter.feed()
- if DEBUG: print('encodePDF(%s,%s,%s,%s,%s)==>%s' % tuple([hexText(str(x)) for x in (key, objectNumber, generationNumber, string, revision,encrypted)]))
- return encrypted
- def equalityCheck(observed,expected,label):
- assert observed==expected,'%s\n expected=%s\n observed=%s' % (label,expected,observed)
- ######################################################################
- #
- # quick tests of algorithm, should be moved elsewhere
- #
- ######################################################################
- def test():
- # do a 40 bit example known to work in Acrobat Reader 4.0
- enc = StandardEncryption('User','Owner', strength=40)
- enc.prepare(None, overrideID='xxxxxxxxxxxxxxxx')
- expectedO = '<FA7F558FACF8205D25A7F1ABFA02629F707AE7B0211A2BB26F5DF4C30F684301>'
- expectedU = '<09F26CF46190AF8F93B304AD50C16B615DC43C228C9B2D2EA34951A80617B2B1>'
- expectedKey = '<BB2C00EB3D>' # 5 byte key = 40 bits
- equalityCheck(hexText(enc.O),expectedO, '40 bit O value')
- equalityCheck(hexText(enc.U),expectedU, '40 bit U value')
- equalityCheck(hexText(enc.key),expectedKey, '40 bit key value')
- # now for 128 bit example
- enc = StandardEncryption('userpass','ownerpass', strength=128)
- enc.prepare(None, overrideID = 'xxxxxxxxxxxxxxxx')
- expectedO = '<68E5704AC779A5F0CD89704406587A52F25BF61CADC56A0F8DB6C4DB0052534D>'
- expectedU = '<A9AE45CDE827FE0B7D6536267948836A00000000000000000000000000000000>'
- expectedKey = '<13DDE7585D9BE366C976DDD56AF541D1>' # 16 byte key = 128 bits
- equalityCheck(hexText(enc.O), expectedO, '128 bit O value')
- equalityCheck(hexText(enc.U), expectedU, '128 bit U value')
- equalityCheck(hexText(enc.key), expectedKey, '128 key value')
- ######################################################################
- #
- # These represent the higher level API functions
- #
- ######################################################################
- def encryptCanvas(canvas,
- userPassword, ownerPassword=None,
- canPrint=1, canModify=1, canCopy=1, canAnnotate=1,
- strength=40):
- "Applies encryption to the document being generated"
- enc = StandardEncryption(userPassword, ownerPassword,
- canPrint, canModify, canCopy, canAnnotate,
- strength=strength)
- canvas._doc.encrypt = enc
- # Platypus stuff needs work, sadly. I wanted to do it without affecting
- # needing changes to latest release.
- class EncryptionFlowable(StandardEncryption, Flowable):
- """Drop this in your Platypus story and it will set up the encryption options.
- If you do it multiple times, the last one before saving will win."""
- def wrap(self, availWidth, availHeight):
- return (0,0)
- def draw(self):
- encryptCanvas(self.canv,
- self.userPassword,
- self.ownerPassword,
- self.canPrint,
- self.canModify,
- self.canCopy,
- self.canAnnotate)
- ## I am thinking about this one. Needs a change to reportlab to
- ## work.
- def encryptDocTemplate(dt,
- userPassword, ownerPassword=None,
- canPrint=1, canModify=1, canCopy=1, canAnnotate=1,
- strength=40):
- "For use in Platypus. Call before build()."
- raise Exception("Not implemented yet")
- def encryptPdfInMemory(inputPDF,
- userPassword, ownerPassword=None,
- canPrint=1, canModify=1, canCopy=1, canAnnotate=1,
- strength=40):
- """accepts a PDF file 'as a byte array in memory'; return encrypted one.
- This is a high level convenience and does not touch the hard disk in any way.
- If you are encrypting the same file over and over again, it's better to use
- pageCatcher and cache the results."""
- try:
- from rlextra.pageCatcher.pageCatcher import storeFormsInMemory, restoreFormsInMemory
- except ImportError:
- raise ImportError('''reportlab.lib.pdfencrypt.encryptPdfInMemory failed because rlextra cannot be imported.
- See https://www.reportlab.com/downloads''')
- (bboxInfo, pickledForms) = storeFormsInMemory(inputPDF, all=1, BBoxes=1)
- names = list(bboxInfo.keys())
- firstPageSize = bboxInfo['PageForms0'][2:]
- #now make a new PDF document
- buf = getBytesIO()
- canv = Canvas(buf, pagesize=firstPageSize)
- # set a standard ID while debugging
- if CLOBBERID:
- canv._doc._ID = "[(xxxxxxxxxxxxxxxx)(xxxxxxxxxxxxxxxx)]"
- formNames = restoreFormsInMemory(pickledForms, canv)
- for formName in formNames:
- canv.setPageSize(bboxInfo[formName][2:])
- canv.doForm(formName)
- canv.showPage()
- encryptCanvas(canv,
- userPassword, ownerPassword,
- canPrint, canModify, canCopy, canAnnotate,
- strength=strength)
- canv.save()
- return buf.getvalue()
- def encryptPdfOnDisk(inputFileName, outputFileName,
- userPassword, ownerPassword=None,
- canPrint=1, canModify=1, canCopy=1, canAnnotate=1,
- strength=40):
- "Creates encrypted file OUTPUTFILENAME. Returns size in bytes."
- inputPDF = open(inputFileName, 'rb').read()
- outputPDF = encryptPdfInMemory(inputPDF,
- userPassword, ownerPassword,
- canPrint, canModify, canCopy, canAnnotate,
- strength=strength)
- open(outputFileName, 'wb').write(outputPDF)
- return len(outputPDF)
- def scriptInterp():
- sys_argv = sys.argv[:] # copy
- usage = """PDFENCRYPT USAGE:
- PdfEncrypt encrypts your PDF files.
- Line mode usage:
- % pdfencrypt.exe pdffile [-o ownerpassword] | [owner ownerpassword],
- \t[-u userpassword] | [user userpassword],
- \t[-p 1|0] | [printable 1|0],
- \t[-m 1|0] | [modifiable 1|0],
- \t[-c 1|0] | [copypastable 1|0],
- \t[-a 1|0] | [annotatable 1|0],
- \t[-s savefilename] | [savefile savefilename],
- \t[-v 1|0] | [verbose 1|0],
- \t[-e128], [encrypt128],
- \t[-h] | [help]
- -o or owner set the owner password.
- -u or user set the user password.
- -p or printable set the printable attribute (must be 1 or 0).
- -m or modifiable sets the modifiable attribute (must be 1 or 0).
- -c or copypastable sets the copypastable attribute (must be 1 or 0).
- -a or annotatable sets the annotatable attribute (must be 1 or 0).
- -s or savefile sets the name for the output PDF file
- -v or verbose prints useful output to the screen.
- (this defaults to 'pdffile_encrypted.pdf').
- '-e128' or 'encrypt128' allows you to use 128 bit encryption (in beta).
- '-e256' or 'encrypt256' allows you to use 256 bit encryption (in beta AES).
- -h or help prints this message.
- See PdfEncryptIntro.pdf for more information.
- """
- known_modes = ['-o', 'owner',
- '-u', 'user',
- '-p', 'printable',
- '-m', 'modifiable',
- '-c', 'copypastable',
- '-a', 'annotatable',
- '-s', 'savefile',
- '-v', 'verbose',
- '-h', 'help',
- '-e128', 'encrypt128',
- '-e256', 'encryptAES',
- ]
- OWNER = ''
- USER = ''
- PRINTABLE = 1
- MODIFIABLE = 1
- COPYPASTABLE = 1
- ANNOTATABLE = 1
- SAVEFILE = 'encrypted.pdf'
- #try:
- caller = sys_argv[0] # may be required later - eg if called by security.py
- argv = list(sys_argv)[1:]
- if len(argv)>0:
- if argv[0] == '-h' or argv[0] == 'help':
- print(usage)
- return
- if len(argv)<2:
- raise ValueError("Must include a filename and one or more arguments!")
- if argv[0] not in known_modes:
- infile = argv[0]
- argv = argv[1:]
- if not os.path.isfile(infile):
- raise ValueError("Can't open input file '%s'!" % infile)
- else:
- raise ValueError("First argument must be name of the PDF input file!")
- # meaningful name at this stage
- STRENGTH = 40
- for (s,_a) in ((128,('-e128','encrypt128')),(256,('-e256','encrypt256'))):
- for a in _a:
- if a in argv:
- STRENGTH = s
- argv.remove(a)
- if ('-v' in argv) or ('verbose' in argv):
- if '-v' in argv:
- pos = argv.index('-v')
- arg = "-v"
- elif 'verbose' in argv:
- pos = argv.index('verbose')
- arg = "verbose"
- try:
- verbose = int(argv[pos+1])
- except:
- verbose = 1
- argv.remove(argv[pos+1])
- argv.remove(arg)
- else:
- from reportlab.rl_config import verbose
- #argument, valid license variable, invalid license variable, text for print
- arglist = (('-o', 'OWNER', OWNER, 'Owner password'),
- ('owner', 'OWNER', OWNER, 'Owner password'),
- ('-u', 'USER', USER, 'User password'),
- ('user', 'USER', USER, 'User password'),
- ('-p', 'PRINTABLE', PRINTABLE, "'Printable'"),
- ('printable', 'PRINTABLE', PRINTABLE, "'Printable'"),
- ('-m', 'MODIFIABLE', MODIFIABLE, "'Modifiable'"),
- ('modifiable', 'MODIFIABLE', MODIFIABLE, "'Modifiable'"),
- ('-c', 'COPYPASTABLE', COPYPASTABLE, "'Copypastable'"),
- ('copypastable', 'COPYPASTABLE', COPYPASTABLE, "'Copypastable'"),
- ('-a', 'ANNOTATABLE', ANNOTATABLE, "'Annotatable'"),
- ('annotatable', 'ANNOTATABLE', ANNOTATABLE, "'Annotatable'"),
- ('-s', 'SAVEFILE', SAVEFILE, "Output file"),
- ('savefile', 'SAVEFILE', SAVEFILE, "Output file"),
- )
- binaryrequired = ('-p', 'printable', '-m', 'modifiable', 'copypastable', '-c', 'annotatable', '-a')
- for thisarg in arglist:
- if thisarg[0] in argv:
- pos = argv.index(thisarg[0])
- if thisarg[0] in binaryrequired:
- if argv[pos+1] not in ('1', '0'):
- raise ValueError("%s value must be either '1' or '0'!" % thisarg[1])
- try:
- if argv[pos+1] not in known_modes:
- if thisarg[0] in binaryrequired:
- exec(thisarg[1] +' = int(argv[pos+1])',vars())
- else:
- exec(thisarg[1] +' = argv[pos+1]',vars())
- if verbose:
- print("%s set to: '%s'." % (thisarg[3], argv[pos+1]))
- argv.remove(argv[pos+1])
- argv.remove(thisarg[0])
- except:
- raise "Unable to set %s." % thisarg[3]
- if verbose>4:
- #useful if feeling paranoid and need to double check things at this point...
- print("\ninfile:", infile)
- print("STRENGTH:", STRENGTH)
- print("SAVEFILE:", SAVEFILE)
- print("USER:", USER)
- print("OWNER:", OWNER)
- print("PRINTABLE:", PRINTABLE)
- print("MODIFIABLE:", MODIFIABLE)
- print("COPYPASTABLE:", COPYPASTABLE)
- print("ANNOTATABLE:", ANNOTATABLE)
- print("SAVEFILE:", SAVEFILE)
- print("VERBOSE:", verbose)
- if SAVEFILE == 'encrypted.pdf':
- if infile[-4:] == '.pdf' or infile[-4:] == '.PDF':
- tinfile = infile[:-4]
- else:
- tinfile = infile
- SAVEFILE = tinfile+"_encrypted.pdf"
- filesize = encryptPdfOnDisk(infile, SAVEFILE, USER, OWNER,
- PRINTABLE, MODIFIABLE, COPYPASTABLE, ANNOTATABLE,
- strength=STRENGTH)
- if verbose:
- print("wrote output file '%s'(%s bytes)\n owner password is '%s'\n user password is '%s'" % (SAVEFILE, filesize, OWNER, USER))
- if len(argv)>0:
- raise ValueError("\nUnrecognised arguments : %s\nknown arguments are:\n%s" % (str(argv)[1:-1], known_modes))
- else:
- print(usage)
- def main():
- from reportlab.rl_config import verbose
- scriptInterp()
- if __name__=="__main__": #NO RUNTESTS
- a = [x for x in sys.argv if x[:7]=='--debug']
- if a:
- sys.argv = [x for x in sys.argv if x[:7]!='--debug']
- DEBUG = len(a)
- if '--test' in sys.argv: test()
- else: main()
|