Example #1
0
    def _decrypt(self, stream):
        """
        Build the master key from header settings and key-hash list.
        
        Start reading from `stream` after the header and decrypt all the data.
        Remove padding as needed and feed into hashed block reader, set as
        in-buffer.
        """
        super(KDB4File, self)._decrypt(stream)

        ciphername = self.header.ciphers.get(self.header.CipherID,
                                             self.header.CipherID)
        if ciphername == 'AES':
            data = aes_cbc_decrypt(stream.read(), self.master_key,
                                   self.header.EncryptionIV)
            data = unpad(data)
        elif ciphername == 'Twofish':
            data = twofish_cbc_decrypt(stream.read(), self.master_key,
                                       self.header.EncryptionIV)
            data = unpad(data)
        else:
            raise IOError('Unsupported decryption type: %s' %
                          codecs.encode(ciphername, 'hex'))

        length = len(self.header.StreamStartBytes)
        if self.header.StreamStartBytes == data[:length]:
            # skip startbytes and wrap data in a hashed block io
            self.in_buffer = HashedBlockIO(initial_bytes=data[length:])
            # set successful decryption flag
            self.opened = True
        else:
            raise IOError('Master key invalid.')
Example #2
0
    def _encrypt(self):
        """
        Rebuild the master key from header settings and key-hash list. Encrypt
        the stream start bytes and the out-buffer formatted as hashed block
        stream with padding added as needed.
        """
        # rebuild master key from (possibly) updated header
        self._make_master_key()

        # make hashed block stream
        block_buffer = HashedBlockIO()
        block_buffer.write(self.out_buffer.read())
        # data is buffered in hashed block io, start a new one
        self.out_buffer = io.BytesIO()
        # write start bytes (for successful decrypt check)
        self.out_buffer.write(self.header.StreamStartBytes)
        # append blocked data to out-buffer
        block_buffer.write_block_stream(self.out_buffer)
        block_buffer.close()
        self.out_buffer.seek(0)

        # encrypt the whole thing with header settings and master key
        data = pad(self.out_buffer.read())
        self.out_buffer = aes_cbc_encrypt(data, self.master_key,
                                          self.header.EncryptionIV)
Example #3
0
    def _encrypt(self):
        """
        Rebuild the master key from header settings and key-hash list. Encrypt
        the stream start bytes and the out-buffer formatted as hashed block
        stream with padding added as needed.
        """
        # rebuild master key from (possibly) updated header
        self._make_master_key()

        # make hashed block stream
        block_buffer = HashedBlockIO()
        block_buffer.write(self.out_buffer.read())
        # data is buffered in hashed block io, start a new one
        self.out_buffer = io.BytesIO()
        # write start bytes (for successful decrypt check)
        self.out_buffer.write(self.header.StreamStartBytes)
        # append blocked data to out-buffer
        block_buffer.write_block_stream(self.out_buffer)
        block_buffer.close()
        self.out_buffer.seek(0)

        # encrypt the whole thing with header settings and master key
        ciphername = self.header.ciphers.get(self.header.CipherID,
                                             self.header.CipherID)
        if ciphername == 'AES':
            data = pad(self.out_buffer.read())
            self.out_buffer = aes_cbc_encrypt(data, self.master_key,
                                              self.header.EncryptionIV)
        elif ciphername == 'Twofish':
            data = pad(self.out_buffer.read())
            self.out_buffer = twofish_cbc_encrypt(data, self.master_key,
                                                  self.header.EncryptionIV)
        else:
            raise IOError('Unsupported encryption type: %s' %
                          codecs.encode(ciphername, 'hex'))
Example #4
0
 def _encrypt(self):
     """
     Rebuild the master key from header settings and key-hash list. Encrypt
     the stream start bytes and the out-buffer formatted as hashed block
     stream with padding added as needed.
     """
     # rebuild master key from (possibly) updated header
     self._make_master_key()
     
     # make hashed block stream
     block_buffer = HashedBlockIO()
     block_buffer.write(self.out_buffer.read())
     # data is buffered in hashed block io, start a new one
     self.out_buffer = io.BytesIO()
     # write start bytes (for successful decrypt check)
     self.out_buffer.write(self.header.StreamStartBytes)
     # append blocked data to out-buffer
     block_buffer.write_block_stream(self.out_buffer)
     block_buffer.close()
     self.out_buffer.seek(0)
     
     # encrypt the whole thing with header settings and master key
     data = pad(self.out_buffer.read())
     self.out_buffer = aes_cbc_encrypt(data, self.master_key, 
         self.header.EncryptionIV)
Example #5
0
    def _decrypt(self, stream):
        """
        Build the master key from header settings and key-hash list.
        
        Start reading from `stream` after the header and decrypt all the data.
        Remove padding as needed and feed into hashed block reader, set as
        in-buffer.
        """
        super(KDB4File, self)._decrypt(stream)

        data = aes_cbc_decrypt(stream.read(), self.master_key,
                               self.header.EncryptionIV)
        data = unpad(data)

        length = len(self.header.StreamStartBytes)
        if self.header.StreamStartBytes == data[:length]:
            # skip startbytes and wrap data in a hashed block io
            self.in_buffer = HashedBlockIO(initial_bytes=data[length:])
            # set successful decryption flag
            self.opened = True
        else:
            raise IOError('Master key invalid.')
Example #6
0
 def _decrypt(self, stream):
     """
     Build the master key from header settings and key-hash list.
     
     Start reading from `stream` after the header and decrypt all the data.
     Remove padding as needed and feed into hashed block reader, set as
     in-buffer.
     """
     super(KDB4File, self)._decrypt(stream)
     
     data = aes_cbc_decrypt(stream.read(), self.master_key, 
         self.header.EncryptionIV)
     data = unpad(data)
     
     length = len(self.header.StreamStartBytes)
     if self.header.StreamStartBytes == data[:length]:
         # skip startbytes and wrap data in a hashed block io
         self.in_buffer = HashedBlockIO(bytes=data[length:])
         # set successful decryption flag
         self.opened = True
     else:
         raise IOError('Master key invalid.')
Example #7
0
class KDB4File(KDBFile):
    def __init__(self, stream=None, **credentials):
        self.header = KDB4Header()
        KDBFile.__init__(self, stream, **credentials)

    def set_compression(self, flag=1):
        """Dis- (0) or enable (default: 1) compression"""
        if flag not in [0, 1]:
            raise ValueError('Compression flag can be 0 or 1.')
        self.header.CompressionFlags = flag

    # def set_comment(self, comment):
    # self.header.Comment = comment

    def read_from(self, stream):
        """
        Read, parse, decrypt, decompress a KeePass file from a stream.
        
        :arg stream: A file-like object (opened in 'rb' mode) or IO buffer
            containing a KeePass file.
        """
        super(KDB4File, self).read_from(stream)
        if self.header.CompressionFlags == 1:
            self._unzip()

    def write_to(self, stream):
        """
        Write the KeePass database back to a KeePass2 compatible file.
        
        :arg stream: A writeable file-like object or IO buffer.
        """
        if not self._is_file(stream):
            raise TypeError('Stream does not have the buffer interface.')

        self._write_header(stream)

    def _read_header(self, stream):
        """
        Parse the header and write the values into self.header. Also sets
        self.header_length.
        """
        # KeePass 2.07 has version 1.01,
        # 2.08 has 1.02,
        # 2.09 has 2.00, 2.10 has 2.02, 2.11 has 2.04,
        # 2.15 has 3.00.
        # The first 2 bytes are critical (i.e. loading will fail, if the
        # file version is too high), the last 2 bytes are informational.
        # TODO implement version check

        # the first header field starts at byte 12 after the signature
        stream.seek(12)

        while True:
            # field_id is a single byte
            field_id = stream_unpack(stream, None, 1, 'b')

            # field_id >10 is undefined
            if not field_id in self.header.fields.values():
                raise IOError('Unknown header field found.')

            # two byte (short) length of field data
            length = stream_unpack(stream, None, 2, 'h')
            if length > 0:
                data = stream_unpack(stream, None, length,
                                     '{}s'.format(length))
                self.header.b[field_id] = data

            # set position in data stream of end of header
            if field_id == 0:
                self.header_length = stream.tell()
                break

    def _header(self):
        # serialize header to stream
        header = bytearray()
        # write file signature
        header.extend(struct.pack('<II', *KDB4_SIGNATURE))
        # and version
        header.extend(struct.pack('<hh', 1, 3))

        field_ids = list(self.header.keys())
        field_ids.sort()
        field_ids.append(field_ids.pop(0))  # field_id 0 must be last
        for field_id in field_ids:
            value = self.header.b[field_id]
            length = len(value)
            header.extend(struct.pack('<b', field_id))
            header.extend(struct.pack('<h', length))
            header.extend(struct.pack('{}s'.format(length), value))

        return header

    def _write_header(self, stream):
        """Serialize the header fields from self.header into a byte stream, prefix
        with file signature and version before writing header and out-buffer
        to `stream`.

        Note, that `stream` is flushed, but not closed!"""
        header = self._header()

        # write header to stream
        stream.write(header)

        headerHash = base64.b64encode(sha256(header))
        self.obj_root.Meta.HeaderHash = headerHash

        # create HeaderHash if it does not exist
        if len(self.obj_root.Meta.xpath("HeaderHash")) < 1:
            etree.SubElement(self.obj_root.Meta, "HeaderHash")

        # reload out_buffer because we just changed the HeaderHash
        self.protect()
        self.out_buffer = io.BytesIO(self.pretty_print())

        # zip or not according to header setting
        if self.header.CompressionFlags == 1:
            self._zip()

        self._encrypt()

        # write encrypted block to stream
        stream.write(self.out_buffer)
        stream.flush()

    def _decrypt(self, stream):
        """
        Build the master key from header settings and key-hash list.
        
        Start reading from `stream` after the header and decrypt all the data.
        Remove padding as needed and feed into hashed block reader, set as
        in-buffer.
        """
        super(KDB4File, self)._decrypt(stream)

        ciphername = self.header.ciphers.get(self.header.CipherID,
                                             self.header.CipherID)
        if ciphername == 'AES':
            data = aes_cbc_decrypt(stream.read(), self.master_key,
                                   self.header.EncryptionIV)
            data = unpad(data)
        elif ciphername == 'Chacha20':
            data = chacha20_cbc_decrypt(stream.read(), self.master_key,
                                        self.header.EncryptionIV)
            data = unpad(data)
        elif ciphername == 'Twofish':
            data = twofish_cbc_decrypt(stream.read(), self.master_key,
                                       self.header.EncryptionIV)
            data = unpad(data)
        else:
            raise IOError('Unsupported decryption type: %s' %
                          codecs.encode(ciphername, 'hex'))

        length = len(self.header.StreamStartBytes)
        if self.header.StreamStartBytes == data[:length]:
            # skip startbytes and wrap data in a hashed block io
            self.in_buffer = HashedBlockIO(initial_bytes=data[length:])
            # set successful decryption flag
            self.opened = True
        else:
            raise IOError('Master key invalid.')

    def _encrypt(self):
        """
        Rebuild the master key from header settings and key-hash list. Encrypt
        the stream start bytes and the out-buffer formatted as hashed block
        stream with padding added as needed.
        """
        # rebuild master key from (possibly) updated header
        self._make_master_key()

        # make hashed block stream
        block_buffer = HashedBlockIO()
        block_buffer.write(self.out_buffer.read())
        # data is buffered in hashed block io, start a new one
        self.out_buffer = io.BytesIO()
        # write start bytes (for successful decrypt check)
        self.out_buffer.write(self.header.StreamStartBytes)
        # append blocked data to out-buffer
        block_buffer.write_block_stream(self.out_buffer)
        block_buffer.close()
        self.out_buffer.seek(0)

        # encrypt the whole thing with header settings and master key
        ciphername = self.header.ciphers.get(self.header.CipherID,
                                             self.header.CipherID)
        if ciphername == 'AES':
            data = pad(self.out_buffer.read())
            self.out_buffer = aes_cbc_encrypt(data, self.master_key,
                                              self.header.EncryptionIV)
        elif ciphername == 'Chacha20':
            data = pad(self.out_buffer.read())
            self.out_buffer = chacha20_cbc_encrypt(data, self.master_key,
                                                   self.header.EncryptionIV)
        elif ciphername == 'Twofish':
            data = pad(self.out_buffer.read())
            self.out_buffer = twofish_cbc_encrypt(data, self.master_key,
                                                  self.header.EncryptionIV)
        else:
            raise IOError('Unsupported encryption type: %s' %
                          codecs.encode(ciphername, 'hex'))

    def _unzip(self):
        """
        Inplace decompress in-buffer. Read/write position is moved to 0.
        """
        self.in_buffer.seek(0)
        d = zlib.decompressobj(16 + zlib.MAX_WBITS)
        self.in_buffer = io.BytesIO(d.decompress(self.in_buffer.read()))
        self.in_buffer.seek(0)

    def _zip(self):
        """
        Inplace compress out-buffer. Read/write position is moved to 0.
        """
        data = self.out_buffer.read()
        self.out_buffer = io.BytesIO()
        # note: compresslevel=6 seems to be important for kdb4!
        gz = gzip.GzipFile(fileobj=self.out_buffer, mode='wb', compresslevel=6)
        gz.write(data)
        gz.close()
        self.out_buffer.seek(0)

    def _make_master_key(self):
        """
        Make the master key by (1) combining the credentials to create 
        a composite hash, (2) transforming the hash using the transform seed
        for a specific number of rounds and (3) finally hashing the result in 
        combination with the master seed.
        """
        super(KDB4File, self)._make_master_key()
        composite = sha256(b''.join(self.keys))
        tkey = transform_key(composite, self.header.TransformSeed,
                             self.header.TransformRounds)
        self.master_key = sha256(self.header.MasterSeed + tkey)
Example #8
0
class KDB4File(KDBFile):
    def __init__(self, stream=None, **credentials):
        self.header = KDB4Header()
        KDBFile.__init__(self, stream, **credentials)

    def set_compression(self, flag=1):
        """Dis- (0) or enable (default: 1) compression"""
        if flag not in [0, 1]:
            raise ValueError('Compression flag can be 0 or 1.')
        self.header.CompressionFlags = flag

    #def set_comment(self, comment):
    #    self.header.Comment = comment

    def read_from(self, stream):
        """
        Read, parse, decrypt, decompress a KeePass file from a stream.
        
        :arg stream: A file-like object (opened in 'rb' mode) or IO buffer
            containing a KeePass file.
        """
        super(KDB4File, self).read_from(stream)
        if self.header.CompressionFlags == 1:
            self._unzip()

    def write_to(self, stream):
        """
        Write the KeePass database back to a KeePass2 compatible file.
        
        :arg stream: A writeable file-like object or IO buffer.
        """
        if not (isinstance(stream, io.IOBase)):
            raise TypeError('Stream does not have the buffer interface.')

        self._write_header(stream)

    def _read_header(self, stream):
        """
        Parse the header and write the values into self.header. Also sets
        self.header_length.
        """
        # KeePass 2.07 has version 1.01,
        # 2.08 has 1.02,
        # 2.09 has 2.00, 2.10 has 2.02, 2.11 has 2.04,
        # 2.15 has 3.00.
        # The first 2 bytes are critical (i.e. loading will fail, if the
        # file version is too high), the last 2 bytes are informational.
        #TODO implement version check
        
        # the first header field starts at byte 12 after the signature
        stream.seek(12)
        
        while True:
            # field_id is a single byte
            field_id = stream_unpack(stream, None, 1, 'b')
            
            # field_id >10 is undefined
            if not field_id in self.header.fields.values():
                raise IOError('Unknown header field found.')
            
            # two byte (short) length of field data
            length = stream_unpack(stream, None, 2, 'h')
            if length > 0:
                data = stream_unpack(stream, None, length, '{}s'.format(length))
                self.header.b[field_id] = data
            
            # set position in data stream of end of header
            if field_id == 0:
                self.header_length = stream.tell()
                break

    def _write_header(self, stream):
        """Serialize the header fields from self.header into a byte stream, prefix
        with file signature and version before writing header and out-buffer
        to `stream`.
        
        Note, that `stream` is flushed, but not closed!"""
        # serialize header to stream
        header = bytearray()
        # write file signature
        header.extend(struct.pack('<II', *KDB4_SIGNATURE))
        # and version
        header.extend(struct.pack('<hh', 0, 3))
        
        field_ids = list(self.header.keys())
        field_ids.sort()
        field_ids.reverse() # field_id 0 must be last
        for field_id in field_ids:
            value = self.header.b[field_id]
            length = len(value)
            header.extend(struct.pack('<b', field_id))
            header.extend(struct.pack('<h', length))
            header.extend(struct.pack('{}s'.format(length), value))
        

        # write header to stream
        stream.write(header)
        
        headerHash = base64.b64encode(sha256(header))
        self.obj_root.Meta.HeaderHash = headerHash
        
        # create HeaderHash if it does not exist
        if len(self.obj_root.Meta.xpath("HeaderHash")) < 1:
            etree.SubElement(self.obj_root.Meta, "HeaderHash")

        # reload out_buffer because we just changed the HeaderHash
        self.protect()
        self.out_buffer = io.BytesIO(self.pretty_print())

        # zip or not according to header setting
        if self.header.CompressionFlags == 1:
            self._zip()

        self._encrypt();
        
        # write encrypted block to stream
        stream.write(self.out_buffer)
        stream.flush()

    def _decrypt(self, stream):
        """
        Build the master key from header settings and key-hash list.
        
        Start reading from `stream` after the header and decrypt all the data.
        Remove padding as needed and feed into hashed block reader, set as
        in-buffer.
        """
        super(KDB4File, self)._decrypt(stream)
        
        data = aes_cbc_decrypt(stream.read(), self.master_key, 
            self.header.EncryptionIV)
        data = unpad(data)
        
        length = len(self.header.StreamStartBytes)
        if self.header.StreamStartBytes == data[:length]:
            # skip startbytes and wrap data in a hashed block io
            self.in_buffer = HashedBlockIO(bytes=data[length:])
            # set successful decryption flag
            self.opened = True
        else:
            raise IOError('Master key invalid.')

    def _encrypt(self):
        """
        Rebuild the master key from header settings and key-hash list. Encrypt
        the stream start bytes and the out-buffer formatted as hashed block
        stream with padding added as needed.
        """
        # rebuild master key from (possibly) updated header
        self._make_master_key()
        
        # make hashed block stream
        block_buffer = HashedBlockIO()
        block_buffer.write(self.out_buffer.read())
        # data is buffered in hashed block io, start a new one
        self.out_buffer = io.BytesIO()
        # write start bytes (for successful decrypt check)
        self.out_buffer.write(self.header.StreamStartBytes)
        # append blocked data to out-buffer
        block_buffer.write_block_stream(self.out_buffer)
        block_buffer.close()
        self.out_buffer.seek(0)
        
        # encrypt the whole thing with header settings and master key
        data = pad(self.out_buffer.read())
        self.out_buffer = aes_cbc_encrypt(data, self.master_key, 
            self.header.EncryptionIV)

    def _unzip(self):
        """
        Inplace decompress in-buffer. Read/write position is moved to 0.
        """
        self.in_buffer.seek(0)
        d = zlib.decompressobj(16+zlib.MAX_WBITS)
        self.in_buffer = io.BytesIO(d.decompress(self.in_buffer.read()))
        self.in_buffer.seek(0)

    def _zip(self):
        """
        Inplace compress out-buffer. Read/write position is moved to 0.
        """
        data = self.out_buffer.read()
        self.out_buffer = io.BytesIO()
        # note: compresslevel=6 seems to be important for kdb4!
        gz = gzip.GzipFile(fileobj=self.out_buffer, mode='wb', compresslevel=6)
        gz.write(data)
        gz.close()
        self.out_buffer.seek(0)

    def _make_master_key(self):
        """
        Make the master key by (1) combining the credentials to create 
        a composite hash, (2) transforming the hash using the transform seed
        for a specific number of rounds and (3) finally hashing the result in 
        combination with the master seed.
        """
        super(KDB4File, self)._make_master_key()
        composite = sha256(b''.join(self.keys))
        tkey = transform_key(composite, 
            self.header.TransformSeed, 
            self.header.TransformRounds)
        self.master_key = sha256(self.header.MasterSeed + tkey)