Changeset 775

Show
Ignore:
Timestamp:
12/14/06 12:47:48 (2 years ago)
Author:
mfenniak
Message:

Add support for encrypting PDF files. Poor support for different permission bits so far, but it works well.

Files:

Legend:

Unmodified
Added
Removed
Modified
Copied
Moved
  • pypdf/trunk/pyPdf/generic.py

    r758 r775  
    3636 
    3737import re 
    38 from utils import readNonWhitespace 
     38from utils import readNonWhitespace, RC4_encrypt 
    3939import filters 
    4040 
     
    104104        self.value = value 
    105105 
    106     def writeToStream(self, stream): 
     106    def writeToStream(self, stream, encryption_key): 
    107107        if self.value: 
    108108            stream.write("true") 
     
    122122 
    123123class ArrayObject(list, PdfObject): 
    124     def writeToStream(self, stream): 
     124    def writeToStream(self, stream, encryption_key): 
    125125        stream.write("[") 
    126126        for data in self: 
    127127            stream.write(" ") 
    128             data.writeToStream(stream
     128            data.writeToStream(stream, encryption_key
    129129        stream.write(" ]") 
    130130 
     
    173173        return not self.__eq__(other) 
    174174 
    175     def writeToStream(self, stream): 
     175    def writeToStream(self, stream, encryption_key): 
    176176        stream.write("%s %s R" % (self.idnum, self.generation)) 
    177177 
     
    200200 
    201201class FloatObject(float, PdfObject): 
    202     def writeToStream(self, stream): 
     202    def writeToStream(self, stream, encryption_key): 
    203203        stream.write(repr(self)) 
    204204 
     
    208208        int.__init__(self, value) 
    209209 
    210     def writeToStream(self, stream): 
     210    def writeToStream(self, stream, encryption_key): 
    211211        stream.write(repr(self)) 
    212212 
     
    227227 
    228228class StringObject(str, PdfObject): 
    229     def writeToStream(self, stream): 
     229    def writeToStream(self, stream, encryption_key): 
     230        string = self 
     231        if encryption_key: 
     232            string = RC4_encrypt(encryption_key, string) 
    230233        stream.write("(") 
    231         for c in self
     234        for c in string
    232235            if not c.isalnum() and not c.isspace(): 
    233236                stream.write("\\%03o" % ord(c)) 
     
    299302        str.__init__(self, data) 
    300303 
    301     def writeToStream(self, stream): 
     304    def writeToStream(self, stream, encryption_key): 
    302305        stream.write(self) 
    303306 
     
    319322        pass 
    320323 
    321     def writeToStream(self, stream): 
     324    def writeToStream(self, stream, encryption_key): 
    322325        stream.write("<<\n") 
    323326        for key, value in self.items(): 
    324             key.writeToStream(stream
     327            key.writeToStream(stream, encryption_key
    325328            stream.write(" ") 
    326             value.writeToStream(stream
     329            value.writeToStream(stream, encryption_key
    327330            stream.write("\n") 
    328331        stream.write(">>") 
     
    399402        self.decodedSelf = None 
    400403 
    401     def writeToStream(self, stream): 
     404    def writeToStream(self, stream, encryption_key): 
    402405        self[NameObject("/Length")] = NumberObject(len(self._data)) 
    403         DictionaryObject.writeToStream(self, stream
     406        DictionaryObject.writeToStream(self, stream, encryption_key
    404407        del self["/Length"] 
    405408        stream.write("\nstream\n") 
    406         stream.write(self._data) 
     409        data = self._data 
     410        if encryption_key: 
     411            data = RC4_encrypt(encryption_key, data) 
     412        stream.write(data) 
    407413        stream.write("\nendstream") 
    408414 
  • pypdf/trunk/pyPdf/pdf.py

    r774 r775  
    106106 
    107107    ## 
     108    # Encrypt this PDF file with the PDF Standard encryption handler. 
     109    # @param user_pwd The "user password", which allows for opening and reading 
     110    # the PDF file with the restrictions provided. 
     111    # @param owner_pwd The "owner password", which allows for opening the PDF 
     112    # files without any restrictions.  By default, the owner password is the 
     113    # same as the user password. 
     114    # @param use_128bit Boolean argument as to whether to use 128bit 
     115    # encryption.  When false, 40bit encryption will be used.  By default, this 
     116    # flag is on. 
     117    def encrypt(self, user_pwd, owner_pwd = None, use_128bit = True): 
     118        import md5, time, random 
     119        if owner_pwd == None: 
     120            owner_pwd = user_pwd 
     121        if use_128bit: 
     122            V = 2 
     123            rev = 3 
     124            keylen = 128 / 8 
     125        else: 
     126            V = 1 
     127            rev = 2 
     128            keylen = 40 / 8 
     129        # permit everything: 
     130        P = -1 
     131        O = StringObject(_alg33(owner_pwd, user_pwd, rev, keylen)) 
     132        ID_1 = md5.new(repr(time.time())).digest() 
     133        ID_2 = md5.new(repr(random.random())).digest() 
     134        self._ID = ArrayObject((StringObject(ID_1), StringObject(ID_2))) 
     135        if rev == 2: 
     136            U, key = _alg34(user_pwd, O, P, ID_1) 
     137        else: 
     138            assert rev == 3 
     139            U, key = _alg35(user_pwd, rev, keylen, O, P, ID_1, False) 
     140        encrypt = DictionaryObject() 
     141        encrypt[NameObject("/Filter")] = NameObject("/Standard") 
     142        encrypt[NameObject("/V")] = NumberObject(V) 
     143        if V == 2: 
     144            encrypt[NameObject("/Length")] = NumberObject(keylen * 8) 
     145        encrypt[NameObject("/R")] = NumberObject(rev) 
     146        encrypt[NameObject("/O")] = StringObject(O) 
     147        encrypt[NameObject("/U")] = StringObject(U) 
     148        encrypt[NameObject("/P")] = NumberObject(P) 
     149        self._encrypt = self._addObject(encrypt) 
     150        self._encrypt_key = key 
     151 
     152    ## 
    108153    # Writes the collection of pages added to this object out as a PDF file. 
    109154    # <p> 
     
    112157    # the write method, and the tell method, similar to a file object. 
    113158    def write(self, stream): 
     159        import struct, md5 
     160 
    114161        externalReferenceMap = {} 
    115162        self.stack = [] 
     
    121168        stream.write(self._header + "\n") 
    122169        for i in range(len(self._objects)): 
     170            idnum = (i + 1) 
    123171            obj = self._objects[i] 
    124172            object_positions.append(stream.tell()) 
    125             stream.write(str(i + 1) + " 0 obj\n") 
    126             obj.writeToStream(stream) 
     173            stream.write(str(idnum) + " 0 obj\n") 
     174            key = None 
     175            if idnum != self._encrypt.idnum and hasattr(self, "_encrypt_key"): 
     176                pack1 = struct.pack("<i", i + 1)[:3] 
     177                pack2 = struct.pack("<i", 0)[:2] 
     178                key = self._encrypt_key + pack1 + pack2 
     179                assert len(key) == (len(self._encrypt_key) + 5) 
     180                md5_hash = md5.new(key).digest() 
     181                key = md5_hash[:min(16, len(self._encrypt_key) + 5)] 
     182            obj.writeToStream(stream, key) 
    127183            stream.write("\nendobj\n") 
    128184 
     
    143199                NameObject("/Info"): self._info, 
    144200                }) 
    145         trailer.writeToStream(stream) 
     201        if hasattr(self, "_ID"): 
     202            trailer[NameObject("/ID")] = self._ID 
     203        if hasattr(self, "_encrypt"): 
     204            trailer[NameObject("/Encrypt")] = self._encrypt 
     205        trailer.writeToStream(stream, None) 
    146206         
    147207        # eof 
     
    540600        return line 
    541601 
    542     # ref: pdf1.8 spec section 3.5.2 algorithm 3.2 
    543     _encryption_padding = '\x28\xbf\x4e\x5e\x4e\x75\x8a\x41\x64\x00\x4e\x56' + \ 
    544             '\xff\xfa\x01\x08\x2e\x2e\x00\xb6\xd0\x68\x3e\x80\x2f\x0c' + \ 
    545             '\xa9\xfe\x64\x53\x69\x7a' 
    546  
    547     def _alg32(self, password, rev, keylen, metadata_encrypt=True): 
    548         import md5, struct 
    549         m = md5.new() 
    550         password = (password + self._encryption_padding)[:32] 
    551         m.update(password) 
    552         encrypt = self.safeGetObject(self.trailer['/Encrypt']) 
    553         owner_entry = self.safeGetObject(encrypt['/O']) 
    554         m.update(owner_entry) 
    555         p_entry = self.safeGetObject(encrypt['/P']) 
    556         p_entry = struct.pack('<i', p_entry) 
    557         m.update(p_entry) 
    558         id_entry = self.safeGetObject(self.trailer['/ID']) 
    559         id1_entry = self.safeGetObject(id_entry[0]) 
    560         m.update(id1_entry) 
    561         if rev >= 3 and not metadata_encrypt: 
    562             m.update("\xff\xff\xff\xff") 
    563         md5_hash = m.digest() 
    564         if rev >= 3: 
    565             for i in range(50): 
    566                 md5_hash = md5.new(md5_hash[:keylen]).digest() 
    567         return md5_hash[:keylen] 
    568  
    569     def _alg34(self, password): 
    570         key = self._alg32(password, 2, 5) 
    571         U = utils.RC4_encrypt(key, self._encryption_padding) 
    572         return U, key 
    573  
    574     def _alg33_1(self, password, rev, keylen): 
    575         import md5 
    576         m = md5.new() 
    577         password = (password + self._encryption_padding)[:32] 
    578         m.update(password) 
    579         md5_hash = m.digest() 
    580         if rev >= 3: 
    581             for i in range(50): 
    582                 md5_hash = md5.new(md5_hash).digest() 
    583         key = md5_hash[:keylen] 
    584         return key 
    585  
    586     def _alg35(self, password, rev, keylen, metadata_encrypt): 
    587         import md5 
    588         m = md5.new() 
    589         m.update(self._encryption_padding) 
    590         id_entry = self.safeGetObject(self.trailer['/ID']) 
    591         id1_entry = self.safeGetObject(id_entry[0]) 
    592         m.update(id1_entry) 
    593         md5_hash = m.digest() 
    594         key = self._alg32(password, rev, keylen) 
    595         val = utils.RC4_encrypt(key, md5_hash) 
    596         for i in range(1, 20): 
    597             new_key = '' 
    598             for l in range(len(key)): 
    599                 new_key += chr(ord(key[l]) ^ i) 
    600             val = utils.RC4_encrypt(new_key, val) 
    601         return val + ('\x00' * 16), key 
    602  
    603     def _authenticateUserPassword(self, password): 
    604         encrypt = self.safeGetObject(self.trailer['/Encrypt']) 
    605         rev = self.safeGetObject(encrypt['/R']) 
    606         if rev == 2: 
    607             U, key = self._alg34(password) 
    608         elif rev >= 3: 
    609             U, key = self._alg35(password, rev, self.safeGetObject(encrypt["/Length"]) / 8, 
    610                     self.safeGetObject(encrypt.get("/EncryptMetadata", False))) 
    611         real_U = self.safeGetObject(encrypt['/U']) 
    612         return U == real_U, key 
    613  
    614602    ## 
    615603    # When using an encrypted / secured PDF file with the PDF Standard 
     
    622610    # the correct decryption key that will allow the document to be used with 
    623611    # this library. 
     612    # <p> 
     613    # Stability: Added in v1.8, will exist for all future v1.x releases. 
    624614    # 
    625615    # @return 0 if the password failed, 1 if the password matched the user 
     
    651641            else: 
    652642                keylen = self.safeGetObject(encrypt['/Length']) / 8 
    653             key = self._alg33_1(password, rev, keylen) 
     643            key = _alg33_1(password, rev, keylen) 
    654644            real_O = self.safeGetObject(encrypt["/O"]) 
    655645            if rev == 2: 
     
    668658                return 2 
    669659        return 0 
     660 
     661    def _authenticateUserPassword(self, password): 
     662        encrypt = self.safeGetObject(self.trailer['/Encrypt']) 
     663        rev = self.safeGetObject(encrypt['/R']) 
     664        owner_entry = self.safeGetObject(encrypt['/O']) 
     665        p_entry = self.safeGetObject(encrypt['/P']) 
     666        id_entry = self.safeGetObject(self.trailer['/ID']) 
     667        id1_entry = self.safeGetObject(id_entry[0]) 
     668        if rev == 2: 
     669            U, key = _alg34(password, owner_entry, p_entry, id1_entry) 
     670        elif rev >= 3: 
     671            U, key = _alg35(password, rev, 
     672                    self.safeGetObject(encrypt["/Length"]) / 8, owner_entry, 
     673                    p_entry, id1_entry, 
     674                    self.safeGetObject(encrypt.get("/EncryptMetadata", False))) 
     675        real_U = self.safeGetObject(encrypt['/U']) 
     676        return U == real_U, key 
    670677 
    671678    def getIsEncrypted(self): 
     
    10671074        assert False 
    10681075 
     1076# ref: pdf1.8 spec section 3.5.2 algorithm 3.2 
     1077_encryption_padding = '\x28\xbf\x4e\x5e\x4e\x75\x8a\x41\x64\x00\x4e\x56' + \ 
     1078        '\xff\xfa\x01\x08\x2e\x2e\x00\xb6\xd0\x68\x3e\x80\x2f\x0c' + \ 
     1079        '\xa9\xfe\x64\x53\x69\x7a' 
     1080 
     1081def _alg32(password, rev, keylen, owner_entry, p_entry, id1_entry, metadata_encrypt=True): 
     1082    import md5, struct 
     1083    m = md5.new() 
     1084    password = (password + _encryption_padding)[:32] 
     1085    m.update(password) 
     1086    m.update(owner_entry) 
     1087    p_entry = struct.pack('<i', p_entry) 
     1088    m.update(p_entry) 
     1089    m.update(id1_entry) 
     1090    if rev >= 3 and not metadata_encrypt: 
     1091        m.update("\xff\xff\xff\xff") 
     1092    md5_hash = m.digest() 
     1093    if rev >= 3: 
     1094        for i in range(50): 
     1095            md5_hash = md5.new(md5_hash[:keylen]).digest() 
     1096    return md5_hash[:keylen] 
     1097 
     1098def _alg33(owner_pwd, user_pwd, rev, keylen): 
     1099    key = _alg33_1(owner_pwd, rev, keylen) 
     1100    user_pwd = (user_pwd + _encryption_padding)[:32] 
     1101    val = utils.RC4_encrypt(key, user_pwd) 
     1102    if rev >= 3: 
     1103        for i in range(1, 20): 
     1104            new_key = '' 
     1105            for l in range(len(key)): 
     1106                new_key += chr(ord(key[l]) ^ i) 
     1107            val = utils.RC4_encrypt(new_key, val) 
     1108    return val 
     1109 
     1110def _alg33_1(password, rev, keylen): 
     1111    import md5 
     1112    m = md5.new() 
     1113    password = (password + _encryption_padding)[:32] 
     1114    m.update(password) 
     1115    md5_hash = m.digest() 
     1116    if rev >= 3: 
     1117        for i in range(50): 
     1118            md5_hash = md5.new(md5_hash).digest() 
     1119    key = md5_hash[:keylen] 
     1120    return key 
     1121 
     1122def _alg34(password, owner_entry, p_entry, id1_entry): 
     1123    key = _alg32(password, 2, 5, owner_entry, p_entry, id1_entry) 
     1124    U = utils.RC4_encrypt(key, _encryption_padding) 
     1125    return U, key 
     1126 
     1127def _alg35(password, rev, keylen, owner_entry, p_entry, id1_entry, metadata_encrypt): 
     1128    import md5 
     1129    m = md5.new() 
     1130    m.update(_encryption_padding) 
     1131    m.update(id1_entry) 
     1132    md5_hash = m.digest() 
     1133    key = _alg32(password, rev, keylen, owner_entry, p_entry, id1_entry) 
     1134    val = utils.RC4_encrypt(key, md5_hash) 
     1135    for i in range(1, 20): 
     1136        new_key = '' 
     1137        for l in range(len(key)): 
     1138            new_key += chr(ord(key[l]) ^ i) 
     1139        val = utils.RC4_encrypt(new_key, val) 
     1140    return val + ('\x00' * 16), key 
    10691141 
    10701142#if __name__ == "__main__":