pdfencrypt.py 30 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776
  1. #copyright ReportLab Europe Limited. 2000-2016
  2. #see license.txt for license details
  3. __version__='3.3.0'
  4. """helpers for pdf encryption/decryption"""
  5. import sys, os, tempfile
  6. from binascii import hexlify, unhexlify
  7. from reportlab.lib.utils import getBytesIO, md5, asBytes, int2Byte, char2int, rawUnicode, rawBytes, asNative
  8. from reportlab.pdfgen.canvas import Canvas
  9. from reportlab.pdfbase import pdfutils
  10. from reportlab.pdfbase.pdfdoc import PDFObject
  11. from reportlab.platypus.flowables import Flowable
  12. from reportlab import rl_config
  13. try:
  14. import pyaes
  15. from hashlib import sha256
  16. except ImportError:
  17. pyaes = None
  18. def xorKey(num,key):
  19. "xor's each byte of the key with the number, which is <256"
  20. if num==0: return key
  21. return bytes(num^k for k in key)
  22. bytes3 = bytes
  23. #AR debug hooks - leaving in for now
  24. CLOBBERID = 0 # set a constant Doc ID to allow comparison with other software like iText
  25. CLOBBERPERMISSIONS = 0
  26. DEBUG = rl_config.debug # print stuff to trace calculations
  27. # permission bits
  28. reserved1 = 1 # bit 1 must be 0
  29. reserved2 = 1<<1 # bit 2 must be 0
  30. printable = 1<<2
  31. modifiable = 1<<3
  32. copypastable = 1<<4
  33. annotatable = 1<<5
  34. # others [7..32] are reserved, must be 1
  35. higherbits = 0
  36. for i in range(6,31):
  37. higherbits = higherbits | (1<<i)
  38. if rl_config.invariant:
  39. _os_random_x=0
  40. _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'
  41. def os_urandom(n):
  42. global _os_random_x
  43. b = [_os_random_b[(i+_os_random_x)%256] for i in range(n)]
  44. b = bytes(b)
  45. _os_random_x = (_os_random_x + n) % 256
  46. return b
  47. else:
  48. os_urandom = os.urandom
  49. # no encryption
  50. class StandardEncryption:
  51. prepared = 0
  52. def __init__(self, userPassword, ownerPassword=None, canPrint=1, canModify=1, canCopy=1, canAnnotate=1, strength=None):
  53. '''
  54. This class defines the encryption properties to be used while creating a pdf document.
  55. Once initiated, a StandardEncryption object can be applied to a Canvas or a BaseDocTemplate.
  56. The userPassword parameter sets the user password on the encrypted pdf.
  57. The ownerPassword parameter sets the owner password on the encrypted pdf.
  58. The boolean flags canPrint, canModify, canCopy, canAnnotate determine wether a user can
  59. perform the corresponding actions on the pdf when only a user password has been supplied.
  60. If the user supplies the owner password while opening the pdf, all actions can be performed regardless
  61. of the flags.
  62. Note that the security provided by these encryption settings (and even more so for the flags) is very weak.
  63. '''
  64. self.userPassword = userPassword
  65. if ownerPassword:
  66. self.ownerPassword = ownerPassword
  67. else:
  68. self.ownerPassword = userPassword
  69. if strength is None:
  70. strength = rl_config.encryptionStrength
  71. if strength == 40:
  72. self.revision = 2
  73. elif strength == 128:
  74. self.revision = 3
  75. elif strength == 256:
  76. if not pyaes:
  77. raise ValueError('strength==256 is not supported as package pyaes is not importable')
  78. self.revision = 5
  79. else:
  80. raise ValueError('Unknown encryption strength=%s' % repr(strength))
  81. self.canPrint = canPrint
  82. self.canModify = canModify
  83. self.canCopy = canCopy
  84. self.canAnnotate = canAnnotate
  85. self.O = self.U = self.P = self.key = self.OE = self.UE = self.Perms = None
  86. def setAllPermissions(self, value):
  87. self.canPrint = \
  88. self.canModify = \
  89. self.canCopy = \
  90. self.canAnnotate = value
  91. def permissionBits(self):
  92. p = 0
  93. if self.canPrint: p = p | printable
  94. if self.canModify: p = p | modifiable
  95. if self.canCopy: p = p | copypastable
  96. if self.canAnnotate: p = p | annotatable
  97. p = p | higherbits
  98. return p
  99. def encode(self, t):
  100. "encode a string, stream, text"
  101. if not self.prepared:
  102. raise ValueError("encryption not prepared!")
  103. if self.objnum is None:
  104. raise ValueError("not registered in PDF object")
  105. return encodePDF(self.key, self.objnum, self.version, t, revision=self.revision)
  106. def prepare(self, document, overrideID=None):
  107. # get ready to do encryption
  108. if DEBUG: print('StandardEncryption.prepare(...) - revision %d' % self.revision)
  109. if self.prepared:
  110. raise ValueError("encryption already prepared!")
  111. # get the unescaped string value of the document id (first array element).
  112. # we allow one to be passed in instead to permit reproducible tests
  113. # of our algorithm, but in real life overrideID will always be None
  114. if overrideID:
  115. internalID = overrideID
  116. else:
  117. externalID = document.ID() # initialize it...
  118. internalID = document.signature.digest()
  119. #AR debugging
  120. if CLOBBERID:
  121. internalID = "xxxxxxxxxxxxxxxx"
  122. if DEBUG:
  123. print('userPassword = %r' % self.userPassword)
  124. print('ownerPassword = %r' % self.ownerPassword)
  125. print('internalID = %r' % internalID)
  126. self.P = int(self.permissionBits() - 2**31)
  127. if CLOBBERPERMISSIONS: self.P = -44 # AR hack
  128. if DEBUG:
  129. print("self.P = %s" % repr(self.P))
  130. if self.revision == 5:
  131. # Init vectro for AES cipher (should be 16 bytes null array)
  132. iv = b'\x00' * 16
  133. # Random User salts
  134. uvs = os_urandom(8)
  135. uks = os_urandom(8)
  136. # the main encryption key
  137. self.key = asBytes(os_urandom(32))
  138. if DEBUG:
  139. print("uvs (hex) = %s" % hexText(uvs))
  140. print("uks (hex) = %s" % hexText(uks))
  141. print("self.key (hex) = %s" % hexText(self.key))
  142. # Calculate the sha-256 hash of the User password (U)
  143. md = sha256(asBytes(self.userPassword[:127]) + uvs)
  144. self.U = md.digest() + uvs + uks
  145. if DEBUG:
  146. print("self.U (hex) = %s" % hexText(self.U))
  147. # Calculate the User encryption key (UE)
  148. md = sha256(asBytes(self.userPassword[:127]) + uks)
  149. encrypter = pyaes.Encrypter(pyaes.AESModeOfOperationCBC(md.digest(), iv=iv))
  150. self.UE = encrypter.feed(self.key)
  151. self.UE += encrypter.feed()
  152. if DEBUG:
  153. print("self.UE (hex) = %s" % hexText(self.UE))
  154. # Random Owner salts
  155. ovs = os_urandom(8)
  156. oks = os_urandom(8)
  157. # Calculate the hash of the Owner password (U)
  158. md = sha256(asBytes(self.ownerPassword[:127]) + ovs + self.U )
  159. self.O = md.digest() + ovs + oks
  160. if DEBUG:
  161. print("self.O (hex) = %s" % hexText(self.O))
  162. # Calculate the User encryption key (OE)
  163. md = sha256(asBytes(self.ownerPassword[:127]) + oks + self.U)
  164. encrypter = pyaes.Encrypter(pyaes.AESModeOfOperationCBC(md.digest(), iv=iv))
  165. self.OE = encrypter.feed(self.key)
  166. self.OE += encrypter.feed()
  167. if DEBUG:
  168. print("self.OE (hex) = %s" % hexText(self.OE))
  169. # Compute permissions array
  170. permsarr = [
  171. self.P & 0xFF, # store the permission value in the first 32-bits
  172. self.P >> 8 & 0xFF,
  173. self.P >> 16 & 0xFF,
  174. self.P >> 24 & 0xFF,
  175. 0xFF,
  176. 0xFF,
  177. 0xFF,
  178. 0xFF,
  179. ord('T'), # 'T' if EncryptMetaData is True (default), 'F' otherwise
  180. ord('a'), # a, d, b are magic values
  181. ord('d'),
  182. ord('b'),
  183. 0x01, # trailing zeros will be ignored
  184. 0x01,
  185. 0x01,
  186. 0x01
  187. ]
  188. # the permission array should be enrypted in the Perms field
  189. encrypter = pyaes.Encrypter(pyaes.AESModeOfOperationCBC(self.key, iv=iv))
  190. self.Perms = encrypter.feed(bytes3(permsarr))
  191. self.Perms += encrypter.feed()
  192. if DEBUG:
  193. print("self.Perms (hex) = %s" % hexText(self.Perms))
  194. elif self.revision in (2, 3):
  195. self.O = computeO(self.userPassword, self.ownerPassword, self.revision)
  196. if DEBUG:
  197. print("self.O (as hex) = %s" % hexText(self.O))
  198. #print "\nself.O", self.O, repr(self.O)
  199. self.key = encryptionkey(self.userPassword, self.O, self.P, internalID, revision=self.revision)
  200. if DEBUG:
  201. print("self.key (hex) = %s" % hexText(self.key))
  202. self.U = computeU(self.key, revision=self.revision, documentId=internalID)
  203. if DEBUG:
  204. print("self.U (as hex) = %s" % hexText(self.U))
  205. self.objnum = self.version = None
  206. self.prepared = 1
  207. def register(self, objnum, version):
  208. # enter a new direct object
  209. if not self.prepared:
  210. raise ValueError("encryption not prepared!")
  211. self.objnum = objnum
  212. self.version = version
  213. def info(self):
  214. # the representation of self in file if any (should be None or PDFDict)
  215. if not self.prepared:
  216. raise ValueError("encryption not prepared!")
  217. return StandardEncryptionDictionary(O=self.O, OE=self.OE, U=self.U, UE=self.UE, P=self.P, Perms=self.Perms, revision=self.revision)
  218. class StandardEncryptionDictionary(PDFObject):
  219. __RefOnly__ = 1
  220. def __init__(self, O, OE, U, UE, P, Perms, revision):
  221. self.O, self.OE, self.U, self.UE, self.P, self.Perms = O, OE, U, UE, P, Perms
  222. self.revision = revision
  223. def format(self, document):
  224. # use a dummy document to bypass encryption
  225. from reportlab.pdfbase.pdfdoc import DummyDoc, PDFDictionary, PDFString, PDFName
  226. dummy = DummyDoc()
  227. dict = {"Filter": PDFName("Standard"),
  228. "O": hexText(self.O),
  229. "U": hexText(self.U),
  230. "P": self.P}
  231. if self.revision == 5:
  232. dict['Length'] = 256
  233. dict['R'] = 5
  234. dict['V'] = 5
  235. dict['O'] = hexText(self.O)
  236. dict['U'] = hexText(self.U)
  237. dict['OE'] = hexText(self.OE)
  238. dict['UE'] = hexText(self.UE)
  239. dict['Perms'] = hexText(self.Perms)
  240. dict['StrF'] = PDFName("StdCF")
  241. dict['StmF'] = PDFName("StdCF")
  242. stdcf = {
  243. "Length": 32,
  244. "AuthEvent": PDFName("DocOpen"),
  245. "CFM": PDFName("AESV3")
  246. }
  247. cf = {
  248. "StdCF": PDFDictionary(stdcf)
  249. }
  250. dict['CF'] = PDFDictionary(cf)
  251. elif self.revision == 3:
  252. dict['Length'] = 128
  253. dict['R'] = 3
  254. dict['V'] = 2
  255. else:
  256. dict['R'] = 2
  257. dict['V'] = 1
  258. pdfdict = PDFDictionary(dict)
  259. return pdfdict.format(dummy)
  260. # from pdf spec
  261. padding = """
  262. 28 BF 4E 5E 4E 75 8A 41 64 00 4E 56 FF FA 01 08
  263. 2E 2E 00 B6 D0 68 3E 80 2F 0C A9 FE 64 53 69 7A
  264. """
  265. def hexText(text):
  266. "a legitimate way to show strings in PDF"
  267. return '<%s>' % asNative(hexlify(rawBytes(text))).upper()
  268. def unHexText(hexText):
  269. equalityCheck(hexText[0], '<', 'bad hex text')
  270. equalityCheck(hexText[-1], '>', 'bad hex text')
  271. return unhexlify(hexText[1:-1])
  272. PadString = rawBytes(''.join(chr(int(c, 16)) for c in padding.strip().split()))
  273. def checkRevision(revision):
  274. if revision is None:
  275. strength = rl_config.encryptionStrength
  276. if strength == 40:
  277. revision = 2
  278. elif strength == 128:
  279. revision = 3
  280. elif strength == 256:
  281. if not pyaes:
  282. raise ValueError('strength==256 is not supported as package pyaes is not importable')
  283. revision = 5
  284. else:
  285. raise ValueError('Unknown encryption strength=%s' % repr(strength))
  286. return revision
  287. def encryptionkey(password, OwnerKey, Permissions, FileId1, revision=None):
  288. revision = checkRevision(revision)
  289. # FileId1 is first string of the fileid array
  290. # add padding string
  291. #AR force same as iText example
  292. #Permissions = -1836 #int(Permissions - 2**31)
  293. password = asBytes(password) + PadString
  294. # truncate to 32 bytes
  295. password = password[:32]
  296. # translate permissions to string, low order byte first
  297. p = Permissions# + 2**32L
  298. permissionsString = b""
  299. for i in range(4):
  300. byte = (p & 0xff) # seems to match what iText does
  301. p = p>>8
  302. permissionsString += int2Byte(byte % 256)
  303. hash = md5(asBytes(password))
  304. hash.update(asBytes(OwnerKey))
  305. hash.update(asBytes(permissionsString))
  306. hash.update(asBytes(FileId1))
  307. md5output = hash.digest()
  308. if revision==2:
  309. key = md5output[:5]
  310. elif revision==3: #revision 3 algorithm - loop 50 times
  311. for x in range(50):
  312. md5output = md5(md5output).digest()
  313. key = md5output[:16]
  314. if DEBUG: print('encryptionkey(%s,%s,%s,%s,%s)==>%s' % tuple([hexText(str(x)) for x in (password, OwnerKey, Permissions, FileId1, revision, key)]))
  315. return key
  316. def computeO(userPassword, ownerPassword, revision):
  317. from reportlab.lib.arciv import ArcIV
  318. #print 'digest of hello is %s' % md5('hello').digest()
  319. assert revision in (2,3), 'Unknown algorithm revision %s' % revision
  320. if not ownerPassword:
  321. ownerPassword = userPassword
  322. ownerPad = asBytes(ownerPassword) + PadString
  323. ownerPad = ownerPad[0:32]
  324. password = asBytes(userPassword) + PadString
  325. userPad = password[:32]
  326. digest = md5(ownerPad).digest()
  327. 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))
  328. if revision == 2:
  329. O = ArcIV(digest[:5]).encode(userPad)
  330. elif revision == 3:
  331. for i in range(50):
  332. digest = md5(digest).digest()
  333. digest = digest[:16]
  334. O = userPad
  335. for i in range(20):
  336. thisKey = xorKey(i, digest)
  337. O = ArcIV(thisKey).encode(O)
  338. if DEBUG: print('computeO(%s,%s,%s)==>%s' % tuple([hexText(str(x)) for x in (userPassword, ownerPassword, revision,O)]))
  339. return O
  340. def computeU(encryptionkey, encodestring=PadString,revision=None,documentId=None):
  341. revision = checkRevision(revision)
  342. from reportlab.lib.arciv import ArcIV
  343. if revision == 2:
  344. result = ArcIV(encryptionkey).encode(encodestring)
  345. elif revision == 3:
  346. assert documentId is not None, "Revision 3 algorithm needs the document ID!"
  347. h = md5(PadString)
  348. h.update(rawBytes(documentId))
  349. tmp = h.digest()
  350. tmp = ArcIV(encryptionkey).encode(tmp)
  351. for n in range(1,20):
  352. thisKey = xorKey(n, encryptionkey)
  353. tmp = ArcIV(thisKey).encode(tmp)
  354. while len(tmp) < 32:
  355. tmp += b'\0'
  356. result = tmp
  357. if DEBUG: print('computeU(%s,%s,%s,%s)==>%s' % tuple([hexText(str(x)) for x in (encryptionkey, encodestring,revision,documentId,result)]))
  358. return result
  359. def checkU(encryptionkey, U):
  360. decoded = computeU(encryptionkey, U)
  361. #print len(decoded), len(U), len(PadString)
  362. if decoded!=PadString:
  363. if len(decoded)!=len(PadString):
  364. raise ValueError("lengths don't match! (password failed)")
  365. raise ValueError("decode of U doesn't match fixed padstring (password failed)")
  366. def encodePDF(key, objectNumber, generationNumber, string, revision=None):
  367. "Encodes a string or stream"
  368. revision = checkRevision(revision)
  369. #print 'encodePDF (%s, %d, %d, %s)' % (hexText(key), objectNumber, generationNumber, string)
  370. # extend 3 bytes of the object Number, low byte first
  371. if revision in (2,3):
  372. newkey = key
  373. n = objectNumber
  374. for i in range(3):
  375. newkey += int2Byte(n & 0xff)
  376. n = n>>8
  377. # extend 2 bytes of the generationNumber
  378. n = generationNumber
  379. for i in range(2):
  380. newkey += int2Byte(n & 0xff)
  381. n = n>>8
  382. md5output = md5(newkey).digest()
  383. if revision == 2:
  384. key = md5output[:10]
  385. elif revision == 3:
  386. key = md5output #all 16 bytes
  387. from reportlab.lib.arciv import ArcIV
  388. encrypted = ArcIV(key).encode(string)
  389. #print 'encrypted=', hexText(encrypted)
  390. elif revision == 5:
  391. iv = os_urandom(16)
  392. encrypter = pyaes.Encrypter(pyaes.AESModeOfOperationCBC(key, iv=iv))
  393. # pkcs7 style padding so that the size of the encrypted block is multiple of 16
  394. string_len = len(string)
  395. padding = ""
  396. padding_len = (16 - (string_len % 16)) if string_len > 16 else (16 - string_len)
  397. if padding_len > 0:
  398. padding = chr(padding_len) * padding_len
  399. if isinstance(string, str):
  400. string = (string + padding).encode("utf-8")
  401. else:
  402. string += asBytes(padding)
  403. encrypted = iv + encrypter.feed(string)
  404. encrypted += encrypter.feed()
  405. if DEBUG: print('encodePDF(%s,%s,%s,%s,%s)==>%s' % tuple([hexText(str(x)) for x in (key, objectNumber, generationNumber, string, revision,encrypted)]))
  406. return encrypted
  407. def equalityCheck(observed,expected,label):
  408. assert observed==expected,'%s\n expected=%s\n observed=%s' % (label,expected,observed)
  409. ######################################################################
  410. #
  411. # quick tests of algorithm, should be moved elsewhere
  412. #
  413. ######################################################################
  414. def test():
  415. # do a 40 bit example known to work in Acrobat Reader 4.0
  416. enc = StandardEncryption('User','Owner', strength=40)
  417. enc.prepare(None, overrideID='xxxxxxxxxxxxxxxx')
  418. expectedO = '<FA7F558FACF8205D25A7F1ABFA02629F707AE7B0211A2BB26F5DF4C30F684301>'
  419. expectedU = '<09F26CF46190AF8F93B304AD50C16B615DC43C228C9B2D2EA34951A80617B2B1>'
  420. expectedKey = '<BB2C00EB3D>' # 5 byte key = 40 bits
  421. equalityCheck(hexText(enc.O),expectedO, '40 bit O value')
  422. equalityCheck(hexText(enc.U),expectedU, '40 bit U value')
  423. equalityCheck(hexText(enc.key),expectedKey, '40 bit key value')
  424. # now for 128 bit example
  425. enc = StandardEncryption('userpass','ownerpass', strength=128)
  426. enc.prepare(None, overrideID = 'xxxxxxxxxxxxxxxx')
  427. expectedO = '<68E5704AC779A5F0CD89704406587A52F25BF61CADC56A0F8DB6C4DB0052534D>'
  428. expectedU = '<A9AE45CDE827FE0B7D6536267948836A00000000000000000000000000000000>'
  429. expectedKey = '<13DDE7585D9BE366C976DDD56AF541D1>' # 16 byte key = 128 bits
  430. equalityCheck(hexText(enc.O), expectedO, '128 bit O value')
  431. equalityCheck(hexText(enc.U), expectedU, '128 bit U value')
  432. equalityCheck(hexText(enc.key), expectedKey, '128 key value')
  433. ######################################################################
  434. #
  435. # These represent the higher level API functions
  436. #
  437. ######################################################################
  438. def encryptCanvas(canvas,
  439. userPassword, ownerPassword=None,
  440. canPrint=1, canModify=1, canCopy=1, canAnnotate=1,
  441. strength=40):
  442. "Applies encryption to the document being generated"
  443. enc = StandardEncryption(userPassword, ownerPassword,
  444. canPrint, canModify, canCopy, canAnnotate,
  445. strength=strength)
  446. canvas._doc.encrypt = enc
  447. # Platypus stuff needs work, sadly. I wanted to do it without affecting
  448. # needing changes to latest release.
  449. class EncryptionFlowable(StandardEncryption, Flowable):
  450. """Drop this in your Platypus story and it will set up the encryption options.
  451. If you do it multiple times, the last one before saving will win."""
  452. def wrap(self, availWidth, availHeight):
  453. return (0,0)
  454. def draw(self):
  455. encryptCanvas(self.canv,
  456. self.userPassword,
  457. self.ownerPassword,
  458. self.canPrint,
  459. self.canModify,
  460. self.canCopy,
  461. self.canAnnotate)
  462. ## I am thinking about this one. Needs a change to reportlab to
  463. ## work.
  464. def encryptDocTemplate(dt,
  465. userPassword, ownerPassword=None,
  466. canPrint=1, canModify=1, canCopy=1, canAnnotate=1,
  467. strength=40):
  468. "For use in Platypus. Call before build()."
  469. raise Exception("Not implemented yet")
  470. def encryptPdfInMemory(inputPDF,
  471. userPassword, ownerPassword=None,
  472. canPrint=1, canModify=1, canCopy=1, canAnnotate=1,
  473. strength=40):
  474. """accepts a PDF file 'as a byte array in memory'; return encrypted one.
  475. This is a high level convenience and does not touch the hard disk in any way.
  476. If you are encrypting the same file over and over again, it's better to use
  477. pageCatcher and cache the results."""
  478. try:
  479. from rlextra.pageCatcher.pageCatcher import storeFormsInMemory, restoreFormsInMemory
  480. except ImportError:
  481. raise ImportError('''reportlab.lib.pdfencrypt.encryptPdfInMemory failed because rlextra cannot be imported.
  482. See https://www.reportlab.com/downloads''')
  483. (bboxInfo, pickledForms) = storeFormsInMemory(inputPDF, all=1, BBoxes=1)
  484. names = list(bboxInfo.keys())
  485. firstPageSize = bboxInfo['PageForms0'][2:]
  486. #now make a new PDF document
  487. buf = getBytesIO()
  488. canv = Canvas(buf, pagesize=firstPageSize)
  489. # set a standard ID while debugging
  490. if CLOBBERID:
  491. canv._doc._ID = "[(xxxxxxxxxxxxxxxx)(xxxxxxxxxxxxxxxx)]"
  492. formNames = restoreFormsInMemory(pickledForms, canv)
  493. for formName in formNames:
  494. canv.setPageSize(bboxInfo[formName][2:])
  495. canv.doForm(formName)
  496. canv.showPage()
  497. encryptCanvas(canv,
  498. userPassword, ownerPassword,
  499. canPrint, canModify, canCopy, canAnnotate,
  500. strength=strength)
  501. canv.save()
  502. return buf.getvalue()
  503. def encryptPdfOnDisk(inputFileName, outputFileName,
  504. userPassword, ownerPassword=None,
  505. canPrint=1, canModify=1, canCopy=1, canAnnotate=1,
  506. strength=40):
  507. "Creates encrypted file OUTPUTFILENAME. Returns size in bytes."
  508. inputPDF = open(inputFileName, 'rb').read()
  509. outputPDF = encryptPdfInMemory(inputPDF,
  510. userPassword, ownerPassword,
  511. canPrint, canModify, canCopy, canAnnotate,
  512. strength=strength)
  513. open(outputFileName, 'wb').write(outputPDF)
  514. return len(outputPDF)
  515. def scriptInterp():
  516. sys_argv = sys.argv[:] # copy
  517. usage = """PDFENCRYPT USAGE:
  518. PdfEncrypt encrypts your PDF files.
  519. Line mode usage:
  520. % pdfencrypt.exe pdffile [-o ownerpassword] | [owner ownerpassword],
  521. \t[-u userpassword] | [user userpassword],
  522. \t[-p 1|0] | [printable 1|0],
  523. \t[-m 1|0] | [modifiable 1|0],
  524. \t[-c 1|0] | [copypastable 1|0],
  525. \t[-a 1|0] | [annotatable 1|0],
  526. \t[-s savefilename] | [savefile savefilename],
  527. \t[-v 1|0] | [verbose 1|0],
  528. \t[-e128], [encrypt128],
  529. \t[-h] | [help]
  530. -o or owner set the owner password.
  531. -u or user set the user password.
  532. -p or printable set the printable attribute (must be 1 or 0).
  533. -m or modifiable sets the modifiable attribute (must be 1 or 0).
  534. -c or copypastable sets the copypastable attribute (must be 1 or 0).
  535. -a or annotatable sets the annotatable attribute (must be 1 or 0).
  536. -s or savefile sets the name for the output PDF file
  537. -v or verbose prints useful output to the screen.
  538. (this defaults to 'pdffile_encrypted.pdf').
  539. '-e128' or 'encrypt128' allows you to use 128 bit encryption (in beta).
  540. '-e256' or 'encrypt256' allows you to use 256 bit encryption (in beta AES).
  541. -h or help prints this message.
  542. See PdfEncryptIntro.pdf for more information.
  543. """
  544. known_modes = ['-o', 'owner',
  545. '-u', 'user',
  546. '-p', 'printable',
  547. '-m', 'modifiable',
  548. '-c', 'copypastable',
  549. '-a', 'annotatable',
  550. '-s', 'savefile',
  551. '-v', 'verbose',
  552. '-h', 'help',
  553. '-e128', 'encrypt128',
  554. '-e256', 'encryptAES',
  555. ]
  556. OWNER = ''
  557. USER = ''
  558. PRINTABLE = 1
  559. MODIFIABLE = 1
  560. COPYPASTABLE = 1
  561. ANNOTATABLE = 1
  562. SAVEFILE = 'encrypted.pdf'
  563. #try:
  564. caller = sys_argv[0] # may be required later - eg if called by security.py
  565. argv = list(sys_argv)[1:]
  566. if len(argv)>0:
  567. if argv[0] == '-h' or argv[0] == 'help':
  568. print(usage)
  569. return
  570. if len(argv)<2:
  571. raise ValueError("Must include a filename and one or more arguments!")
  572. if argv[0] not in known_modes:
  573. infile = argv[0]
  574. argv = argv[1:]
  575. if not os.path.isfile(infile):
  576. raise ValueError("Can't open input file '%s'!" % infile)
  577. else:
  578. raise ValueError("First argument must be name of the PDF input file!")
  579. # meaningful name at this stage
  580. STRENGTH = 40
  581. for (s,_a) in ((128,('-e128','encrypt128')),(256,('-e256','encrypt256'))):
  582. for a in _a:
  583. if a in argv:
  584. STRENGTH = s
  585. argv.remove(a)
  586. if ('-v' in argv) or ('verbose' in argv):
  587. if '-v' in argv:
  588. pos = argv.index('-v')
  589. arg = "-v"
  590. elif 'verbose' in argv:
  591. pos = argv.index('verbose')
  592. arg = "verbose"
  593. try:
  594. verbose = int(argv[pos+1])
  595. except:
  596. verbose = 1
  597. argv.remove(argv[pos+1])
  598. argv.remove(arg)
  599. else:
  600. from reportlab.rl_config import verbose
  601. #argument, valid license variable, invalid license variable, text for print
  602. arglist = (('-o', 'OWNER', OWNER, 'Owner password'),
  603. ('owner', 'OWNER', OWNER, 'Owner password'),
  604. ('-u', 'USER', USER, 'User password'),
  605. ('user', 'USER', USER, 'User password'),
  606. ('-p', 'PRINTABLE', PRINTABLE, "'Printable'"),
  607. ('printable', 'PRINTABLE', PRINTABLE, "'Printable'"),
  608. ('-m', 'MODIFIABLE', MODIFIABLE, "'Modifiable'"),
  609. ('modifiable', 'MODIFIABLE', MODIFIABLE, "'Modifiable'"),
  610. ('-c', 'COPYPASTABLE', COPYPASTABLE, "'Copypastable'"),
  611. ('copypastable', 'COPYPASTABLE', COPYPASTABLE, "'Copypastable'"),
  612. ('-a', 'ANNOTATABLE', ANNOTATABLE, "'Annotatable'"),
  613. ('annotatable', 'ANNOTATABLE', ANNOTATABLE, "'Annotatable'"),
  614. ('-s', 'SAVEFILE', SAVEFILE, "Output file"),
  615. ('savefile', 'SAVEFILE', SAVEFILE, "Output file"),
  616. )
  617. binaryrequired = ('-p', 'printable', '-m', 'modifiable', 'copypastable', '-c', 'annotatable', '-a')
  618. for thisarg in arglist:
  619. if thisarg[0] in argv:
  620. pos = argv.index(thisarg[0])
  621. if thisarg[0] in binaryrequired:
  622. if argv[pos+1] not in ('1', '0'):
  623. raise ValueError("%s value must be either '1' or '0'!" % thisarg[1])
  624. try:
  625. if argv[pos+1] not in known_modes:
  626. if thisarg[0] in binaryrequired:
  627. exec(thisarg[1] +' = int(argv[pos+1])',vars())
  628. else:
  629. exec(thisarg[1] +' = argv[pos+1]',vars())
  630. if verbose:
  631. print("%s set to: '%s'." % (thisarg[3], argv[pos+1]))
  632. argv.remove(argv[pos+1])
  633. argv.remove(thisarg[0])
  634. except:
  635. raise "Unable to set %s." % thisarg[3]
  636. if verbose>4:
  637. #useful if feeling paranoid and need to double check things at this point...
  638. print("\ninfile:", infile)
  639. print("STRENGTH:", STRENGTH)
  640. print("SAVEFILE:", SAVEFILE)
  641. print("USER:", USER)
  642. print("OWNER:", OWNER)
  643. print("PRINTABLE:", PRINTABLE)
  644. print("MODIFIABLE:", MODIFIABLE)
  645. print("COPYPASTABLE:", COPYPASTABLE)
  646. print("ANNOTATABLE:", ANNOTATABLE)
  647. print("SAVEFILE:", SAVEFILE)
  648. print("VERBOSE:", verbose)
  649. if SAVEFILE == 'encrypted.pdf':
  650. if infile[-4:] == '.pdf' or infile[-4:] == '.PDF':
  651. tinfile = infile[:-4]
  652. else:
  653. tinfile = infile
  654. SAVEFILE = tinfile+"_encrypted.pdf"
  655. filesize = encryptPdfOnDisk(infile, SAVEFILE, USER, OWNER,
  656. PRINTABLE, MODIFIABLE, COPYPASTABLE, ANNOTATABLE,
  657. strength=STRENGTH)
  658. if verbose:
  659. print("wrote output file '%s'(%s bytes)\n owner password is '%s'\n user password is '%s'" % (SAVEFILE, filesize, OWNER, USER))
  660. if len(argv)>0:
  661. raise ValueError("\nUnrecognised arguments : %s\nknown arguments are:\n%s" % (str(argv)[1:-1], known_modes))
  662. else:
  663. print(usage)
  664. def main():
  665. from reportlab.rl_config import verbose
  666. scriptInterp()
  667. if __name__=="__main__": #NO RUNTESTS
  668. a = [x for x in sys.argv if x[:7]=='--debug']
  669. if a:
  670. sys.argv = [x for x in sys.argv if x[:7]!='--debug']
  671. DEBUG = len(a)
  672. if '--test' in sys.argv: test()
  673. else: main()