├── tests ├── __init__.py ├── conftest.py ├── test_kravatte_internals.py └── test_kravatte_oracle.py ├── .gitignore ├── .travis.yml ├── kravatte ├── __init__.py └── kravatte.py ├── setup.cfg ├── LICENSE ├── setup.py └── Readme.rst /tests/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .vscode/ 2 | .cache/ 3 | .idea/ 4 | __pycache__/ 5 | .eggs/ 6 | build/ 7 | dist/ 8 | kravatte.egg-info/ 9 | kravatte/*.pyc 10 | -------------------------------------------------------------------------------- /tests/conftest.py: -------------------------------------------------------------------------------- 1 | from os import cpu_count 2 | 3 | 4 | def pytest_generate_tests(metafunc): 5 | metafunc.parametrize("test_workers", [None, cpu_count()]) 6 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | python: 3 | - "3.5" 4 | - "3.5-dev" # 3.5 development branch 5 | - "3.6" 6 | - "3.6-dev" # 3.6 development branch 7 | - "3.7-dev" # 3.7 development branch 8 | install: 9 | - pip install numpy 10 | - python setup.py install 11 | script: 12 | - pytest -vvv 13 | -------------------------------------------------------------------------------- /kravatte/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """Top-level package for Kravatte""" 4 | from .kravatte import Kravatte, KravatteSAE, KravatteSANE, KravatteSANSE, KravatteWBC, KravatteWBC_AE, siv_wrap, siv_unwrap, mac, KravatteTagOutput, KravatteValidatedOutput, KravatteOracle 5 | __author__ = """Michael Calvin McCoy""" 6 | __email__ = 'calvin.mccoy@protonmail.com' 7 | __version__ = '1.2.0' 8 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [bumpversion] 2 | current_version = 1.2.0 3 | commit = True 4 | tag = True 5 | 6 | [bumpversion:file:setup.py] 7 | 8 | [bumpversion:file:kravatte/__init__.py] 9 | search = __version__ = '{current_version}' 10 | replace = __version__ = '{new_version}' 11 | 12 | [bdist_wheel] 13 | universal = 1 14 | 15 | [aliases] 16 | test = pytest 17 | 18 | [tool:pytest] 19 | collect_ignore = ['setup.py'] 20 | 21 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Calvin McCoy 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | """The setup script.""" 5 | 6 | from setuptools import setup, find_packages 7 | 8 | with open('Readme.rst') as readme_file: 9 | readme = readme_file.read() 10 | 11 | requirements = ['numpy>=1.12.0'] 12 | 13 | setup_requirements = ['pytest-runner'] 14 | 15 | test_requirements = ['pytest'] 16 | 17 | setup( 18 | author="Michael Calvin McCoy", 19 | author_email='calvin.mccoy@protonmail.com', 20 | classifiers=[ 21 | 'Development Status :: 4 - Beta', 22 | 'Intended Audience :: Developers', 23 | 'License :: OSI Approved :: MIT License', 24 | 'Topic :: Security :: Cryptography', 25 | 'Natural Language :: English', 26 | 'Programming Language :: Python :: 3', 27 | 'Programming Language :: Python :: 3.5', 28 | 'Programming Language :: Python :: 3.6', 29 | ], 30 | description="Kravatte Encryption Authentication Tools", 31 | install_requires=requirements, 32 | license="MIT license", 33 | long_description=readme, 34 | include_package_data=True, 35 | keywords='kravatte, farfalle, PRF, AEAD, MAC, crypto, encryption', 36 | name='kravatte', 37 | packages=find_packages(include=['kravatte']), 38 | setup_requires=setup_requirements, 39 | test_suite='tests', 40 | tests_require=test_requirements, 41 | url='https://github.com/inmcm/kravatte', 42 | download_url='https://github.com/inmcm/kravatte/archive/1.2.0.tar.gz', 43 | version='1.2.0', 44 | zip_safe=False, 45 | ) 46 | -------------------------------------------------------------------------------- /tests/test_kravatte_internals.py: -------------------------------------------------------------------------------- 1 | import hashlib 2 | import numpy as np 3 | from kravatte import Kravatte 4 | 5 | 6 | # Official Test Vectors 7 | class TestKravatte_Internals: 8 | """ 9 | Test Object Behaviors 10 | """ 11 | 12 | def test_kravatte_scrub(self, test_workers): 13 | """Verify Scrub clear key and collector arrays """ 14 | g = Kravatte(b'123456789ABCDEF0', workers=test_workers) 15 | g.collect_message(b"Alan Turing's Birthday is June 23rd") 16 | active_collector_state = np.array([[0x47f2a8301022502b, 0x175a0ba024f597e6, 0x9b4e04613147e6ef, 17 | 0x1765b925c31c079e, 0x6ab67495fea87309], 18 | [0xa0e08cf91f6036e7, 0xbde524db485dd8c5, 0xd5bef8ec82ec546c, 19 | 0x1d5d5bb702ecd106, 0xd2859d853decd0ba], 20 | [0x933a7ae8cbc00112, 0x9670141bc39379b4, 0x8592eca8ed33d40a, 21 | 0x633024eb4c392d43, 0x68832b65ee7815f6], 22 | [0xfbd578b54286aacb, 0x77d40e7b2453f93a, 0x97650682ed2063aa, 23 | 0x10247404d7916fb, 0x2c1aa50e5985f0d7], 24 | [0xf8c6c899ee338b89, 0xad3149bd7cd30c52, 0x5ca8af3af0a6095f, 25 | 0x29c8b966c76d3ab2, 0x897804d9e8d92c88]], dtype=np.uint64) 26 | active_key_state = np.array([[0xfd3dc5a634820be2, 0xda84238d059df308, 0xe1273a75cbe9ca79, 27 | 0xb42c26e0142eb005, 0xe408e6c3432721ae], 28 | [0xfda5f1b13312de28, 0x8550cb0da540eb36, 0xe659e72afb21d426, 29 | 0xa8a129c543ba8cfe, 0x9e4f929e6a3ab546], 30 | [0x491282fd4e25cf37, 0xf2d80d06aad4c4d7, 0xcb26fbbba4611f6b, 31 | 0xd5326fcd86c85641, 0x51cb367bf84cbf1], 32 | [0xdce9402ead58c081, 0x41d58bb12faa6eb3, 0x84bf1457dc9a9002, 33 | 0xe3eb07702cb8d973, 0xf6cf479fbee020de], 34 | [0xb877c006a804f95d, 0xf8fa17134fd29bee, 0x4270ff5f591d0054, 35 | 0x3662557cd15143f4, 0x21aad09eda854b56]], dtype=np.uint64) 36 | np.testing.assert_array_equal(g.kra_key, active_key_state) 37 | np.testing.assert_array_equal(g.collector, active_collector_state) 38 | # Clear collector and key state 39 | g.scrub() 40 | 41 | zero_keccak_array = np.array([[0x0, 0x0, 0x0, 0x0, 0x0], 42 | [0x0, 0x0, 0x0, 0x0, 0x0], 43 | [0x0, 0x0, 0x0, 0x0, 0x0], 44 | [0x0, 0x0, 0x0, 0x0, 0x0], 45 | [0x0, 0x0, 0x0, 0x0, 0x0]], dtype=np.uint64) 46 | np.testing.assert_array_equal(g.kra_key, zero_keccak_array) 47 | np.testing.assert_array_equal(g.collector, zero_keccak_array) 48 | np.testing.assert_array_equal(g.roll_key, zero_keccak_array) 49 | 50 | def test_kravatte_async_workers_input_only(self, test_workers): 51 | """Enable Multiprocessing only for Input Message Collection""" 52 | my_key = bytes([0xdf, 0x34, 0xa8, 0xb5, 0x87, 0xd8, 0x94, 0xce, 0xbd, 0x00, 0x63, 0x93, 53 | 0x19, 0xf4, 0xee, 0x15, 0x4c, 0x06, 0xe3, 0x78, 0x14, 0x00, 0x24, 0x0e, 54 | 0x27, 0x9a, 0x52, 0xf6, 0x4f, 0x9b, 0x11, 0x70, 0xd6, 0x9f, 0xa9, 0x61, 55 | 0x61, 0x1f, 0x80, 0xbf, 0x34, 0x6c, 0x00, 0x13, 0x0b, 0x13, 0x26, 0x89, 56 | 0x1d, 0x01, 0x6e, 0x14, 0x1a, 0xcd, 0xaa, 0xe4, 0x4e, 0xe4, 0x78, 0xf7, 57 | 0xfa, 0xbe, 0xfd, 0x06]) 58 | my_message = bytes([0x06, 0x7f, 0xee, 0x21, 0xb3, 0xe2, 0x8d, 0x8c, 0xd8, 0xe2, 0x42, 0x00, 59 | 0xb0, 0xd5, 0xc5, 0xb4, 0x2e, 0xe8, 0x13, 0xbc, 0xa1, 0x26, 0x5f, 0x5a, 60 | 0x35, 0x83, 0x51, 0x64, 0x64, 0x86, 0x4b, 0x97, 0x0b, 0x0a, 0x0d, 0xb4, 61 | 0x32, 0x54, 0x72, 0x4c, 0xc0, 0x91, 0xf5, 0x57, 0x6f, 0x3a, 0xd6, 0xd4, 62 | 0x4f, 0x41, 0x10, 0x33, 0x22, 0xbe, 0xff, 0xbf, 0xf6, 0x2b, 0x0e, 0x45, 63 | 0x16, 0x27, 0x41, 0x38]) 64 | real_collector = np.array([[0x54e0ec264ca8c59a, 0x4b6fc50546a0bc21, 0x1bfbdb05b9f59286, 0xd5c9326c207f5be4, 0x375cc6860a942eaa], 65 | [0x2fdce82332ae82f4, 0xf3514f8ccda98eca, 0x8787fc911658feea, 0xc804a1d21acbc2c0, 0xe0ab88818ece9ec8], 66 | [0xf0c286ae557896d3, 0xa106537a749842d3, 0xd6460164c12e5a0b, 0xa746ff01fa0f191f, 0x89189a584c857048], 67 | [0x5313e654653eac3c, 0x6cc4088ba50d88c7, 0x1486c2621e7b6c66, 0x678cb5aa64372917, 0x36fba87d0ec90c52], 68 | [0x4490c47365118d8f, 0xa3be17e62f3c241a, 0x6cc90d7d5d8e3cc4, 0x5dce0aea4a2c95c1, 0x3b1574b2476f1cfc]], dtype=np.uint64) 69 | real_digest = bytes([0x3f, 0xe1, 0xcd, 0x75, 0x18, 0x89, 0x32, 0x90, 0xaa, 0x8d, 0x97, 70 | 0xab, 0x7f, 0x86, 0xa8, 0x60, 0xcb, 0x88, 0x54, 0x32, 0xa0, 0x4a, 71 | 0x85, 0x46, 0xf9, 0x87, 0xce, 0xd8, 0x07, 0x85, 0xa4, 0x00, 0x34, 72 | 0x47, 0x69, 0xf7, 0x08, 0x53, 0xff, 0x04, 0xf6, 0xa4, 0xe5, 0x2c, 73 | 0xba, 0x14, 0xe9, 0xef, 0xcb, 0x1f, 0xa2, 0x6f, 0x23, 0xa8, 0x1f, 74 | 0x3b, 0x53, 0xdb, 0xfb, 0x8a, 0x5d, 0x9d, 0x9e, 0xd8, 0x6b, 0xde, 75 | 0x2e, 0x19, 0x26, 0xa7, 0xb4, 0x25, 0xf5, 0xad, 0x77, 0x61, 0xd5, 76 | 0xe7, 0x12, 0x47, 0x52, 0x20, 0x5b, 0x93, 0x46, 0xe9, 0xb4, 0xf5, 77 | 0x52, 0xfe, 0x87, 0x32, 0x0a, 0x7a, 0x49, 0x28, 0xef, 0x1f, 0x04, 78 | 0xd3, 0x5b, 0xa2, 0xa5, 0xff, 0x02, 0xd7, 0x23, 0x4f, 0xb5, 0x7b, 79 | 0x88, 0xf7, 0xe5, 0xd4, 0x74, 0x5c, 0x80, 0x8e, 0x78, 0xe5, 0x9d, 80 | 0xae, 0x9b, 0x4b, 0x82, 0x86, 0x47, 0xb4, 0x00, 0xca, 0x81, 0x8c, 81 | 0xc8, 0xcc, 0xba, 0xb3, 0x06, 0xfd, 0xaf, 0x0d, 0x66, 0x8c, 0x8f, 82 | 0xe1, 0x8e, 0xdc, 0xff, 0xe5, 0x8a, 0x6b, 0xcd, 0xf2, 0x94, 0x20, 83 | 0xd3, 0xe2, 0x31, 0x5e, 0x9a, 0xa4, 0xe1, 0xa9, 0x66, 0xae, 0x07, 84 | 0x31, 0x56, 0xe9, 0x29, 0x80, 0x7f, 0xd8, 0x5b, 0xcf, 0x7d, 0x23, 85 | 0x26, 0x37, 0x7b, 0x3d, 0x59, 0x2f, 0xaf, 0x99, 0x9f, 0x17, 0xce, 86 | 0x50, 0x7f, 0x3b, 0x99, 0x29, 0x44, 0xce, 0xe6, 0xea, 0xef, 0x67, 87 | 0xc2, 0x2e]) 88 | g = Kravatte(my_key, workers=test_workers, mp_input=True, mp_output=False) 89 | if test_workers: 90 | assert g.collect_message == g._collect_message_mp 91 | assert g.generate_digest == g._generate_digest_sp 92 | else: 93 | assert g.collect_message == g._collect_message_sp 94 | assert g.generate_digest == g._generate_digest_sp 95 | g.collect_message(my_message) 96 | assert (real_collector == g.collector).all() 97 | g.generate_digest(200) 98 | assert real_digest == g.digest 99 | 100 | def test_kravatte_async_workers_output_only(self, test_workers): 101 | """Enable Multiprocessing only for Output Digest Generation""" 102 | my_key = bytes([0x4e, 0x69, 0x25, 0x47, 0xce, 0x01, 0x7b, 0xd0, 0x53, 0xe1, 0x0d, 0x0b, 103 | 0xd2, 0xb2, 0x89, 0x9f, 0x6d, 0xce, 0xa0, 0xfa, 0x98, 0x74, 0x97, 0x53, 104 | 0xdf, 0xa2, 0xa8, 0xb4, 0x0e, 0x04, 0x3e, 0xf2, 0xc3, 0x3f, 0x45, 0xf3, 105 | 0x99, 0x51, 0xf1, 0xd2, 0xda, 0xe0, 0xee, 0xcb, 0xd1, 0x8d, 0x77, 0xed, 106 | 0x71, 0xe0, 0xc7, 0x75, 0xe9, 0x41, 0x78, 0x6e, 0x19, 0x6f, 0x97, 0x59, 107 | 0x54, 0x5e, 0xaf, 0xa5]) 108 | my_message = bytes([0x06, 0x25, 0xf8, 0x40, 0x6f, 0x74, 0x1d, 0xa3, 0x69, 0x13, 0x24, 0x06, 109 | 0x02, 0xea, 0xfb, 0xc2, 0xdd, 0xd8, 0xd0, 0x99, 0x4a, 0x58, 0xa6, 0xbc, 110 | 0x9d, 0x8a, 0xb1, 0x7f, 0xa2, 0x03, 0x4a, 0x53, 0xf1, 0xf2, 0xc3, 0x7b, 111 | 0x20, 0x81, 0x8a, 0x6b, 0x8b, 0x40, 0xf9, 0xe2, 0x7e, 0x9d, 0x61, 0xcc, 112 | 0xfd, 0xe7, 0x7f, 0xb3, 0x41, 0x6e, 0x82, 0x6b, 0x0e, 0x38, 0xa5, 0x59, 113 | 0x81, 0x0c, 0xb4, 0x37]) 114 | real_collector = np.array([[0x49382d1a6d6333ba, 0x1edd2f81e2d73d9d, 0x8805834ab4925a46, 0xfffe59f67acb272, 0xe45a3f77eb2b27a5], 115 | [0xe98356c0405bd2a, 0x4c4e36ee9fd7140, 0xc4ad304253621be4, 0x3413a5e4697b34cc, 0xc492d9f80be73c74], 116 | [0x4f305b758c030696, 0x855c85b4addd25d2, 0xe62415f70809e980, 0x708724adb6c442f3, 0xf074992dbf26076e], 117 | [0xe5857abfa46ff189, 0xee3add1524bb5d32, 0x8edb25f78ff42d1, 0xd79f1a4149960ac6, 0x4c217455991f2bf7], 118 | [0xd4e81912604d468b, 0xc1789f3967dc427f, 0xab20b23fab6d4c9a, 0xbd8f0a52b83f6e5f, 0x465859be99a6ebff]], dtype=np.uint64) 119 | real_digest = bytes([0xc5, 0x65, 0x5b, 0x1c, 0x58, 0x54, 0xb7, 0xf2, 0x0f, 0x53, 0x80, 0x8f, 120 | 0xf4, 0xfa, 0x42, 0x24, 0x7d, 0xb7, 0x6b, 0xd6, 0x7c, 0x15, 0x2f, 0x7f, 121 | 0xdb, 0x8f, 0x37, 0xfe, 0x08, 0xa7, 0x73, 0xf2, 0x4e, 0xcd, 0x71, 0xf8, 122 | 0x03, 0xdd, 0xe1, 0xb2, 0xbb, 0x49, 0xde, 0x79, 0xb8, 0x07, 0xbe, 0x13, 123 | 0x26, 0x9d, 0x7d, 0x36, 0x92, 0x51, 0xa3, 0x5a, 0xb1, 0xb9, 0xff, 0x9e, 124 | 0x16, 0xa0, 0x57, 0xc4, 0x9b, 0x91, 0xd6, 0x63, 0x11, 0x1d, 0xaf, 0x84, 125 | 0x0c, 0x50, 0x36, 0xac, 0x9d, 0x9f, 0xff, 0x50, 0xba, 0x32, 0x39, 0x19, 126 | 0x18, 0xef, 0xb5, 0x6a, 0x90, 0xf4, 0x40, 0x6e, 0x71, 0xa6, 0x4e, 0x96, 127 | 0xf2, 0xab, 0x25, 0x96, 0xea, 0x66, 0xf5, 0xe2, 0xd9, 0x84, 0x69, 0xce, 128 | 0xfd, 0x20, 0xa1, 0x24, 0xf8, 0x97, 0x9f, 0x52, 0xce, 0x7d, 0x0a, 0x3a, 129 | 0xdd, 0xb4, 0x5a, 0x99, 0x12, 0x1d, 0x9b, 0x44, 0x1f, 0x5b, 0x71, 0x50, 130 | 0x7e, 0xea, 0x15, 0xd2, 0x63, 0x16, 0x91, 0xa3, 0xb4, 0xe7, 0x57, 0x2f, 131 | 0x39, 0xdf, 0xe1, 0x59, 0x4d, 0x73, 0x06, 0x53, 0xff, 0x47, 0xb0, 0x03, 132 | 0x8a, 0xa8, 0xf8, 0x68, 0x8d, 0xc0, 0x21, 0x88, 0x35, 0x91, 0x0e, 0x4d, 133 | 0x99, 0xeb, 0x7f, 0xd9, 0x7f, 0xf6, 0x78, 0xfd, 0x4e, 0xdf, 0xc6, 0xb6, 134 | 0x30, 0x95, 0xb0, 0x0d, 0x6b, 0x5a, 0x56, 0xaf, 0xf5, 0xd6, 0x9e, 0xce, 135 | 0xf6, 0x36, 0xb9, 0xe2, 0x9b, 0x75, 0xb9, 0x0b]) 136 | g = Kravatte(my_key, workers=test_workers, mp_input=False, mp_output=True) 137 | if test_workers: 138 | assert g.collect_message == g._collect_message_sp 139 | assert g.generate_digest == g._generate_digest_mp 140 | else: 141 | assert g.collect_message == g._collect_message_sp 142 | assert g.generate_digest == g._generate_digest_sp 143 | g.collect_message(my_message) 144 | assert (real_collector == g.collector).all() 145 | g.generate_digest(200) 146 | assert real_digest == g.digest 147 | -------------------------------------------------------------------------------- /Readme.rst: -------------------------------------------------------------------------------- 1 | Kravatte Achouffe Cipher Suite in Python/NumPy 2 | ============================================== 3 | 4 | An implementation, in Python3, of the 5 | `Kravatte `__ pseudo-random function 6 | and associated modes based on the `Farfalle 7 | PRF `__ system. At its core, 8 | Kravatte accepts a user defined secret key and a sequence of input bytes 9 | to generate pseudo-random output of arbitrary size. From this primitive, a 10 | number of authenticated encryption modes can be built. 11 | 12 | Kravatte makes use of the 13 | `Keccak `__ 14 | permutation, most notably used in NIST’s `FIPS 202 SHA-3 15 | algorithm `__. 16 | Because the underlying structure of Keccak function works on a 17 | three-dimensional state of 1600 bits, it maps well to a 5x5 matrix of 18 | 64-bit unsigned integers. As such, the `NumPy `__ 19 | computational library is a natural fit to quickly manipulate such a 20 | structure and thus is a hard requirement. 21 | 22 | This implementation reflects the updated, more secure Kravatte 23 | **Achouffe** released in late 2017. The older ``Kravatte 6644`` logic is 24 | available within this repo as well. 25 | 26 | Also supported are the `Kravatte-SANE and Kravatte-SANSE `__ 27 | session based modes. These modes replace the deprecated Kravatte-SAE and Kravatte-SIV modes and 28 | utilizes the ``Deck-SANE`` and ``Deck-SANSE`` modes described in the `Xoodoo Cookbook `__. 29 | 30 | Installation 31 | ------------ 32 | Kravatte can be easily installed from `pypi `__ via ``pip``: 33 | 34 | .. code:: bash 35 | 36 | $ pip install kravatte 37 | 38 | If ``pip`` is unavailable, this repo can be cloned and setup can be done manually: 39 | 40 | .. code:: bash 41 | 42 | $ python setup.py install 43 | 44 | 45 | Kravatte Object 46 | --------------- 47 | 48 | The basic ``Kravatte`` object operates on two Keccak-1600 state matrices; 49 | the collector state and the key state. Instantiating a ``Kravatte`` object 50 | initializes the key state with provided user key and sets the collector 51 | state to zeros. 52 | 53 | .. code:: python 54 | 55 | In [1]: from kravatte import Kravatte 56 | In [2]: my_krav = Kravatte(b'1234567890') 57 | 58 | The newly initialized ``Kravatte`` object is now ready to accept input 59 | strings of bytes for absorption into the collector state via the 60 | ``collect_message`` method. Repeated calls to ``collect_message`` are 61 | equivalent to ``B ◦ A`` sequences as described in the the Farfalle 62 | spec: 63 | 64 | .. code:: python 65 | 66 | In [3]: input_a = b'The quick brown fox jumps over the lazy dog' 67 | In [4]: my_krav.collect_message(input_a) 68 | In [5]: input_b = b'3533392d36302d35313235' 69 | In [6]: my_krav.collect_message(input_b) 70 | 71 | Once absorbing message strings is complete, the ``Kravatte`` object can 72 | produce an arbitrary number of pseudo-random output bytes via the 73 | ``generate_digest`` method. Those bytes are then available in the 74 | ``digest`` attribute: 75 | 76 | .. code:: python 77 | 78 | In [7]: output_bytes = 64 79 | In [8]: my_krav.generate_digest(output_bytes) 80 | In [9]: from binascii import hexlify 81 | In [10]: hexlify(my_krav.digest) 82 | Out[10]: b'8a0fc89899e058dedd368b60111bf4958f4f24216bbac76936471e6f7c3958b881c38c8e829ff07bf137701917b3e49ab392e93f3b2abfc714f90c0ca023124d' 83 | 84 | The absorb/output sequence can be restarted with another call to 85 | ``collect_message``. This clears the collector state and resets the key 86 | state to its initialized value. Alternatively, the user may change to a 87 | new secret key with the ``update_key`` method to reinitialize the key 88 | state used at the start of message absorption. 89 | 90 | When a Kravatte object has reached the end of its usable lifetime, the ``scrub`` method 91 | can be used to try and cleanup interim state and key data in resident memory. This method is executed by default 92 | on the standalone functions ``mac``, ``siv_wrap``, ``siv_unwrap``, and is available in all Kravatte derived classes. 93 | *NOTE* This method only clears the sensitive attributes ``collector``, ``kra_key``, and ``roll_key`` and shouldn't be 94 | considered applicable when using the multi-process accelerated mode. 95 | 96 | MAC 97 | --- 98 | 99 | The most basic mode of ``Kravatte`` is an authenticated pseudo-random 100 | function (PRF). ``Kravatte`` can absorb an arbitrary sized user message and 101 | key, and output an arbitrary collection of pseudo-random bytes that can 102 | act as a message authentication code. The ``mac`` does this in a single step: 103 | 104 | .. code:: python 105 | 106 | In [1] from kravatte import mac 107 | In [2] from binascii import hexlify 108 | In [3] message = b'Attack at Dawn!' 109 | In [4] key = b'something_secret' 110 | In [5] mac_size = 64 111 | In [6] g = mac(key, message, mac_size) 112 | In [7] hexlify(g) 113 | Out[7] b'24f61fc5fd38fef7f3d799ed72b24578c4479e1c035c70d8bc55ce23d74124255d5e8a0c5dd33aa36d5289f1e4e995a19be804d97bb338fa875e01e3c2d2dd51' 114 | 115 | 116 | Kravatte-SANE 117 | ------------- 118 | 119 | ``Kravatte-SANE`` mode is a session based method of AEAD. Given a random 120 | nonce and secret key, this mode encrypts a sequence of plaintext 121 | messages and/or metadata into appropriately sized ciphertexts and a validation 122 | tags. The sequence of plaintext/metadata is tracked as a history that 123 | builds a chain of authentication from message to message and requires 124 | all generated ciphertexts to be processed to fully decrypt and verify. 125 | 126 | A separate ``KravatteSANE`` class is provided that adds the history 127 | tracking for each encryption operation done via the ``wrap`` method. 128 | 129 | Encrypt 130 | ~~~~~~~ 131 | 132 | .. code:: python 133 | 134 | In [1]: from os import urandom 135 | In [2]: from binascii import hexlify 136 | In [3]: from time import monotonic 137 | In [4]: my_nonce=urandom(32) 138 | In [5]: hexlify(my_nonce) 139 | Out[5]: b'41c48803e34eefd9ac1d39d3412d3e32592173fbcdd1b60d85dc177ae7156733' 140 | In [6]: message1=b'Nice List:' 141 | In [7]: meta1=str(monotonic()).encode() 142 | In [8]: message2=b'Alice,Bob' 143 | In [9]: meta2=str(monotonic()).encode() 144 | In [10]: message3=b'Naughty List:' 145 | In [11]: meta3=str(monotonic()).encode() 146 | In [12]: message4=b'Chuck, Eve' 147 | In [13]: meta4=str(monotonic()).encode() 148 | In [14]: my_sane = KravatteSANE(my_nonce,my_key) 149 | In [15]: ctext_1, tag_1 = my_sane.wrap(message1, meta1) 150 | In [16]: hexlify(ctext_1) 151 | Out[16]: b'4b42fef9cb5a6ce69d78' 152 | In [17]: hexlify(tag_1) 153 | Out[17]: b'169e7eb0f63cebd70efb779ff45a67f0' 154 | In [18]: ctext_2, tag_2 = my_sane.wrap(message2, meta2) 155 | In [19]: ctext_3, tag_3 = my_sane.wrap(message3, meta3) 156 | In [20]: ctext_4, tag_4 = my_sane.wrap(message4, meta4) 157 | 158 | For decryption and validation, the ``unwrap`` method accepts the 159 | ciphertext, original metadata, and validation tag to not only decrypt 160 | the plaintext, but return a boolean if the decrypted plaintext is valid 161 | within the chain of messages. 162 | 163 | Decrypt 164 | ~~~~~~~ 165 | 166 | .. code:: python 167 | 168 | In [21]: decrypt_sane = KravatteSANE(my_nonce,my_key) 169 | In [22]: ptext_1, tag_valid1 = decrypt_sane.unwrap(ctext_1, meta1, tag_1) 170 | In [23]: ptext_1 171 | Out[23]: b'Nice List:' 172 | In [24]: tag_valid1 173 | Out[24]: True 174 | In [25]: ptext_2, tag_valid2 = decrypt_sane.unwrap(ctext_2, meta2, tag_2) 175 | In [26]: tag_valid2 176 | Out[26]: True 177 | In [27]: ptext_2 178 | Out[27]: b'Alice,Bob' 179 | In [28]: ptext_3, tag_valid3 = decrypt_sane.unwrap(ctext_3, meta3, tag_3) 180 | In [29]: ptext_3 181 | Out[29]: b'Naughty List:' 182 | In [30]: tag_valid3 183 | Out[30]: True 184 | In [31]: ptext_4, tag_valid4 = decrypt_sane.unwrap(ctext_4, meta4, tag_4) 185 | In [32]: ptext_4 186 | Out[32]: b'Chuck, Eve' 187 | In [33]: tag_valid4 188 | Out[33]: True 189 | 190 | 191 | Kravatte-SANSE 192 | -------------- 193 | 194 | ``Kravatte-SANSE`` mode is session based method of authenticated encryption with 195 | associated metadata (AEAD) that allows for encrypting a provided 196 | plaintext with a secret shared key and an arbitrary metadata value. 197 | This mode does not require a nonce as it operates with a 198 | `Synthetic Initialization Vector (SIV) `__ 199 | Encryption generates an equal length ciphertext and fixed length tag 200 | that can be used to validate the plaintext at decryption. Metadata 201 | values can be shared for different key/message combinations with 202 | understanding that the more a value is used, the greater the chance of a 203 | tag collision. This mode replaces ``Kravatte-SIV`` 204 | 205 | A ``KravatteSANSE`` class is provided that adds the history 206 | tracking for each encryption operation done via the ``wrap`` method. 207 | 208 | Encrypt 209 | ~~~~~~~ 210 | 211 | .. code:: python 212 | 213 | In [1]: from binascii import hexlify 214 | In [2]: from kravatte import KravatteSANSE 215 | In [3]: my_message = b'And yet it moves' 216 | In [4]: my_key = b'name of childhood pet' 217 | In [5]: metadata_1 = b'1024x768' 218 | In [6]: another_message = b'The present is theirs; the future, for which I really worked, is mine.' 219 | In [7]: metadata_2 = b'7680x4320' 220 | In [8]: my_sanse = KravatteSANSE(my_key) 221 | In [9]: ctext_1, tag_1 = my_sanse.wrap(my_message, metadata_1) 222 | In [10]: hexlify(ctext_1) 223 | Out[10]: b'79e4773536a2ac4b4ec9e93583a817a5' 224 | In [11]: hexlify(tag_1) 225 | Out[11]: b'eaa50cb8a02e3238aa8dd5d1186ec0a87ebf6fe71b6fd89bea20b2001fef6810' 226 | In [12]: ctext_2, tag_2 = my_sanse.wrap(another_message, metadata_2) 227 | 228 | Decrypt 229 | ~~~~~~~ 230 | 231 | .. code:: python 232 | 233 | In [13]: decrypt_sanse = KravatteSANSE(my_key) 234 | In [14]: ptext_1, tag_valid_1 = decrypt_sanse.unwrap(ctext_1, metadata_1, tag_1) 235 | In [15]: ptext_1 236 | Out[15]: b'And yet it moves' 237 | In [16]: tag_valid_1 238 | Out[16]: True 239 | In [17]: ptext_2, tag_valid_2 = decrypt_sanse.unwrap(ctext_2, metadata_2, tag_2) 240 | In [18]: ptext_2 241 | Out[18]: b'The present is theirs; the future, for which I really worked, is mine.' 242 | In [19]: tag_valid_2 243 | Out[19]: True 244 | 245 | 246 | KravatteWBC 247 | ----------- 248 | 249 | Kravatte Wide Block Cipher mode is a symmetric block cipher mode where the user can specify 250 | the size of the block, an arbitrary ``tweak`` value input, and arbitrary secret key. The ``KravatteWBC`` 251 | object, once initialized, can encrypt/decrypt messages of the given block size (or smaller). ``KravatteWBC`` 252 | splits messages into left and right components and uses a 4-stage Feistel sequence to encrypt/decrypt. 253 | 254 | Encrypt and Decrypt 255 | ~~~~~~~~~~~~~~~~~~~ 256 | 257 | .. code:: python 258 | 259 | In [1]: from kravatte import KravatteWBC 260 | In [2]: block_size = 64 261 | In [3]: my_tweak = b'tweak can be anything' 262 | In [4]: my_key = b'\x00' * 24 263 | In [5]: my_wbc = KravatteWBC(block_size, my_tweak, my_key) 264 | In [6]: c_block = my_wbc.encrypt(b'This is some random 64-byte text string to use in this example!!') 265 | In [7]: from binascii import hexlify 266 | In [8]: hexlify(c_block) 267 | Out[8]: b'2368fae1271e5c784537df331586d5d4daeeb34a6fe4ebea03cc1df7f9c0d79fcc709a9ff2199514f431da685e27658dbf6c5afed11ce5c8172f7615c19db1b9' 268 | In [9]: my_wbc.decrypt(c_block) 269 | Out[9]: b'This is some random 64-byte text string to use in this example!!' 270 | 271 | 272 | KravatteWBC-AE 273 | -------------- 274 | 275 | ``KravatteWBC-AE`` is a variant of ``KravatteWBC`` that extends the desired block size by 16 bytes and 276 | embeds authentication data. The tweak is replaced with arbitrary associated metadata. When the 277 | block is decrypted it is also validated as being encrypted with same secret key. 278 | 279 | Encrypt and Decrypt 280 | ~~~~~~~~~~~~~~~~~~~ 281 | 282 | .. code:: python 283 | 284 | In [1]: from datetime import datetime 285 | In [2]: from binascii import hexlify 286 | In [3]: my_key = b"Doesn't look like anything to me" 287 | In [4]: metadata = str(datetime.now()).encode() 288 | In [5]: message = b'These violent delights have violent ends' 289 | In [6]: len(message) 290 | Out[6]: 40 291 | In [7]: my_WBC_AE = KravatteWBC_AE(40, my_key) 292 | In [8]: ctext_ae = my_WBC_AE.wrap(message, metadata) 293 | In [9]: len(ctext_ae) 294 | Out[9]: 56 295 | In [10]: hexlify(ctext_ae) 296 | Out[10]: b'388623f7a7d3c044cda574063b4ff16edbdfc95cb449f335a1c5ad5ed37897aa2470f3575825a55df04cc1dab34b4feb03aa6d35f6190d62' 297 | In [11]: plaintext, validated = my_WBC_AE.unwrap(ctext_ae, metadata) 298 | In [12]: plaintext 299 | Out[12]: b'These violent delights have violent ends' 300 | In [13]: validated 301 | Out[13]: True 302 | 303 | 304 | KravatteOracle 305 | -------------- 306 | 307 | ``KravatteOracle`` is a simple pseudo-random number generator built from the ``Kravatte`` PRF primitive. Initialized 308 | with an authentication key, the ``KravatteOracle`` object absorbs an arbitrarily sized seed value into the 309 | collector state. From there, streams of random bytes can be generated on demand via the ``random`` method. 310 | The generator can be re-seeded at any point with the ``seed_generator`` method. 311 | 312 | Generate Random Numbers 313 | ~~~~~~~~~~~~~~~~~~~~~~~ 314 | 315 | .. code:: python 316 | 317 | In [1]: my_psrng = KravatteOracle(my_seed, my_key) 318 | In [2]: my_key = b'1234' 319 | In [3]: my_seed = b'watermelon' 320 | In [4]: my_psrng = KravatteOracle(my_seed, my_key) 321 | In [5]: random_bytes = my_psrng.random(24) 322 | In [6]: hexlify(random_bytes) 323 | Out[6]: b'14a42ab5756efe61eae73893570b6736b392d0031a87e36d' 324 | In [7]: random_bytes = my_psrng.random(42) 325 | In [8]: hexlify(random_bytes) 326 | Out[8]: b'77d6308e18d57fb124e75602ced2e863e7de34c69ea57bec47efae84e85d0075c3ebbf7e535ec0fb096f' 327 | 328 | Re-seed Generator 329 | ~~~~~~~~~~~~~~~~~ 330 | 331 | .. code:: python 332 | 333 | In [9]: my_psrng.seed_generator(b'apple') 334 | In [10]: random_bytes = my_psrng.random(18) 335 | In [11]: hexlify(random_bytes) 336 | Out[11]: b'3e108c3f627f561943893b6a3184e5b76472' 337 | 338 | Kravatte-SIV (Deprecated) 339 | ------------------------- 340 | 341 | ``Kravatte-SIV`` mode is a method of authenticated encryption with 342 | associated metadata (AEAD) that allows for encrypting a provided 343 | plaintext with a secret shared key and an arbitrary metadata value. 344 | Encryption generates an equal length ciphertext and fixed length tag 345 | that can be used to validate the plaintext at decryption. Metadata 346 | values can be shared for different key/message combinations with 347 | understanding that the more a value is used, the greater the chance of a 348 | tag collision. **Deprecated in favor of Kravatte-SANSE** 349 | 350 | Encrypt 351 | ~~~~~~~ 352 | 353 | .. code:: python 354 | 355 | In [1] from kravatte import siv_wrap, siv_unwrap 356 | In [2] from binascii import hexlify 357 | In [3] from datetime import datetime 358 | In [4] message = b'Attack at Dawn!' 359 | In [5] key = b'something_secret' 360 | In [6] metadata = str(datetime.now()).encode() 361 | In [7] ciphertext, tag = siv_wrap(key, message, metadata) 362 | In [8] hexlify(ciphertext) 363 | Out[8] b'79f7bd89a7cb7af1892ea51c531f4b' 364 | In [9] hexlify(tag) 365 | Out[9] b'37c7e11f0c9c744e7c113590fdfba7737cb38b629ef6901df22d6994340e89eas' 366 | 367 | Decrypt 368 | ~~~~~~~ 369 | 370 | .. code:: python 371 | 372 | In [10] plaintext, tag_valid = siv_unwrap(key, ciphertext, tag, metadata) 373 | In [11] plaintext 374 | Out[11] b'Attack at Dawn!' 375 | In [12] tag_valid 376 | Out[12] True 377 | 378 | Kravatte-SAE (Deprecated) 379 | ------------------------- 380 | 381 | ``Kravatte-SAE`` mode is a session based method of AEAD. Given a random 382 | nonce and secret key, this mode encrypts a sequence of plaintext 383 | messages and/or metadata into equal size ciphertexts and a validation 384 | tag. The sequence of plaintext/metadata is tracked as a history that 385 | builds a chain of authentication from message to message and requires 386 | all generated ciphertexts to be processed to fully decrypt and verify. 387 | **Deprecated in favor of Kravatte-SANE** 388 | 389 | A separate ``KravatteSAE`` class is provided that adds the history 390 | tracking for each encryption operation done via the ``sae_wrap`` method. 391 | 392 | Encrypt 393 | ~~~~~~~ 394 | 395 | .. code:: python 396 | 397 | In [1]: from kravatte import KravatteSAE 398 | In [2]: from datetime import datetime 399 | In [3]: from binascii import hexlify 400 | In [4]: message_1 = b'Directions to my house:' 401 | In [5]: metadata_1 = str(datetime.now()).encode() 402 | In [6]: message_2 = b'Turn right on main street' 403 | In [7]: metadata_2 = str(datetime.now()).encode() 404 | In [8]: message_3 = b'Continue straight for 3500 miles' 405 | In [9]: metadata_3 = str(datetime.now()).encode() 406 | In [10]: message_4 = b'You have arrived at your destination' 407 | In [11]: metadata_4 = str(datetime.now()).encode() 408 | In [12]: nonce = b'a well chosen random number' 409 | In [13]: key = b'an even better random number' 410 | In [14]: KravSAE_wrapper = KravatteSAE(nonce, key) 411 | In [15]: ciphertext_1, tag_1 = KravSAE_wrapper.sae_wrap(message_1, metadata_1) 412 | In [16]: hexlify(ciphertext_1) 413 | Out[16]: b'7b8932a1c3673fcfe752631ef5b867843951514335de61' 414 | In [17]: hexlify(tag_1) 415 | Out[17]: b'3384885ca293925cc65a03fa10790420' 416 | In [18]: ciphertext_2, tag_2 = KravSAE_wrapper.sae_wrap(message_2, metadata_2) 417 | In [19]: hexlify(ciphertext_2) 418 | Out[19]: b'ab48882d4339c6def9d5d06f608db5318a87a417566c0b20bd' 419 | In [20]: hexlify(tag_2) 420 | Out[20]: b'347f5a152dcc9ccc3c19fa936067c3d2' 421 | In [21]: ciphertext_3, tag_3 = KravSAE_wrapper.sae_wrap(message_3, metadata_3) 422 | In [22]: hexlify(ciphertext_3) 423 | Out[22]: b'bc461f40db74705c10b1400b6a9967dd7164cbf774c196d5b649faf2bd792339' 424 | In [23]: hexlify(tag_3) 425 | Out[23]: b'6ba2faee4d2aa5654a054222a049d926' 426 | In [24]: ciphertext_4, tag_4 = KravSAE_wrapper.sae_wrap(message_4, metadata_4) 427 | In [25]: hexlify(ciphertext_4) 428 | Out[25]: b'1f451f51d9882f9f7674c37dace4036efd9efe39d6b58ccdf6b012ef988e4e1f2617479f' 429 | In [26]: hexlify(tag_4) 430 | Out[26]: b'5f3511f140b4ea36412c0e4b22d1c218' 431 | 432 | For decryption and validation, the ``sae_unwrap`` method accepts the 433 | ciphertext, original metadata, and validation tag to not only decrypt 434 | the plaintext, but return a boolean if the decrypted plaintext is valid 435 | within the chain of messages. 436 | 437 | Decrypt 438 | ~~~~~~~ 439 | 440 | .. code:: python 441 | 442 | In [27]: KravSAE_unwrapper = KravatteSAE(nonce, key) 443 | In [28]: plaintext_1, check_tag_1 = KravSAE_unwrapper.sae_unwrap(ciphertext_1, metadata_1, tag_1) 444 | In [29]: plaintext_1 445 | Out[29]: b'Directions to my house:' 446 | In [30]: check_tag_1 447 | Out[30]: True 448 | In [31]: plaintext_2, check_tag_2 = KravSAE_unwrapper.sae_unwrap(ciphertext_2, metadata_2, tag_2) 449 | In [32]: plaintext_2 450 | Out[32]: b'Turn right on main street' 451 | In [33]: check_tag_2 452 | Out[33]: True 453 | In [34]: plaintext_3, check_tag_3 = KravSAE_unwrapper.sae_unwrap(ciphertext_3, metadata_3, tag_3) 454 | In [35]: plaintext_3 455 | Out[35]: b'Continue straight for 3500 miles' 456 | In [36]: check_tag_3 457 | Out[36]: True 458 | In [37]: plaintext_4, check_tag_4 = KravSAE_unwrapper.sae_unwrap(ciphertext_4, metadata_4, tag_4) 459 | In [38]: plaintext_4 460 | Out[38]: b'You have arrived at your destination' 461 | In [39]: check_tag_4 462 | Out[39]: True 463 | 464 | 465 | Multi-Process Performance Mode 466 | --------------------------------- 467 | The Farfalle PRF allows for significant parallelism in both the compression and expansion phases when 468 | consuming or generating large numbers of blocks. We can exploit that fact for increased performance 469 | via Python's `multiprocessing `__ module. 470 | This allows us to spawn any number of identical worker subprocesses that can consume additional 471 | CPU core resources. Enabling the multi-process mode is done at object creation time for ``Kravatte``, 472 | or any of its operating modes, with the ``workers`` arguments: 473 | 474 | .. code:: python 475 | 476 | In [1]: new_kravatte = Kravatte(my_key, workers=8) 477 | In [2]: my_kra_mac = mac(my_key, my_message, my_output_size, workers=16) 478 | In [3]: my_wbc = KravatteWBC(block_size, my_tweak, my_key, workers=4) 479 | 480 | For optimal performance, the number of workers should match the number of CPU cores reported by 481 | ``os.cpu_count``. This is set automatically if ``workers`` is set to 0: 482 | 483 | .. code:: python 484 | 485 | # Equivalent objects 486 | In [4]: my_psrng = KravatteOracle(my_seed, my_key, workers=0) 487 | In [5]: my_psrng = KravatteOracle(my_seed, my_key, workers=os.cpu_count()) 488 | 489 | Multi-process mode can be explicitly disabled by setting workers to ``None``: 490 | 491 | .. code:: python 492 | 493 | In [6]: my_psrng = KravatteOracle(my_seed, my_key, workers=None) 494 | 495 | There is a non-trivial performance cost associated with generating new Python processes. For small, 496 | generally < 100KB, inputs and outputs, it can be faster to use the single process variant. 497 | 498 | For asymmetrically sized workloads, such a generating a MAC on a multi-megabyte input and only 499 | generating a few dozen bytes of output, multiprocessing mode can be explicitly enabled or disabled with the 500 | ``mp_input`` and ``mp_output`` arguments. These booleans are available for ``Kravatte`` and all derived classes/functions. 501 | 502 | .. code:: python 503 | 504 | # Enable Multiprocessing acceleration only for processing of input bytes 505 | In [7]: my_psrng = KravatteOracle(my_seed, my_key, workers=16, mp_input=True, mp_output=False) 506 | 507 | 508 | Testing 509 | ------- 510 | 511 | A full test suite is available in the ``test/`` dir. Tests can be invoked with pytest: 512 | 513 | .. code:: bash 514 | 515 | $ cd path/to/cloned/kravatte/ 516 | $ pytest -xvvv 517 | 518 | The same tests are run against the standard codepath and the multiprocess code path utilizing all available 519 | CPU cores. Test vectors were generated using the 520 | `KeccakTools `__ C++ library available from the Keccak Team 521 | 522 | Caveats 523 | ------- 524 | 525 | - Being a Python implementation, performance on large files or data 526 | sets may be inadequate (even with multi-processing enabled). 527 | - The inputs and outputs of this implementation are limited to byte 528 | (8-bit) divisible sizes 529 | - While security was top of mind during development, this 530 | implementation has not been fully audited for timing attacks, side 531 | channel attacks or other vulnerabilities. Other bugs not caught by 532 | the test cases may be present. Use in a production environment is not 533 | encouraged. 534 | 535 | If any of above are of concern, please check out the official 536 | `KeccakTools `__ and `Keccak Code 537 | Package `__ 538 | 539 | Changelog 540 | --------- 541 | 542 | 1.2.0 (2018-12-02) 543 | ~~~~~~~~~~~~~~~~~~ 544 | 545 | - Add Kravatte-SANE Support [Calvin McCoy] 546 | - Add Kravatte-SANSE Support [Calvin McCoy] 547 | - Refactor tests into seperate classes [Calvin McCoy] 548 | 549 | 1.1.0 (2018-09-08) 550 | ~~~~~~~~~~~~~~~~~~ 551 | 552 | - Add TravisCI Testing [Calvin McCoy] 553 | - Add memory scrub functionality. [Calvin McCoy] 554 | - Optimizations to Keccak, expand permutation, and compress permutations [Calvin McCoy] 555 | - Added ability to enable Multi-processing for just input or output [Calvin McCoy] 556 | - Fixed Typos and Added Some Extra Comments [Calvin McCoy] 557 | 558 | 1.0.0 (2018-05-19) 559 | ~~~~~~~~~~~~~~~~~~ 560 | 561 | - Added Multi-processing mode [Calvin McCoy] 562 | - Cleanup for 1.0 release [Calvin McCoy] 563 | 564 | 0.9.2 (2018-04-07) 565 | ~~~~~~~~~~~~~~~~~~ 566 | 567 | - Add KravatteOracle pseudo-random generator [Calvin McCoy] 568 | - Add type hinting [Calvin McCoy] 569 | - Fix Typos [Calvin McCoy] 570 | 571 | 0.9.0 (2018-03-31) 572 | ~~~~~~~~~~~~~~~~~~ 573 | 574 | - General package cleanup and fix typos [Calvin McCoy] 575 | 576 | 577 | 0.8.0 (2018-03-28) 578 | ~~~~~~~~~~~~~~~~~~ 579 | 580 | - Initial Commit [Calvin McCoy] -------------------------------------------------------------------------------- /tests/test_kravatte_oracle.py: -------------------------------------------------------------------------------- 1 | import hashlib 2 | import numpy as np 3 | from kravatte import KravatteOracle 4 | 5 | 6 | # Official Test Vectors 7 | class TestOfficialTestVectors_ORACLE: 8 | """ 9 | Test Vectors Generated From KeccakTools https://github.com/gvanas/KeccakTools 10 | """ 11 | 12 | def test_kravatte_random_oracle_k_32_seed_64(self, test_workers): 13 | my_key = bytes([0x19, 0x18, 0x17, 0x16, 0x15, 0x14, 0x13, 0x12, 0x11, 0x10, 0x0f, 0x0e, 0x0d, 14 | 0x0c, 0x0b, 0x0a]) 15 | my_message = bytes([0xad, 0x8e, 0x6f, 0x50, 0x31, 0x12, 0xf3, 0xd4, 0xb4, 0x95, 0x76, 0x57, 16 | 0x38, 0x19, 0xfa, 0xdb, 0xbb, 0x9c, 0x7d, 0x5e, 0x3f, 0x20, 0x01, 0xe2, 17 | 0xc2, 0xa3, 0x84, 0x65, 0x46, 0x27, 0x08, 0xe9, 0xc9, 0xaa, 0x8b, 0x6c, 18 | 0x4d, 0x2e, 0x0f, 0xf0, 0xd0, 0xb1, 0x92, 0x73, 0x54, 0x35, 0x16, 0xf7, 19 | 0xd7, 0xb8, 0x99, 0x7a, 0x5b, 0x3c, 0x1d, 0xfe, 0xde, 0xbf, 0xa0, 0x81, 20 | 0x62, 0x43, 0x24, 0x05]) 21 | 22 | real_output = bytes([0xff, 0x97, 0x4f, 0x96, 0xed, 0xa0, 0x10, 0xa7, 0x6b, 0x30, 0x28, 0x40, 23 | 0x13, 0x27, 0xcd, 0xe0, 0xf4, 0x6e, 0x7f, 0x8d, 0x15, 0x59, 0x75, 0xf2, 24 | 0x1e, 0x03, 0x08, 0xa4, 0x6a, 0xc4, 0xdb, 0x5d, 0x96, 0x20, 0xed, 0x73, 25 | 0xbd, 0xd9, 0xe9, 0x0a, 0x97, 0x30, 0x95, 0xc6, 0xa7, 0x94, 0xe8, 0x5a, 26 | 0x6f, 0xc1, 0x40, 0x43, 0xd4, 0xa9, 0x42, 0x4a, 0x93, 0x81, 0x2f, 0xb4, 27 | 0xcf, 0xb3, 0xff, 0x8d, 0xb1, 0x64, 0xe4, 0xd9, 0x68, 0xcc, 0xd1, 0x37, 28 | 0x22, 0x42, 0x4e, 0xa9, 0xf0, 0x53, 0x0a, 0x2b, 0x5b, 0x0a, 0x33, 0x9c, 29 | 0x32, 0x05, 0x97, 0x71, 0x7e, 0x26, 0x0e, 0xdd, 0x92, 0xd5, 0x82, 0x11, 30 | 0xbb, 0xf6, 0xce, 0xbf, 0xc9, 0x3d, 0x37, 0x48, 0x88, 0x01, 0xc0, 0xba, 31 | 0xe3, 0xeb, 0x0a, 0x99, 0xbc, 0xbc, 0xbc, 0xa9, 0xf9, 0x57, 0x5b, 0xa3, 32 | 0x77, 0x1d, 0x2d, 0xb7, 0x03, 0x2b, 0xc4, 0x18, 0x13, 0x75, 0x67, 0x67, 33 | 0x14, 0x7f, 0x7e, 0xf5, 0x80, 0x61, 0x5b, 0x46, 0xf6, 0x42, 0xcf, 0x2b, 34 | 0x2d, 0xec, 0x92, 0x38, 0xbf, 0x96, 0xc0, 0x9a, 0xdb, 0x55, 0xde, 0xec, 35 | 0xfb, 0x70, 0x97, 0x6b, 0xa0, 0xc1, 0x37, 0x6c, 0xf8, 0xd2, 0x12, 0xd6, 36 | 0xdd, 0x32, 0x4e, 0x1c, 0xdd, 0xf8, 0x61, 0x19, 0x05, 0x61, 0xb8, 0xd4, 37 | 0xd4, 0x6c, 0xc4, 0xd6, 0x49, 0xa0, 0x91, 0x5f, 0x35, 0xc8, 0xc7, 0x71, 38 | 0x28, 0xab, 0x35, 0x56, 0x98, 0x2d, 0x30, 0x74, 0x9a, 0x59, 0xa7, 0x0c, 39 | 0xcf, 0x1e, 0x34, 0x5f, 0x31, 0x74, 0xfc, 0x58, 0x35, 0x79, 0xd4, 0xad, 40 | 0x8d, 0xd1, 0x81, 0xbb, 0x98, 0xb7, 0x77, 0x9f, 0xbc, 0x0f, 0x98, 0x79, 41 | 0x06, 0x2a, 0x01, 0xd0, 0x21, 0x46, 0xde, 0x52, 0x5d, 0xc8, 0x9d, 0x25, 42 | 0x1f, 0xb6, 0x53, 0xc2, 0xa2, 0xee, 0x83, 0xf3, 0x6c, 0x49, 0x56, 0xf3, 43 | 0x37, 0xa7, 0xe7, 0xc7, 0x9e, 0xe9, 0x83, 0x77, 0x63, 0xac, 0x22, 0x26, 44 | 0xd8, 0xf1, 0xd7, 0xb8, 0x72, 0x55, 0x01, 0x74, 0xb8, 0x90, 0x82, 0x60, 45 | 0xd1, 0x95, 0xda, 0x35, 0x8e, 0xa1, 0x66, 0x54, 0xd7, 0x35, 0x91, 0xed, 46 | 0x40, 0x5a, 0xb9, 0x14, 0x91, 0x8e, 0xba, 0x69, 0x4d, 0x40, 0x31, 0x40, 47 | 0x89, 0x38, 0xce, 0x9d, 0xfa, 0xa5, 0x2b, 0xf2, 0xc1, 0x31, 0x4f, 0x65, 48 | 0x0c, 0x3a, 0xbe, 0xc1, 0x63, 0x0b, 0xe7, 0x74, 0xf5, 0x01, 0xed, 0x4d, 49 | 0xd7, 0x8a, 0xbd, 0x1a, 0x83, 0x9c, 0x47, 0xe5, 0x6b, 0x87, 0x39, 0xc6, 50 | 0x50, 0xc5, 0x16, 0x8d, 0x51, 0x69, 0x82, 0xfe, 0x00, 0x25, 0xa2, 0xea, 51 | 0xcb, 0x96, 0xab, 0xf8, 0x98, 0x3b, 0x44, 0x4e, 0x2f, 0x38, 0x49, 0x23, 52 | 0xf8, 0x51, 0x37, 0xfb, 0xed, 0x00, 0x66, 0xfb, 0x4f, 0x37, 0x1a, 0x2f, 53 | 0x7a, 0x29, 0xa1, 0x37, 0x17, 0x0c, 0x03, 0x01, 0x75, 0x67, 0x83, 0xdd, 54 | 0xfa, 0xa8, 0x7e, 0xd3, 0x83, 0x93, 0x94, 0xdf, 0xa6, 0xc6, 0x7c, 0xae, 55 | 0x25, 0xff, 0x98, 0x7f, 0x2a, 0xcd, 0xf2, 0x90, 0x60, 0xae, 0xec, 0x0a, 56 | 0x4f, 0x39, 0x79, 0x79, 0x55, 0x47, 0x29, 0x8f, 0x5c, 0x15, 0xfc, 0xdc, 57 | 0xe8, 0x1b, 0xc3, 0x3a, 0x00, 0xf3, 0x79, 0xd7, 0xa3, 0x05, 0x0f, 0x3d, 58 | 0xd5, 0xcd, 0x79, 0xc9, 0x87, 0xfc, 0x7a, 0x0e, 0x5d, 0x07, 0xa0, 0x11, 59 | 0xbd, 0x6f, 0x93, 0x82, 0x64, 0xab, 0xc2, 0xdd, 0x5a, 0x20, 0xd1, 0x04, 60 | 0x3a, 0x34, 0x70, 0xad, 0xe5, 0xf6, 0x2f, 0x3c, 0x50, 0xf5, 0x32, 0xd6, 61 | 0x91, 0x7c, 0x18, 0xc0, 0x0d, 0xbd, 0x04, 0xcb, 0x25, 0x54, 0xfa, 0x33, 62 | 0xec, 0xe1, 0x97, 0xc1, 0x0e, 0xbc, 0x0f, 0x3f, 0xdb, 0x1c, 0x12, 0x35, 63 | 0x1f, 0x98, 0x8e, 0xd7, 0xdc, 0x38, 0x52, 0xf8, 0x17, 0x5a, 0x23, 0xd2, 64 | 0xc6, 0x5f, 0x00, 0x87, 0xc9, 0x64, 0x9b, 0xaf, 0xd1, 0x73, 0x41, 0xda, 65 | 0x66, 0x59, 0xe0, 0x7f, 0x9f, 0x63, 0x47, 0x8b, 0xee, 0xb9, 0xf4, 0xac, 66 | 0xa0, 0xb5, 0x40, 0xa6, 0x4f, 0xe5, 0x7a, 0xd6, 0xcf, 0xf0, 0xab, 0x43, 67 | 0xb6, 0x94, 0x64, 0x89, 0xe8, 0x0a, 0x5a, 0xd6, 0x38, 0x49, 0x91, 0x14, 68 | 0x57, 0x13, 0x3d, 0x9f, 0x42, 0xf0, 0x22, 0x9a, 0xbf, 0xe3, 0xd7, 0x74, 69 | 0x7d, 0x78, 0x91, 0x9b, 0xeb, 0x1b, 0x30, 0x44, 0x9b, 0xa5, 0x0a, 0xfb, 70 | 0xb1, 0x3f, 0x62, 0xf2, 0x7b, 0xbd, 0xf6, 0xbf, 0x0f, 0x0d, 0xc7, 0xdf, 71 | 0xdc, 0x52, 0x4e, 0xc8, 0xd0, 0x3c, 0xaa, 0xf1, 0x6c, 0x08, 0xe4, 0x96, 72 | 0x46, 0xf9, 0x22, 0x45, 0x3d, 0xc5, 0x39, 0xce, 0xce, 0x19, 0xde, 0x96, 73 | 0x72, 0x8a, 0x20, 0x2e, 0x3a, 0x47, 0x38, 0x62, 0xb0, 0xf3, 0x7c, 0x5d, 74 | 0x45, 0xb1, 0xc9, 0xca, 0x14, 0x2b, 0x47, 0x08, 0x43, 0x1f, 0x95, 0xe1, 75 | 0x9d, 0xb9, 0x5e, 0xa7, 0x29, 0x29, 0x24, 0xe9, 0x96, 0x1d, 0xa2, 0xf9, 76 | 0xe4, 0x25, 0x9d, 0xe5, 0x97, 0xb0, 0xc9, 0xe1, 0x98, 0x32, 0x58, 0x68, 77 | 0xef, 0x2e, 0x89, 0xcc, 0x0e, 0xa0, 0xa7, 0x0c, 0xc8, 0x6b, 0x8f, 0x11, 78 | 0x9f, 0x83, 0x59, 0xee, 0xe6, 0xf2, 0x7b, 0x31, 0x2a, 0x3a, 0xb6, 0xb8, 79 | 0xec, 0x23, 0xa6, 0x6d, 0x85, 0x91, 0xc0, 0x34, 0x58, 0x65, 0x88, 0xef, 80 | 0x10, 0x42, 0xd2, 0x77, 0x83, 0x0b, 0x60, 0x2c, 0x4e, 0x93, 0x46, 0x7f, 81 | 0xf5, 0x43, 0x35, 0xaa, 0x1c, 0x2b, 0x0f, 0x27, 0x39, 0x84, 0x4f, 0xd1, 82 | 0x7a, 0xb5, 0x29, 0x86, 0xf8, 0xe9, 0xf8, 0xb3, 0xa5, 0xc8, 0xef, 0x16, 83 | 0x4e, 0x40, 0x48, 0xf2, 0xf9, 0xbb, 0x77, 0x71, 0x1e, 0xc1, 0x59, 0xe7, 84 | 0x7c, 0x59, 0x82, 0xa5, 0x85, 0xd4, 0xd3, 0x22, 0xb8, 0x79, 0xde, 0xd2, 85 | 0x55, 0xbc, 0x8e, 0xec, 0x1f, 0x3c, 0x55, 0x44, 0x5e, 0xc9, 0x64, 0xe7, 86 | 0xac, 0x59, 0xae, 0x9b, 0x8e, 0x05, 0x8b, 0x9f, 0xa3, 0x0a, 0x48, 0x9c, 87 | 0x0f, 0x03, 0x2c, 0x29, 0x50, 0xf9, 0xcb, 0x4a, 0xad, 0x2e, 0x9a, 0x42, 88 | 0x50, 0x11, 0x1b, 0x8d, 0xc5, 0x08, 0x2d, 0x01, 0x7b, 0x1c, 0xd1, 0x1d, 89 | 0xff, 0x3f, 0x98, 0xb5, 0x00, 0x58, 0x81, 0x82, 0x42, 0x5b, 0x70, 0x88, 90 | 0xbf, 0x86, 0x45, 0x0d, 0xec, 0xc0, 0xe7, 0x16, 0xd4, 0x2d, 0xbe, 0xa8, 91 | 0xbf, 0x0e, 0x4e, 0x2b, 0xe7, 0xfb, 0xfd, 0x5b, 0x2f, 0x8f, 0x98, 0x6c, 92 | 0x1b, 0xea, 0xa4, 0x57, 0x53, 0x56, 0xc0, 0x3c, 0xb6, 0x60, 0xbc, 0x7a, 93 | 0x5b, 0x1a, 0xf7, 0xab, 0x85, 0x3c, 0x89, 0x2b, 0x39, 0x08, 0xf1, 0x9c, 94 | 0x6d, 0xda, 0x71, 0xa7, 0xf6, 0x55, 0x48, 0xde, 0x3c, 0xc2, 0x90, 0x3e, 95 | 0x97, 0x9c, 0x5f, 0xcb, 0x33, 0xc5, 0x74, 0x03, 0xdd, 0xe5, 0x07, 0x2d, 96 | 0x67, 0x11, 0x17, 0xe9, 0xf3, 0x46, 0x9f, 0x1c, 0x9a, 0x67, 0x8a, 0x0e, 97 | 0x56, 0x8b, 0x71, 0x3f, 0x49, 0x75, 0x8a, 0x28, 0xb4, 0xb9, 0xa5, 0x0f, 98 | 0xe8, 0xb9, 0x29, 0x06, 0xdd, 0xae, 0x7e, 0xd5, 0x6d, 0xfe, 0x65, 0xf2, 99 | 0x04, 0x3a, 0x21, 0x48, 0xbf, 0x79, 0x0d, 0x89, 0x7c, 0x05, 0xbd, 0x57, 100 | 0x24, 0x06, 0xa0, 0x16, 0x66, 0x46, 0x1c, 0x91, 0xde, 0xf7, 0x59, 0xd3, 101 | 0x3b, 0x66, 0x57, 0xfd, 0x36, 0x45, 0xc3, 0xae, 0x80, 0xe1, 0xcf, 0x26, 102 | 0x3b, 0x25, 0x49, 0xa8, 0x46, 0x4c, 0xab, 0xe9, 0x86, 0x35, 0xad, 0xef, 103 | 0x77, 0xeb, 0x5b, 0x5b, 0x73, 0xc9, 0xb0, 0x6a, 0x26, 0x64, 0xb4, 0xd9, 104 | 0x8f, 0x70, 0xcb, 0x93, 0x25, 0x2f, 0x8c, 0xfc, 0x48, 0xb3, 0xba, 0x3d, 105 | 0xfd, 0xfa, 0x8d, 0x7b, 0x37, 0xae, 0xb3, 0xfa, 0xba, 0x1f, 0x89, 0x52, 106 | 0x32, 0x51, 0xf2, 0x5c, 0xc3, 0x7a, 0x9d, 0xb9, 0x36, 0xcd, 0xb9, 0xb9, 107 | 0x9b, 0x0a, 0x4f, 0x0a, 0x86, 0x80, 0xf2, 0x18, 0x19, 0xba, 0xf3, 0x03, 108 | 0x68, 0xfa, 0xed, 0x33, 0xc2, 0x1c, 0x64, 0xd1, 0xee, 0x28, 0x2a, 0x21, 109 | 0x77, 0xdd, 0xfe, 0x3f, 0xf7, 0x68, 0x51, 0x3c, 0x19, 0x57, 0x0b, 0xbe, 110 | 0xc0, 0x7f, 0xf9, 0x11, 0x24, 0xde, 0xa1, 0x1d, 0xae, 0x5c, 0x9e, 0xab, 111 | 0xa0, 0x7f, 0xd6, 0x02, 0x72, 0x2f, 0x9a, 0xd0, 0xcc, 0xad, 0x6a, 0xe4, 112 | 0xae, 0xcd, 0xcf, 0x8f, 0x52, 0xa6, 0x03, 0xe2, 0xce, 0xb5, 0x88, 0xcf, 113 | 0x22, 0x36, 0xb1, 0x91, 0x73, 0xe2, 0x3d, 0x20, 0xce, 0x4b, 0x6c, 0xb6, 114 | 0xb0, 0x53, 0xb9, 0xcc, 0x56, 0x48, 0x73, 0x21, 0x7f, 0x62, 0x06, 0xe9, 115 | 0xb2, 0x1c, 0x9f, 0x61, 0x70, 0xc7, 0xa3, 0x2e, 0x0e, 0x99, 0x4a, 0x1c, 116 | 0x14, 0x1d, 0x9d, 0x3e, 0xbd, 0x7f, 0x7d, 0x2a, 0xb6, 0xd2, 0x58, 0x37, 117 | 0xa3, 0xef, 0x54, 0x1f, 0x03, 0xd5, 0x67, 0x3a, 0xaa, 0xfa, 0x6d, 0xd6, 118 | 0xa3, 0x99, 0x07, 0xcd, 0xc6, 0x20, 0x89, 0xb7, 0x6f, 0x15, 0xb3, 0x72, 119 | 0xb6, 0xaa, 0xfa, 0x8a, 0xd8, 0x84, 0x93, 0x43, 0xec, 0xcf, 0x2c, 0x05, 120 | 0x46, 0x9c, 0xc5, 0x89, 0xc6, 0xb3, 0x4e, 0x07, 0x9a, 0xab, 0xe4, 0x12, 121 | 0x09, 0xab, 0xfc, 0x0a, 0x63, 0x25, 0xa0, 0xe9, 0x3a, 0x7c, 0x7a, 0x41, 122 | 0xeb, 0xa2, 0x3d, 0x8e, 0x6b, 0x05, 0x22, 0x48, 0x7c, 0x0f, 0x13, 0xd1, 123 | 0x3f, 0xbb, 0x9a, 0xd7, 0x9a, 0x30, 0x6b, 0x9b, 0x0f, 0xb0, 0x80, 0x1d, 124 | 0xc4, 0x05, 0x8e, 0x78, 0x2a, 0xf2, 0x29, 0x51, 0x94, 0xc7, 0xe7, 0x62, 125 | 0x24, 0xd6, 0xad, 0x90, 0x99, 0xba, 0xc1, 0x36, 0x52, 0x3a, 0x6f, 0x5f, 126 | 0x59, 0x20, 0xc2, 0x5c, 0x1c, 0x49, 0x8f, 0xc8, 0x3e, 0xdf, 0x6c, 0xf0, 127 | 0x8d, 0xc1, 0x26, 0xba, 0x87, 0x40, 0xf1, 0x6d, 0xff, 0x78, 0xe5, 0x7e, 128 | 0x4c, 0x90, 0xf8, 0x37, 0xf7, 0xd8, 0x68, 0x24, 0xa1, 0xe4, 0x43, 0x88, 129 | 0x0a, 0x37, 0x64, 0x2c, 0xaf, 0x4d, 0xb2, 0x60, 0x7d, 0xb2, 0x2e, 0x26, 130 | 0x4b, 0xba, 0xfa, 0x77, 0x29, 0x1b, 0x9d, 0x99, 0xf4, 0x28, 0xc2, 0xfa, 131 | 0x6f, 0x1b, 0x76, 0x47, 0xf1, 0x1d, 0x3c, 0x39, 0x98, 0xb9, 0x85, 0x62, 132 | 0x94, 0x59, 0x40, 0x91, 0x72, 0x9e, 0x8a, 0x77, 0x3d, 0x0e, 0xa2, 0xd4, 133 | 0xe4, 0x26, 0x99, 0xe1, 0x02, 0xfd, 0x2a, 0x6e, 0x8e, 0x52, 0xf4, 0xcb, 134 | 0x5d, 0x83, 0xa1, 0xdd, 0x13, 0x78, 0xf6, 0xfe, 0x1b, 0x44, 0x35, 0x51, 135 | 0x10, 0x08, 0x2a, 0x39, 0xe5, 0x31, 0x5f, 0x7c, 0x4f, 0xd3, 0x8b, 0x22, 136 | 0x99, 0x37, 0x53, 0x97, 0xc3, 0xe1, 0xaa, 0xfc, 0xb2, 0x6e, 0xc4, 0x9a, 137 | 0x86, 0x34, 0x70, 0x14, 0x74, 0x4c, 0x5d, 0x06, 0xab, 0x14, 0xe4, 0x0e, 138 | 0x9f, 0x3b, 0x9c, 0xc6, 0x27, 0x36, 0x61, 0x6d, 0xa1, 0xc4, 0xb8, 0xf2, 139 | 0xee, 0x91, 0x05, 0x66, 0x0e, 0x46, 0x34, 0x9c, 0x3b, 0xf9, 0xa4, 0xb1, 140 | 0xc7, 0x70, 0xc1, 0x6c, 0x82, 0xf8, 0x51, 0xb5, 0xbb, 0x09, 0xff, 0x2a, 141 | 0x02, 0x10, 0x50, 0x16, 0x9b, 0xae, 0x33, 0x81, 0x32, 0x8c, 0xd6, 0xf0, 142 | 0xff, 0x86, 0x6c, 0xe9, 0x4a, 0x02, 0x3b, 0x9a, 0xe1, 0x61, 0x6c, 0x4d, 143 | 0xa6, 0xa5, 0x07, 0x29, 0x54, 0xbf, 0x16, 0x2b, 0xa8, 0xa4, 0x5c, 0x45, 144 | 0x4b, 0x5e, 0xc4, 0x22, 0x5d, 0xa9, 0xd3, 0xe6, 0x13, 0x15, 0xe4, 0xf0, 145 | 0xcf, 0x73, 0x01, 0x92, 0xce, 0x7c, 0xae, 0xe5, 0xd5, 0xce, 0xdd, 0x51, 146 | 0xe8, 0xfc, 0x7c, 0x5c, 0x37, 0x6d, 0x66, 0xe2, 0xa2, 0x0d, 0x98, 0x56, 147 | 0xae, 0xe1, 0x67, 0xee, 0x7f, 0xbe, 0x3f, 0x67, 0xfd, 0x25, 0x4d, 0xe6, 148 | 0x6e, 0x56, 0x69, 0x47, 0xd9, 0x91, 0xb9, 0x20, 0x01, 0x5f, 0x2f, 0x21, 149 | 0xed, 0x06, 0x59, 0x52, 0xe4, 0x04, 0xb8, 0x34, 0x0e, 0x20, 0x35, 0x84, 150 | 0xaf, 0xa6, 0xfc, 0x17, 0xb3, 0x7c, 0x95, 0x18, 0xe7, 0xb1, 0x06, 0x14, 151 | 0xfe, 0x08, 0xfc, 0xd9, 0x73, 0x3c, 0xa0, 0x65, 0xc3, 0x58, 0xfa, 0x8f, 152 | 0xae, 0xac, 0x94, 0x97, 0xd8, 0x79, 0x3a, 0x8a, 0xf1, 0x52, 0x02, 0xa7, 153 | 0xa9, 0xd7, 0x1a, 0x41, 0xc1, 0x45, 0x35, 0x5c, 0xb8, 0xcb, 0x77, 0x13, 154 | 0xf1, 0xbd, 0xc3, 0x0f, 0x6c, 0xe7, 0x53, 0xf1, 0x04, 0x8b, 0xc6, 0x22, 155 | 0xfd, 0x87, 0x12, 0xbc, 0xd3, 0x9e, 0x92, 0x69, 0xc8, 0x46, 0x12, 0x39, 156 | 0x9e, 0x70, 0xb6, 0x77, 0xfb, 0xaf, 0x7d, 0xc2, 0x43, 0xc6, 0x18, 0x9e, 157 | 0x41, 0x0c, 0x60, 0xd7, 0xab, 0xdb, 0x90, 0xe6, 0x51, 0xa6, 0x3e, 0x81, 158 | 0x82, 0x64, 0x58, 0xeb, 0xe2, 0xc4, 0xfa, 0xd1, 0xcc, 0x32, 0x8e, 0xbb, 159 | 0xa8, 0x5e, 0x4a, 0x83, 0xf2, 0x72, 0x43, 0x82, 0xcc, 0xed, 0x7b, 0xe4, 160 | 0x35, 0x00, 0x28, 0xa3, 0x88, 0xcc, 0x99, 0xc2, 0x79, 0xee, 0xb3, 0x67, 161 | 0xd3, 0x33, 0xbb, 0xa4, 0xd6, 0xdd, 0xd0, 0xe0, 0xc5, 0xc1, 0xe3, 0xc7, 162 | 0x96, 0x78, 0x3b, 0xfc, 0x37, 0xc0, 0x8a, 0xee, 0x39, 0xf4, 0xc6, 0xcd, 163 | 0x4c, 0xe2, 0xbd, 0xcf, 0x70, 0xa6, 0x2a, 0xd2, 0x15, 0xc3, 0xe8, 0x8a, 164 | 0xf7, 0x88, 0x97, 0xd9, 0xbf, 0x87, 0xc2, 0xcd, 0x65, 0x04, 0xba, 0x89, 165 | 0xa2, 0x06, 0x9b, 0x5c, 0x10, 0x90, 0x37, 0x33, 0x42, 0xe3, 0x06, 0x5c, 166 | 0x85, 0x66, 0x5f, 0x6d, 0x64, 0x86, 0x86, 0xd3, 0x48, 0x35, 0x42, 0xde, 167 | 0xa1, 0xf8, 0x85, 0xa3, 0xfa, 0xde, 0x57, 0xbc, 0x03, 0x0a, 0x63, 0xbb, 168 | 0xe4, 0x73, 0x9c, 0x3d, 0xab, 0x6b, 0x84, 0xcb, 0x1f, 0x54, 0x7b, 0x57, 169 | 0x29, 0x28, 0x8b, 0x9c, 0xd9, 0x04, 0x14, 0x2a, 0x30, 0xae, 0x7d, 0xa2, 170 | 0x2c, 0x03, 0x83, 0xd5, 0xa1, 0x3b, 0x20, 0xd1, 0xa1, 0x95, 0x56, 0x49, 171 | 0x8b, 0x3b, 0xf5, 0x7b, 0x7f, 0x8e, 0x27, 0xba, 0x56, 0x60, 0x37, 0x48, 172 | 0x93, 0xdc, 0xbd, 0x0f, 0xa1, 0x78, 0xb6, 0x29, 0xc2, 0x4f, 0x41, 0xb4, 173 | 0x43, 0xe5, 0x1a, 0x91, 0x0e, 0xe9, 0xbe, 0xf7, 0xb4, 0x72, 0x60, 0xef, 174 | 0x2f, 0x6f, 0xff, 0x4e, 0xac, 0x03, 0x31, 0xef, 0xfa, 0xc8, 0xde, 0x78, 175 | 0xb3, 0x9f, 0x9d, 0x75, 0x1c, 0x8a, 0xd0, 0xe6, 0xbd, 0x2f, 0x79, 0x81, 176 | 0xf3, 0xb3, 0xfa, 0xb9, 0x72, 0x60, 0x41, 0x30, 0xb2, 0x56, 0xe0, 0x14, 177 | 0x25, 0xf1, 0x26, 0x1b, 0xf3, 0x06, 0xd3, 0x8f, 0xa4, 0x9a, 0x8d, 0x37, 178 | 0xff, 0x32, 0x78, 0x6d, 0xdf, 0x8e, 0x83, 0x9d, 0xf2, 0xa0, 0x81, 0x50, 179 | 0x0f, 0xac, 0x3b, 0x99, 0x19, 0x9e, 0x53, 0x3f, 0x1a, 0x9c, 0x4b, 0xb4, 180 | 0x59, 0x0d, 0x2e, 0x54, 0xdb, 0xb7, 0xba, 0xbf, 0x05, 0xbf, 0x60, 0x20, 181 | 0x73, 0x29, 0xb1, 0x48, 0x3c, 0xe1, 0x4b, 0xda, 0xc2, 0xa9, 0xe1, 0xe6, 182 | 0xcb, 0xe6, 0x92, 0x12, 0xdd, 0x9e, 0x48, 0x46, 0xce, 0x47, 0x81, 0xa8, 183 | 0xe5, 0x4a, 0xc4, 0x2d, 0x1f, 0x09, 0xe1, 0x40, 0x35, 0xc6, 0x01, 0x39, 184 | 0xc8, 0xab, 0x1d, 0x56, 0xab, 0x1f, 0x31, 0x56, 0xd0, 0x2a, 0x94, 0x8f, 185 | 0x13, 0x3a, 0x17, 0x11, 0x83, 0xb1, 0xa1, 0x3a, 0x7b, 0x0c, 0x47, 0x17, 186 | 0x6b, 0x78, 0x2b, 0x87, 0x6e, 0x74, 0x3c, 0x47, 0x0e, 0xe4, 0x0b, 0x49, 187 | 0xd7, 0x06, 0xbb, 0xef, 0x6c, 0x23, 0x76, 0xd5, 0x4e, 0xfc, 0x96, 0x67, 188 | 0x0e, 0x86, 0x6a, 0xfb, 0x6f, 0xb6, 0x37, 0x2c, 0xfd, 0x5e, 0x7d, 0x58, 189 | 0x8f, 0x47, 0xee, 0x6e, 0xe2, 0x6e, 0x39, 0x45, 0x3b, 0x4d, 0xf8, 0x21, 190 | 0x4b, 0x4a, 0x8f, 0xf3, 0xe9, 0x1c, 0xbc, 0xe2, 0xb4, 0x18, 0x83, 0x4f, 191 | 0xe6, 0x52, 0x79, 0xbc, 0xbf, 0xa2, 0x19, 0xe8, 0xc8, 0x59, 0x01, 0x5b, 192 | 0xd8, 0x6e, 0x17, 0xf4, 0xd7, 0xe0, 0x41, 0xff, 0xac, 0xdf, 0x09, 0x6a, 193 | 0x7d, 0xf6, 0x30, 0xb7, 0xfc, 0xe4, 0x27, 0x54, 0xcc, 0x8a, 0xc5, 0x43, 194 | 0x50, 0xd3, 0x12, 0x91, 0x10, 0x9d, 0x67, 0x83, 0xf6, 0x32, 0xd2, 0x60, 195 | 0xba, 0xc6, 0x1b, 0xa9, 0x61, 0x1f, 0xb9, 0xa4, 0xfc, 0xc8, 0xe1, 0x4f, 196 | 0x50, 0x43, 0xb7, 0x86, 0x74, 0x46, 0x1b, 0xf4, 0xba, 0x6f, 0xfb, 0x1f, 197 | 0xd5, 0x40, 0x08, 0xd4, 0x6a, 0xed, 0x39, 0xdd, 0x3a, 0xca, 0x1c, 0xbf, 198 | 0x07, 0xa8, 0xac, 0xfd, 0x0d, 0xeb, 0xc2, 0xf3, 0x28, 0xa8, 0xe5, 0xc1, 199 | 0x03, 0xfb, 0x9d, 0x45, 0xbd, 0x4a, 0xd9, 0xd7, 0xf9, 0xdd, 0xf0, 0x7e, 200 | 0x24, 0xea, 0x74, 0x2e, 0x02, 0x3f, 0xd1, 0x8a, 0x48, 0x7a, 0x5d, 0xbc, 201 | 0x95, 0x5d, 0x37, 0x82, 0x5a, 0x9d, 0x7f, 0x08, 0x03, 0xdf, 0x65, 0xdd, 202 | 0x3f, 0x4c, 0x10, 0x1f, 0x19, 0x58, 0x2f, 0xee, 0xe3, 0x93, 0xaa, 0x67, 203 | 0x76, 0x02, 0x72, 0x4b, 0x05, 0x0c, 0xad, 0x5e, 0xe9, 0xd8, 0xb7, 0x51, 204 | 0xa5, 0x76, 0x4f, 0x17, 0xe5, 0xae, 0x03, 0x9f, 0x5f, 0x15, 0x1e, 0x20, 205 | 0xa1, 0x8a, 0xc5, 0x31, 0x35, 0x3f, 0xe7, 0x14, 0x27, 0x7a, 0x81, 0x79, 206 | 0x6f, 0xfd, 0x49, 0xea, 0xfc, 0x89, 0x54, 0x8e, 0x74, 0xbb, 0xf1, 0x19, 207 | 0x2e, 0xc3, 0x59, 0x05, 0xcf, 0x37, 0xe7, 0x82, 0xec, 0x2d, 0x6f, 0x8a, 208 | 0x7d, 0x5e, 0x05, 0x3b, 0x44, 0xa4, 0xf5, 0x18, 0xd2, 0x6c, 0xf5, 0x1a, 209 | 0x36, 0x33, 0x40, 0xaf, 0xca, 0x62, 0x49, 0x1a, 0x03, 0x0c, 0x1f, 0x2f, 210 | 0xe9, 0x3a, 0x6a, 0x1e, 0xc6, 0xd4, 0x09, 0xda, 0xa0, 0x03, 0x48, 0x1d, 211 | 0x5b, 0x26, 0xb1, 0xf6, 0x20, 0xfe, 0xad, 0x76, 0xb1, 0x4f, 0x6a, 0x2c, 212 | 0x68, 0xf2, 0x66, 0x2e, 0xe7, 0xda, 0x8f, 0xbb, 0x83, 0xed, 0xd0, 0xca, 213 | 0xa8, 0x3d, 0xa1, 0x78, 0x80, 0x6b, 0x1c, 0x50, 0x53, 0x87, 0xa2, 0x8e, 214 | 0x5f, 0x6f, 0xb4, 0x5e, 0xe4, 0x44, 0xd7, 0xa3, 0xe9, 0x69, 0x7f, 0xb2, 215 | 0x3d, 0x00, 0x6a, 0x72, 0xf0, 0x17, 0x85, 0xbc, 0x6f, 0x5c, 0x97, 0x52, 216 | 0xe0, 0x03, 0xd9, 0x94, 0xa1, 0x42, 0x1c, 0x1c, 0x53, 0x90, 0xc3, 0xe0, 217 | 0x15, 0xa1, 0xb1, 0x8a, 0xa1, 0x17, 0x9b, 0x7f, 0x0c, 0x1e, 0x3b, 0x46, 218 | 0x75, 0x69, 0x42, 0xb3, 0x81, 0x66, 0xbe, 0xcd, 0x78, 0x16, 0xc3, 0xfa, 219 | 0x06, 0xaa, 0xb9, 0xbe, 0xdb, 0x6d, 0x8b, 0xb8, 0x81, 0x42, 0xa3, 0x7c, 220 | 0xd5, 0x60, 0xc8, 0x46, 0x59, 0x2e, 0x8c, 0xcf, 0x0f, 0x31, 0xd7, 0x1a, 221 | 0x5e, 0xfe, 0xfb, 0x3f, 0x4e, 0x05, 0xbc, 0x68, 0xaf, 0xe4, 0x9c, 0x2f, 222 | 0x9d, 0x1c, 0x59, 0xc1, 0x30, 0x40, 0x2c, 0xde, 0x5f, 0x9d, 0xd8, 0x57, 223 | 0x83, 0xdb, 0x5d, 0xa3, 0x1d, 0x07, 0xe1, 0x22, 0x2b, 0x19, 0x03, 0xca, 224 | 0xed, 0xfe, 0x7e, 0x92, 0xd0, 0x3a, 0x3a, 0xc5, 0x9d, 0x20, 0x54, 0xed, 225 | 0xaa, 0xb9, 0xdc, 0xfb, 0xe7, 0x6a, 0x4b, 0xc4, 0xad, 0xbd, 0x17, 0x95, 226 | 0x2c, 0x77, 0x34, 0xde, 0xcc, 0x0f, 0x57, 0x92, 0xc2, 0x34, 0x9f, 0xcd, 227 | 0x7c, 0xf7, 0x64, 0x56, 0xd8, 0xa1, 0x7b, 0xb3, 0xc8, 0xbb, 0x9b, 0x55, 228 | 0xc3, 0xe6, 0xc3, 0x2a, 0x9d, 0x7f, 0x63, 0x7f, 0xf4, 0xc0, 0x35, 0xf7, 229 | 0xde, 0x6b, 0xc1, 0xd4, 0x8f, 0xd3, 0xaf, 0x86, 0xa2, 0xd6, 0xcc, 0xbf, 230 | 0xb9, 0x04, 0x0a, 0xe6, 0xfc, 0xdb, 0xf0, 0x77, 0x62, 0x5f, 0x7d, 0x08, 231 | 0xbf, 0x07, 0xba, 0x48, 0xc9, 0x17, 0x07, 0x14, 0x26, 0x0b, 0xaf, 0xc6, 232 | 0x6e, 0xbb, 0x23, 0x8a, 0xdb, 0xb4, 0x45, 0x24, 0xc3, 0xd8, 0x50, 0x87, 233 | 0x80, 0xcc, 0x91, 0xe4, 0x1c, 0x2b, 0x96, 0x8b, 0x7a, 0x9f, 0x48, 0x6a, 234 | 0x42, 0x09, 0x40, 0xd9, 0x80, 0xb7, 0x99, 0x8a, 0xbf, 0xcb, 0xe1, 0x84, 235 | 0x94, 0x1b, 0x05, 0x76, 0x11, 0x27, 0xc9, 0x49, 0x5d, 0xab, 0x77, 0x20, 236 | 0xa1, 0xb0, 0x6a, 0x47, 0x75, 0xc4, 0x50, 0x77, 0x43, 0x1f, 0x22, 0xd3, 237 | 0xcb, 0xf2, 0xb6, 0x54, 0x95, 0x2b, 0x2e, 0x64, 0x7f, 0x91, 0xb4, 0x85, 238 | 0x50, 0x2e, 0xa6, 0xea, 0xfa, 0xa9, 0x06, 0xbb, 0x20, 0xce, 0x26, 0xf5, 239 | 0x5c, 0xad, 0xf3, 0xd7, 0xfd, 0x52, 0x87, 0xfc, 0xc5, 0x81, 0x4f, 0xf2, 240 | 0x7a, 0xc4, 0x1a, 0x22, 0x42, 0x64, 0x01, 0x94, 0x83, 0xd8, 0x6b, 0x10, 241 | 0xd8, 0x63, 0x37, 0xcb, 0x47, 0x80, 0xb6, 0x49, 0x52, 0xb0, 0xe4, 0xa9, 242 | 0x1c, 0x2b, 0x63, 0xe4, 0x35, 0x37, 0xe4, 0x05, 0x6b, 0x92, 0xf1, 0xcc, 243 | 0x29, 0xb7, 0xda, 0x4a, 0xbd, 0xae, 0x6f, 0x05, 0x53, 0xd7, 0xab, 0x9e, 244 | 0x64, 0x0c, 0x71, 0xcc, 0xdb, 0x77, 0x4f, 0x65, 0x0d, 0x7f, 0x31, 0x7c, 245 | 0x21, 0x72, 0xe4, 0x93, 0xf6, 0xe9, 0x09, 0x27, 0xdd, 0xad, 0xc7, 0x68, 246 | 0xef, 0xc9, 0x85, 0x0e, 0x2e, 0xa2, 0x9e, 0x9e, 0xc9, 0x63, 0xd5, 0xc1, 247 | 0x2f, 0xf7, 0xdb, 0x95, 0x1f, 0x27, 0x95, 0x92, 0xb7, 0xeb, 0xff, 0xab, 248 | 0x70, 0x0d, 0x4d, 0x0e, 0x13, 0x9b, 0xf5, 0xc7, 0xde, 0x7b, 0x82, 0x36, 249 | 0x4b, 0x71, 0x37, 0x03, 0x73, 0x9c, 0x7b, 0xbd, 0x0a, 0xb4, 0xf5, 0xa3, 250 | 0x0e, 0xe1, 0x94, 0xf4, 0x5f, 0xab, 0x7c, 0xc5, 0x71, 0x65, 0x5d, 0xa2, 251 | 0x72, 0x4f, 0xe4, 0x79, 0xa1, 0x1d, 0x3c, 0xfc, 0xeb, 0x63, 0x32, 0xfc, 252 | 0xc0, 0x8d, 0x2c, 0xac, 0xbe, 0x8f, 0x52, 0xa7, 0xe3, 0xb7, 0x74, 0x17, 253 | 0x6d, 0xc7, 0x73, 0x83, 0xc7, 0xe6, 0x35, 0x77, 0xe3, 0x7e, 0x50, 0x6d, 254 | 0xd4, 0x3f, 0x3b, 0xfb, 0xe6, 0xa0, 0xea, 0x4e, 0xfa, 0x9c, 0xa4, 0xd0, 255 | 0x11, 0xd6, 0xd8, 0xa1, 0x39, 0xa9, 0x80, 0xeb, 0xaf, 0x48, 0x37, 0x6a, 256 | 0x1a, 0x89, 0x63, 0xd0, 0x41, 0x95, 0x29, 0x14, 0x11, 0xfc, 0xcf, 0x93, 257 | 0x42, 0x78, 0xb3, 0x40, 0xe3, 0xea, 0x2c, 0xba, 0x64, 0x26, 0x41, 0x1c, 258 | 0xb1, 0x8c, 0xcd, 0xc6, 0xb9, 0x10, 0xf9, 0x7c, 0x0e, 0x92, 0x10, 0x69, 259 | 0x44, 0x79, 0x9e, 0xe2, 0x4a, 0x4f, 0x44, 0xc9, 0xd6, 0x5a, 0x11, 0x8f, 260 | 0x6d, 0x05, 0x00, 0x1b, 0xf4, 0xec, 0x9c, 0xc6, 0xba, 0x79, 0x45, 0xe9, 261 | 0x2d, 0xcb, 0x00, 0x73, 0xe9, 0x72, 0xaa, 0xa4, 0xaa, 0x21, 0x99, 0xf2, 262 | 0xaa, 0xd7, 0x8d, 0x9b, 0x99, 0x5f, 0x57, 0xfb, 0x2b, 0xbc, 0x45, 0x5b, 263 | 0x03, 0xd1, 0x32, 0x6f, 0xf6, 0x89, 0xf3, 0x28, 0x31, 0x42, 0x01, 0xbc, 264 | 0xb4, 0x37, 0xbe, 0x30, 0xae, 0xe8, 0x2c, 0xd2, 0xbe, 0xa8, 0x4e, 0xe3, 265 | 0x47, 0x4f, 0x0f, 0x1f, 0x46, 0xf8, 0xf8, 0x84, 0x5a, 0x09, 0x03, 0x7d, 266 | 0x8d, 0xb1, 0x0b, 0x7f, 0x1c, 0xbe, 0x75, 0x9d, 0x22, 0x33, 0xc8, 0x50, 267 | 0x21, 0xaa, 0x5e, 0xc3, 0x5e, 0x29, 0xcc, 0xbb, 0x3f, 0x33, 0x4b, 0xbf, 268 | 0x2e, 0x75, 0x3c, 0x8b, 0x0e, 0x05, 0x4e, 0xa8, 0xab, 0x46, 0x1c, 0x67, 269 | 0x05, 0xf3, 0xd0, 0xc7, 0xb7, 0xb4, 0xe2, 0x63, 0x37, 0x5c, 0x64, 0x1a, 270 | 0xb2, 0xc6, 0x58, 0xf1, 0xc3, 0xab, 0x47, 0x65, 0x56, 0xa7, 0x08, 0xb2, 271 | 0xfb, 0x72, 0xd5, 0xfb, 0xdf, 0x58, 0x4e, 0xd8, 0x1a, 0xea, 0x71, 0x41, 272 | 0xc0, 0xe0, 0xf4, 0x8f, 0xd7, 0x3b, 0xce, 0x63, 0x2d, 0xe8, 0xce, 0xb1, 273 | 0xac, 0x81, 0x5f, 0x5c, 0x2e, 0x72, 0x8b, 0x21, 0xb2, 0x6a, 0x0b, 0x05, 274 | 0x58, 0xd8, 0x97, 0xf8, 0xe7, 0xf5, 0xbd, 0xc9, 0x25, 0xcc, 0xda, 0x95, 275 | 0x6b, 0x5d, 0x60, 0xe6, 0x37, 0x08, 0x8f, 0x14, 0x1e, 0x89, 0x81, 0x97, 276 | 0x20, 0x3a, 0xe6, 0xa8, 0xa8, 0x9f, 0x7c, 0x1d, 0x03, 0xa0, 0x5f, 0x82, 277 | 0x36, 0x39, 0xa7, 0x72, 0xc3, 0xa3, 0xc5, 0x25, 0x8f, 0xe2, 0xa2, 0x15, 278 | 0x4f, 0x34, 0x53, 0x58, 0x34, 0x82, 0x05, 0xbc, 0xa5, 0x2c, 0x12, 0x07, 279 | 0x1f, 0x26, 0xd6, 0x84, 0x28, 0x5f, 0xf2, 0x39, 0xd0, 0x9e, 0x6a, 0x82, 280 | 0xbc, 0x48, 0xf5, 0xe4, 0x12, 0x01, 0x5d, 0x36, 0x63, 0x39, 0xbf, 0xf1, 281 | 0x83, 0xbf, 0x8a, 0xf4, 0xb5, 0x6f, 0x16, 0x52, 0xca, 0x39, 0xb3, 0x5c, 282 | 0x1f, 0x09, 0x8d, 0xa1, 0x92, 0x5c, 0xa0, 0x14, 0xa1, 0xa9, 0x4c, 0x6a, 283 | 0x1e, 0x06, 0x44, 0x68, 0x0c, 0xae, 0xa7, 0xcf, 0x31, 0xe9, 0x47, 0x8f, 284 | 0x55, 0xb4, 0x1b, 0xde, 0xbe, 0x09, 0x58, 0x35, 0x5b, 0x16, 0x8b, 0xc6, 285 | 0x45, 0x66, 0x9d, 0x05, 0x80, 0xee, 0x6e, 0x15, 0x09, 0x75, 0xa3, 0xdd, 286 | 0xf7, 0xf6, 0x1a, 0xc5, 0x13, 0xae, 0xe5, 0x6a, 0x31, 0xad, 0x58, 0x87, 287 | 0xc7, 0xf1, 0xc1, 0x8e, 0x2f, 0xb2, 0x07, 0x23, 0xd9, 0xcc, 0x77, 0xa3, 288 | 0x91, 0x12, 0x62, 0x9e, 0x16, 0x62, 0xa9, 0x33, 0x3d, 0x1e, 0xae, 0x6c, 289 | 0xd4, 0xc4, 0x78, 0x1b, 0x51, 0xec, 0xea, 0xcd, 0xcd, 0x61, 0x23, 0xbf, 290 | 0x29, 0x4f, 0x18, 0x0d, 0x97, 0x33, 0x81, 0x07, 0xa8, 0xbc, 0xd7, 0xa1, 291 | 0xf4, 0x76, 0xa5, 0x77, 0xac, 0x3a, 0x80, 0x5b, 0x45, 0x77, 0xce, 0xe7, 292 | 0xfc, 0x23, 0xd0, 0x5e, 0xf7, 0xb9, 0x70, 0x94, 0x5b, 0xf0, 0x57, 0xa7, 293 | 0x5e, 0xdc, 0x48, 0x97, 0xfe, 0xe1, 0xed, 0x77, 0x2a, 0xb6, 0x8d, 0xce, 294 | 0x26, 0xdb, 0xae, 0x77, 0xfe, 0xc7, 0xd7, 0x8f, 0x60, 0xc0, 0xe6, 0xc9, 295 | 0x8c, 0x34, 0xac, 0x63, 0xb6, 0x41, 0x6f, 0xff, 0xc1, 0xb2, 0x79, 0x79, 296 | 0x01, 0x14, 0xfb, 0x4c, 0x46, 0x12, 0xe9, 0xb8, 0x0e, 0xb7, 0xda, 0x38, 297 | 0x54, 0x4c, 0x22, 0xac, 0x26, 0x96, 0xd0, 0xba, 0xb3, 0xd4, 0x33, 0x4b, 298 | 0x43, 0xcc, 0xed, 0x2a, 0x4c, 0xa8, 0x51, 0x2e, 0x98, 0x01, 0xd0, 0xa0, 299 | 0x79, 0xec, 0x80, 0xa6, 0x17, 0x4a, 0x23, 0x65, 0xc8, 0x4d, 0x57, 0x80, 300 | 0x55, 0xbb, 0xd2, 0xb6, 0xc8, 0x3e, 0x13, 0xc3, 0x95, 0x63, 0xfb, 0x9d, 301 | 0xab, 0x2b, 0xf6, 0xd5, 0x24, 0x1d, 0x32, 0xaa, 0xdb, 0x42, 0x3f, 0xf1, 302 | 0x7a, 0x1b, 0x64, 0x78, 0xa4, 0x76, 0x8d, 0xca, 0xc9, 0x14, 0x2c, 0xe5, 303 | 0x05, 0x21, 0x27, 0xca, 0xec, 0x98, 0xc4, 0x28, 0x44, 0xb5, 0x1e, 0xaa, 304 | 0xdb, 0xe3, 0xa2, 0xc3, 0xa2, 0x6e, 0xc8, 0x3c, 0xa6, 0xf6, 0xd5, 0xf1, 305 | 0x29, 0xa5, 0xd7, 0x41, 0x6b, 0x3b, 0x0c, 0xc6, 0xb3, 0x56, 0x13, 0xc7, 306 | 0x9c, 0x8d, 0x83, 0xc2, 0xe9, 0xb4, 0xb3, 0x7b, 0x2a, 0xdd, 0x38, 0x6a, 307 | 0x15, 0x96, 0xb3, 0x59, 0xeb, 0x52, 0xe6, 0x0f, 0x47, 0xda, 0x12, 0x86, 308 | 0xef, 0x9b, 0xc7, 0x7e, 0xe9, 0xed, 0xbe, 0x45, 0x1a, 0x63, 0xf0, 0x6f, 309 | 0x2c, 0xfa, 0xa5, 0x74, 0xc4, 0x4e, 0x82, 0x32, 0x2b, 0xcb, 0x13, 0x36, 310 | 0x1d, 0xb9, 0x56, 0xcb, 0xf4, 0x4d, 0x53, 0x58, 0x2e, 0x0d, 0x15, 0xea, 311 | 0xee, 0xd7, 0x6b, 0x20, 0x3b, 0xea, 0x64, 0x13, 0xbf, 0x77, 0x94, 0x9a, 312 | 0xe5, 0x07, 0x24, 0xb7, 0x68, 0xdb, 0x8d, 0xa9, 0xd2, 0x5d, 0x4a, 0x12, 313 | 0x6e, 0x6b, 0x12, 0x7a, 0x66, 0x30, 0xcb, 0x49, 0xef, 0xc0, 0xe6, 0x3a, 314 | 0x17, 0x28, 0x68, 0x9f, 0x95, 0x4f, 0x10, 0x02, 0xd3, 0xd9, 0x0c, 0x3b, 315 | 0xa4, 0x8e, 0x16, 0x4e, 0x66, 0x94, 0xa8, 0xbf, 0x9c, 0xe5, 0x1d, 0x20, 316 | 0x93, 0xa5, 0xb7, 0xf3, 0x0d, 0xa0, 0x41, 0x5e, 0x30, 0x11, 0x25, 0x08, 317 | 0x84, 0xe4, 0x97, 0x6a, 0x5a, 0x8e, 0x33, 0x22, 0x08, 0xd7, 0xb5, 0xa0, 318 | 0x5c, 0xe3, 0xfd, 0x53, 0xa8, 0xb0, 0x69, 0x65, 0xff, 0xdd, 0xb6, 0x52, 319 | 0xa3, 0x28, 0xa8, 0x49, 0xa4, 0xf1, 0xa6, 0x53, 0x66, 0x6c, 0x2f, 0xc4, 320 | 0x3c, 0x96, 0x4f, 0x83, 0xea, 0xec, 0x2b, 0xfb, 0x2a, 0xe0, 0x98, 0x32, 321 | 0x2c, 0xbf, 0xc5, 0x92, 0x03, 0x62, 0x09, 0xb1, 0x1d, 0xd3, 0x86, 0xca, 322 | 0x21, 0xa3, 0x0d, 0x3e, 0x08, 0xe4, 0xa8, 0xda, 0x4b, 0xc6, 0x70, 0x88, 323 | 0x38, 0xb9, 0x3f, 0xe2, 0xc4, 0xba, 0x3b, 0xb3, 0xd1, 0x21, 0xe4, 0x39, 324 | 0x4d, 0xda, 0x88, 0xb0, 0x0d, 0xcc, 0x07, 0xd1, 0xdb, 0x06, 0x39, 0x69, 325 | 0x47, 0x6a, 0x9b, 0x4f, 0x0f, 0xbe, 0x3e, 0xd5, 0x67, 0xde, 0xf5, 0xcf, 326 | 0xf6, 0x9d, 0x53, 0x9c, 0x85, 0xe3, 0x29, 0x67, 0x10, 0x58, 0x4a, 0xc7, 327 | 0x9f, 0xd5, 0x7a, 0x1c, 0x0a, 0xc7, 0xee, 0x81, 0x9b, 0x62, 0x59, 0xb2, 328 | 0xc1, 0x44, 0x66, 0x36, 0xc0, 0x8f, 0x59, 0xfe, 0xa9, 0xf3, 0x7f, 0x46, 329 | 0x67, 0xcd, 0x50, 0xcf, 0x5b, 0x49, 0xa9, 0xec, 0x24, 0x51, 0x11, 0xc9, 330 | 0x48, 0x6f, 0xbc, 0xf5, 0x19, 0x6f, 0x31, 0xab, 0x38, 0x08, 0x2a, 0x55, 331 | 0x44, 0x34, 0x4e, 0xc5, 0x97, 0xe5, 0xde, 0xaf, 0x33, 0x62, 0x9d, 0xdc, 332 | 0x76, 0x8d, 0x43, 0x91, 0xe0, 0x96, 0x06, 0x4a, 0xd2, 0x92, 0x9e, 0xa6, 333 | 0xac, 0x12, 0x1f, 0x7e, 0x1c, 0xe4, 0x7d, 0x8e, 0x59, 0xe1, 0x33, 0x1c, 334 | 0xab, 0xe1, 0x0e, 0x5d, 0x48, 0x55, 0x01, 0x2f, 0xed, 0x35, 0x56, 0x2f, 335 | 0x1f, 0x5e, 0x28, 0x78, 0x85, 0x36, 0xa1, 0x8b, 0xe6, 0xb9, 0x1f, 0x06, 336 | 0x03, 0x3f, 0x3d, 0x01, 0x28, 0xc5, 0xc9, 0xfd, 0x99, 0x98, 0x98, 0xec, 337 | 0xa7, 0xcf, 0x0f, 0xfd, 0xa3, 0x16, 0xe2, 0xc5, 0xa4, 0x55, 0x78, 0xd3, 338 | 0xb0, 0x66, 0x9a, 0xe3, 0x90, 0xe8, 0xfd, 0x4b, 0x6a, 0xb6, 0x43, 0x7d, 339 | 0x7d, 0x00, 0x7e, 0x07, 0x1d, 0x1a, 0xa8, 0x58, 0xc9, 0x52, 0x00, 0x99, 340 | 0xc7, 0xf2, 0x85, 0x72, 0xd3, 0x9c, 0xc8, 0x28, 0xa1, 0xf7, 0x7e, 0xb3, 341 | 0x50, 0xcd, 0xb1, 0xff, 0x0b, 0x85, 0xcd, 0x0c, 0x56, 0xe7, 0x85, 0xf9, 342 | 0x37, 0x4a, 0x11, 0xb8, 0xaa, 0x6a, 0x5b, 0x99, 0x56, 0x5e, 0xc5, 0xfc, 343 | 0x20, 0xbe, 0xe5, 0x9d, 0xc2, 0xfd, 0x9c, 0x39, 0x68, 0x04, 0x85, 0x20, 344 | 0xad, 0xb2, 0xa9, 0x7e, 0x81, 0x13, 0x39, 0x85, 0x0f, 0x7c, 0x7e, 0xb9, 345 | 0x16, 0x7d, 0xc1, 0xed, 0xf3, 0x89, 0x3b, 0xe6, 0x27, 0xbc, 0xe2, 0xb2, 346 | 0x82, 0x1b, 0xbc, 0x52, 0x7e, 0x30, 0x26, 0x8f, 0x37, 0xe3, 0x96, 0x2b, 347 | 0x69, 0xdb, 0x05, 0x68, 0x34, 0x5a, 0x6b, 0x9c, 0x13, 0x1f, 0xd1, 0x29, 348 | 0xdd, 0x29, 0xc7, 0x4d, 0x24, 0x58, 0xba, 0xbe, 0x90, 0xae, 0x83, 0x0a, 349 | 0x21, 0x8b, 0x32, 0xdd, 0xa3, 0x3d, 0x08, 0x28, 0x84, 0x38, 0xe5, 0xc9, 350 | 0xd6, 0x4d, 0xf1, 0xd6, 0x59, 0xc9, 0xc1, 0x78, 0x3a, 0xc7, 0xf9, 0xfd, 351 | 0xac, 0x6e, 0x1d, 0x3d, 0xbd, 0xdf, 0x17, 0xa6, 0x87, 0x4a, 0x84, 0xd4, 352 | 0xee, 0x8e, 0x9b, 0x55, 0xf0, 0xc9, 0x6c, 0x50, 0x15, 0x40, 0x5f, 0x2a, 353 | 0xeb, 0x72, 0x48, 0x9c, 0xb5, 0xd4, 0x31, 0x78, 0x39, 0x71, 0x3f, 0x68, 354 | 0x99, 0x26, 0x3e, 0xf5, 0x6c, 0x0c, 0xe2, 0x66, 0x87, 0x0d, 0x82, 0x74, 355 | 0x1f, 0x10, 0x5f, 0xee, 0x26, 0x96, 0x4d, 0x7d, 0xdb, 0xdb, 0x78, 0x75, 356 | 0xe6, 0x99, 0xaf, 0x26, 0x65, 0xa9, 0xc4, 0x2f, 0xaf, 0x53, 0x4a, 0x77, 357 | 0x04, 0xec, 0xa5, 0xa2, 0xec, 0xe2, 0x43, 0x31, 0xdd, 0xd4, 0x0c, 0x03, 358 | 0x64, 0x11, 0x71, 0x91, 0x06, 0x75, 0x19, 0x80, 0x8c, 0x49, 0xba, 0x03, 359 | 0xa7, 0x30, 0x97, 0x84, 0x61, 0x25, 0xa7, 0xad, 0xca, 0x9d, 0x38, 0x0a, 360 | 0x80, 0x95, 0xde, 0xa2, 0x4d, 0xc7, 0x28, 0xb5, 0x72, 0xd1, 0xf8, 0x42, 361 | 0x47, 0xf6, 0xd7, 0x68, 0xf2, 0x0a, 0x0e, 0x9d, 0xca, 0x08, 0xe3, 0xb6, 362 | 0x1f, 0x8e, 0xd6, 0x92, 0xb5, 0x3d, 0x18, 0x57, 0xb2, 0xf9, 0xdc, 0xcc, 363 | 0x8a, 0x1d, 0x19, 0x59, 0x43, 0x87, 0x05, 0xec, 0x0c, 0xa6, 0x6a, 0x59, 364 | 0x11, 0x4c, 0xf1, 0x1e, 0x79, 0x63, 0x67, 0xfd, 0x8d, 0x93, 0xe8, 0x68, 365 | 0x6e, 0xaf, 0x52, 0x6a, 0xef, 0x47, 0xa7, 0x10, 0xc8, 0x28, 0x2a, 0x55, 366 | 0x73, 0x27, 0x99, 0x44, 0x3f, 0x36, 0x8a, 0x18, 0x33, 0x9d, 0x6b, 0x10, 367 | 0x9d, 0x4d, 0x02, 0xcd, 0x34, 0x91, 0xf9, 0x1f, 0x2e, 0x47, 0xe8, 0xbd, 368 | 0x2f, 0xa8, 0xf4, 0xff, 0x3f, 0xbb, 0x10, 0xed, 0x1f, 0xbc, 0x28, 0x05, 369 | 0x25, 0xea, 0x7a, 0x5e, 0xb8, 0x9f, 0xde, 0x48, 0xf9, 0xfe, 0xa1, 0xd0, 370 | 0x02, 0x7b, 0xf1, 0x6d, 0xaf, 0xc7, 0x0d, 0xe1, 0x0b, 0xd1, 0x48, 0xe0, 371 | 0xb0, 0x2f, 0x8b, 0xc5, 0x8c, 0xda, 0x9b, 0x0d, 0xd4, 0x0d, 0x2b, 0xb6, 372 | 0x15, 0xe9, 0x4f, 0xa9, 0x22, 0x12, 0x4f, 0x3a, 0x48, 0x50, 0x0e, 0x36, 373 | 0x0b, 0x01, 0xda, 0x55, 0x34, 0x47, 0xa5, 0xb6, 0x24, 0xc7, 0x57, 0xb6, 374 | 0x88, 0xd5, 0x06, 0xee, 0xec, 0xd8, 0x58, 0xf6, 0xb1, 0xc8, 0x60, 0x9a, 375 | 0x2d, 0xea, 0x48, 0x3a, 0x38, 0x8a, 0x92, 0xbf, 0xdc, 0xae, 0x2f, 0x25, 376 | 0xe2, 0xa6, 0x21, 0x95, 0xd4, 0xd1, 0xbb, 0xd7, 0x2e, 0x6d, 0xc7, 0x08, 377 | 0xb8, 0xf0, 0x56, 0x2e, 0x15, 0x49, 0x1e, 0x36, 0x9b, 0x16, 0xa5, 0xea, 378 | 0x87, 0x42, 0x18, 0xea, 0xda, 0x0a, 0x45, 0xf4, 0x64, 0x5d, 0x02, 0xf4, 379 | 0x32, 0x6a, 0x46, 0x60, 0x42, 0x5a, 0x02, 0xac, 0x6a, 0xe4, 0xaa, 0xec, 380 | 0xf6, 0xa1, 0x7d, 0x4e, 0x0f, 0x5e, 0x9c, 0x84, 0x4e, 0x88, 0x54, 0xc6, 381 | 0xac, 0xca, 0x33, 0x43, 0xae, 0x36, 0x26, 0x05, 0xd5, 0xad, 0x7d, 0x46, 382 | 0xa2, 0x1a, 0x48, 0x8c, 0x09, 0xd6, 0x91, 0x83, 0x8e, 0x4e, 0x1f, 0x68, 383 | 0x91, 0xbc, 0x2b, 0x98, 0x12, 0xb7, 0xba, 0x5d, 0xf9, 0xba, 0x99, 0x6f, 384 | 0x9e, 0x95, 0x34, 0x29, 0x19, 0xc0, 0x7a, 0xa9, 0xd2, 0xb0, 0xe7, 0xda, 385 | 0xcf, 0xf0, 0x28, 0x0e, 0x3b, 0x1d, 0xff, 0x9a, 0x41, 0x04, 0xc9, 0x27, 386 | 0xc6, 0x86, 0xb0, 0x5c, 0xf0, 0xe3, 0x94, 0xb1, 0x7e, 0x16, 0x66, 0xe5, 387 | 0xdc, 0xe3, 0x93, 0xfc, 0x4d, 0xc2, 0x7b, 0xff, 0x20, 0xcd, 0x69, 0xa4, 388 | 0x9e, 0xbd, 0x10, 0x78, 0xd8, 0x27, 0xae, 0x0d, 0xdc, 0x99, 0x56, 0x0a, 389 | 0xa7, 0x78, 0xe5, 0xbf, 0xf2, 0xa0, 0x68, 0x57, 0x8a, 0x5d, 0x94, 0x88, 390 | 0xfb, 0x14, 0x49, 0xa1, 0x53, 0x8d, 0x6b, 0x5c, 0x48, 0xef, 0xcc, 0x8f, 391 | 0x1f, 0xc0, 0x0e, 0x68, 0xea, 0xdd, 0x5c, 0xd4, 0xa8, 0xd0, 0x0e, 0xc1, 392 | 0xf5, 0xf3, 0x60, 0x42, 0xf9, 0x27, 0x03, 0xa6, 0xe7, 0x09, 0xf0, 0xe8, 393 | 0x7f, 0x72, 0x6b, 0xd3, 0x3f, 0x39, 0x1b, 0x13, 0xd3, 0x5a, 0x1c, 0x0a, 394 | 0x68, 0x36, 0xa7, 0xcd, 0xd0, 0x83, 0xf8, 0xda, 0xe7, 0x30, 0x14, 0x3a, 395 | 0x46, 0xc9, 0x32, 0xff, 0xac, 0x95, 0x40, 0xb2, 0x48, 0x3f, 0xfc, 0x60, 396 | 0x9d, 0xe5, 0x4d, 0x71, 0x8e, 0x6a, 0xf0, 0x0f, 0x26, 0x32, 0x2b, 0xd8, 397 | 0xa6, 0xd8, 0x10, 0x4d, 0xd5, 0xf5, 0xb6, 0xac, 0xd8, 0x4a, 0xa8, 0xae, 398 | 0xb8, 0xf4, 0x7e, 0xb8, 0xc2, 0x71, 0xc5, 0x47, 0xfc, 0xaa, 0x52, 0xd9, 399 | 0x18, 0x43, 0x5e, 0xd0, 0x53, 0xd2, 0xad, 0x11, 0xb7, 0x0c, 0x39, 0x2c, 400 | 0x81, 0x1f, 0xb5, 0x0f, 0x21, 0xbd, 0x68, 0x78, 0xc3, 0x6f, 0x52, 0x06, 401 | 0x69, 0xe1, 0x20, 0x92, 0x11, 0xa9, 0x3f, 0xc0, 0x32, 0x4e, 0x19, 0x52, 402 | 0xfc, 0x83, 0xa4, 0x29, 0x70, 0xcc, 0xcf, 0x7e, 0x61, 0x68, 0xb7, 0x25, 403 | 0x73, 0xf7, 0x64, 0x17, 0xa3, 0x3d, 0x17, 0xcd, 0x50, 0x6f, 0x71, 0x64, 404 | 0xa4, 0xb1, 0x08, 0xdd, 0x85, 0xb5, 0x08, 0x26, 0xf5, 0x95, 0x33, 0xac, 405 | 0x76, 0x18, 0xda, 0x53, 0x31, 0x60, 0xc9, 0x78, 0x16, 0x5d, 0xcf, 0x10, 406 | 0x7a, 0x80, 0x7c, 0xa7, 0x61, 0x23, 0x53, 0x83, 0x70, 0x6c, 0x63, 0xa0, 407 | 0xf6, 0x67, 0x48, 0x3c, 0x42, 0x9e, 0x96, 0xc0, 0x61, 0x85, 0x4a, 0xd8, 408 | 0x15, 0xe0, 0xb0, 0xd8, 0x65, 0xef, 0xff, 0x9f, 0xa3, 0xc2, 0xf9, 0x49, 409 | 0xa3, 0x78, 0x9b, 0xac, 0x61, 0x9e, 0xc6, 0x90, 0x64, 0x8b, 0x3f, 0xd7, 410 | 0x82, 0xce, 0x37, 0xb3, 0x63, 0xf4, 0xa0, 0x7d, 0xfc, 0x74, 0xc5, 0xee, 411 | 0x23, 0x42, 0xe9, 0x5f, 0xd6, 0x51, 0xe3, 0x32, 0x03, 0x46, 0xe1, 0xed, 412 | 0xf7, 0x5a, 0x99, 0x1e, 0x15, 0x6f, 0xd6, 0x3a, 0x3a, 0x56, 0xa5, 0xa5, 413 | 0xa5, 0x99, 0xe7, 0x18, 0x05, 0xce, 0x41, 0x8a, 0x9e, 0xd3, 0x0b, 0x07, 414 | 0x3d, 0x15, 0x33, 0xbb, 0x27, 0x35, 0xb8, 0x90, 0x02, 0x57, 0xce, 0xa7, 415 | 0xb9, 0x43, 0x61, 0x4b, 0x98, 0x7d, 0x9f, 0x5b, 0x83, 0xbe, 0x7d, 0x69, 416 | 0xa4, 0x81, 0x3b, 0xd7, 0x7e, 0xb3, 0x87, 0xb9, 0x7e, 0x47, 0xf7, 0x4b, 417 | 0x60, 0x0e, 0x47, 0x62, 0x28, 0x8f, 0xee, 0x91, 0x57, 0x86, 0x94, 0x0d, 418 | 0x02, 0x4f, 0xfd, 0x70, 0xa9, 0x4a, 0xb9, 0xf4, 0xe2, 0xa8, 0xc5, 0x16, 419 | 0x2e, 0xa4, 0xa8, 0xed, 0x97, 0xa2, 0x51, 0xbc, 0x1c, 0x44, 0xf4, 0xd8, 420 | 0x7f, 0x17, 0x2d, 0x3e, 0xab, 0x75, 0x88, 0xcb, 0x7a, 0x74, 0x4f, 0xb0, 421 | 0x0e, 0x7c, 0x8a, 0x75, 0x8a, 0xcd, 0x8e, 0xf3, 0x0a, 0x02, 0x80, 0x67, 422 | 0x35, 0xa1, 0xfb, 0xb4, 0x18, 0x6f, 0x6e, 0xf1, 0xc4, 0x6d, 0x09, 0x3d, 423 | 0x87, 0x6a, 0x07, 0x7c, 0xe1, 0x7d, 0xbf, 0x94, 0xd3, 0x47, 0x81, 0x0e, 424 | 0x78, 0x51, 0xc6, 0x41, 0x9c, 0x1b, 0x40, 0xfc, 0x31, 0xe0, 0x6f, 0x51, 425 | 0xbd, 0xec, 0xce, 0x87, 0x2a, 0xa6, 0x7e, 0x48, 0x68, 0x4c, 0x59, 0xa2, 426 | 0x1e, 0x23, 0x70, 0xee, 0xf3, 0x67, 0x40, 0xbf, 0x2e, 0x6e, 0x95, 0xa1, 427 | 0x81, 0xe0, 0xd9, 0xb1, 0xab, 0xb1, 0xc9, 0xd9, 0xba, 0x75, 0xa6, 0x9d, 428 | 0x7a, 0x72, 0x0d, 0x52, 0xec, 0xf5, 0x1a, 0x11, 0x35, 0x5a, 0xc7, 0x2f, 429 | 0xbc, 0x36, 0x07, 0x62, 0x94, 0xb4, 0x4c, 0xd5, 0x1d, 0x60, 0x48, 0x27, 430 | 0xf6, 0x88, 0xbd, 0xc3, 0x74, 0xb4, 0x74, 0x20, 0x36, 0xad, 0x72, 0x2d, 431 | 0x74, 0x7a, 0x0e, 0x0e, 0x47, 0x54, 0x26, 0xf3, 0x75, 0x31, 0x1a, 0x48, 432 | 0xc4, 0x43, 0xc8, 0xfd, 0x81, 0x2e, 0xa2, 0xa4, 0x36, 0xad, 0x67, 0x65, 433 | 0xde, 0x83, 0xaa, 0xda, 0x51, 0xd2, 0xb8, 0xae, 0x32, 0x7a, 0xeb, 0xea, 434 | 0xfb, 0x97, 0x84, 0xfe, 0xa1, 0xc6, 0x26, 0xc8, 0x16, 0xee, 0x9b, 0xf5, 435 | 0x7d, 0x75, 0x71, 0xca, 0x07, 0x4f, 0xc6, 0x5a, 0x27, 0xc4, 0xbf, 0xec, 436 | 0x1d, 0x8e, 0x5f, 0xa2, 0x0e, 0xe8, 0xa3, 0x53, 0xc4, 0x11, 0x60, 0xc9, 437 | 0x9b, 0xe3, 0x50, 0x5c, 0xb2, 0x3d, 0xdf, 0x04, 0x5a, 0x50, 0x77, 0xfa, 438 | 0x05, 0x32, 0x29, 0x6a, 0x81, 0xd2, 0xca, 0xa9, 0x16, 0x22, 0x43, 0x93, 439 | 0x73, 0x4b, 0x5d, 0x01, 0x73, 0x29, 0x21, 0xe9, 0x07, 0xf9, 0xf2, 0x63, 440 | 0x7a, 0x10, 0x4d, 0x81, 0xb0, 0x5f, 0xa8, 0xf5, 0xfe, 0x26, 0xb0, 0x14, 441 | 0x6c, 0x32, 0xc4, 0x6b, 0x15, 0x99, 0xd3, 0x7d, 0x45, 0x37, 0xbe, 0xe3, 442 | 0xb4, 0x0f, 0x63, 0xa6, 0x63, 0x7b, 0x5a, 0x0c, 0xde, 0x4d, 0xb8, 0xcb, 443 | 0x2c, 0xec, 0xb2, 0x54, 0x19, 0x19, 0x54, 0x56, 0xd9, 0xb1, 0x05, 0xa5, 444 | 0xdf, 0xbb, 0xe8, 0xb6, 0x6c, 0xec, 0x79, 0xa8, 0xd8, 0x6b, 0xeb, 0x01, 445 | 0xae, 0x40, 0xa0, 0x62, 0xcb, 0x87, 0xa5, 0x76, 0x6b, 0x4f, 0x6f, 0x45, 446 | 0x2c, 0x7f, 0x67, 0xf9, 0xec, 0x8e, 0xdf, 0xa4, 0xe7, 0xb0, 0x37, 0xa9, 447 | 0x2f, 0x2b, 0xce, 0x52, 0x6d, 0x33, 0x1c, 0x79, 0x56, 0x31, 0x60, 0x31, 448 | 0x8b, 0xe8, 0xcb, 0xf4, 0xc6, 0xe4, 0xc5, 0x76, 0xa2, 0xa4, 0xf5, 0x13, 449 | 0xe4, 0x59, 0xce, 0x42, 0x15, 0xf1, 0x81, 0x82, 0x70, 0x8c, 0x74, 0x0e, 450 | 0x9c, 0x5e, 0x53, 0xce, 0x09, 0xdc, 0xd6, 0xa2, 0x42, 0xb7, 0x65, 0xeb, 451 | 0x14, 0xaa, 0x11, 0xfe, 0xe8, 0x74, 0x94, 0xa9, 0x10, 0xa9, 0x7d, 0xc2, 452 | 0x73, 0x78, 0x50, 0xbc, 0x40, 0x67, 0xba, 0x3e, 0x22, 0x77, 0xe5, 0xfd, 453 | 0x01, 0xc9, 0xd1, 0x63, 0x31, 0x03, 0xfa, 0x7f, 0x96, 0xfd, 0x0f, 0x55, 454 | 0x6a, 0x9b, 0x7b, 0xfe, 0x9b, 0x5a, 0x2c, 0x11, 0x3c, 0xdc, 0x63, 0x3e, 455 | 0x14, 0xa8, 0x6c, 0xec, 0x30, 0x64, 0x3f, 0x94, 0xef, 0xb4, 0xe1, 0x72, 456 | 0x7b, 0x91, 0xaa, 0x6e, 0xe6, 0x5c, 0x96, 0x77, 0x14, 0x2d, 0xa9, 0xb7, 457 | 0x94, 0x72, 0x1a, 0x1e, 0xd5, 0x22, 0xd6, 0xb6, 0x22, 0x5d, 0xa9, 0x83, 458 | 0xb7, 0x07, 0x8e, 0x7a, 0xfc, 0x18, 0x5a, 0x00, 0x1d, 0xb3, 0x6a, 0xfc, 459 | 0x0c, 0x30, 0xec, 0xfa, 0xaa, 0x62, 0x80, 0xb5, 0xf2, 0x54, 0xdc, 0xb6, 460 | 0x10, 0xd8, 0x76, 0x27, 0x37, 0x35, 0xfb, 0xb3, 0x4d, 0xb5, 0xf0, 0x7c, 461 | 0xba, 0xf1, 0x79, 0x05, 0x5a, 0xeb, 0x41, 0x3f, 0x53, 0xbd, 0xa7, 0x12, 462 | 0x41, 0xd6, 0xed, 0x87, 0x12, 0x3b, 0x16, 0x7f, 0xe1, 0x32, 0x07, 0x99, 463 | 0x9d, 0x2e, 0xe3, 0x0a, 0x38, 0x00, 0x29, 0x86, 0x3e, 0x68, 0xea, 0xdb, 464 | 0x94, 0xfa, 0x84, 0x38, 0x57, 0xb6, 0xa6, 0x0c, 0xb4, 0x35, 0xa6, 0x3e, 465 | 0x8a, 0xc3, 0x19, 0x63, 0x35, 0x8e, 0xe4, 0xaa, 0x8c, 0x49, 0x37, 0xba, 466 | 0x1d, 0xaf, 0x5f, 0xd6, 0xe3, 0x8e, 0x45, 0xeb, 0x56, 0xd0, 0xf9, 0x2e, 467 | 0xce, 0xf2, 0xb7, 0x1c, 0x9c, 0x35, 0xa7, 0x2b, 0x53, 0x60, 0x24, 0x6d, 468 | 0xca, 0xfb, 0x8b, 0xe5, 0xcf, 0x2a, 0xd2, 0x11, 0x65, 0x75, 0x3d, 0xd8, 469 | 0xb2, 0xcb, 0xca, 0xfb, 0xd5, 0x6e, 0x82, 0x12, 0x26, 0xa8, 0xf7, 0x46, 470 | 0x52, 0x4e, 0xbf, 0xf9, 0xf5, 0x4b, 0xff, 0x18, 0xf4, 0x98, 0x9b, 0x58, 471 | 0x2e, 0x2b, 0xbd, 0x48, 0xb5, 0xc0, 0x66, 0x85, 0xce, 0x1f, 0xb1, 0xf7, 472 | 0xdb, 0x14, 0xd6, 0x22, 0x36, 0x52, 0x8a, 0x4a, 0xec, 0xb2, 0xf2, 0x12, 473 | 0x61, 0xa6, 0x3e, 0x76, 0x0c, 0xac, 0x2e, 0xe7, 0xf1, 0xf4, 0x4e, 0xb0, 474 | 0x0c, 0xd8, 0x1e, 0x34, 0xb1, 0xf3, 0x8d, 0x84, 0x2e, 0x29, 0x65, 0x31, 475 | 0x04, 0x9f, 0x69, 0x0b, 0x8c, 0xa1, 0x11, 0x47, 0x0d, 0x99, 0xe0, 0x33, 476 | 0xc4, 0xae, 0x3b, 0x21, 0x81, 0xb4, 0xa1, 0x6e, 0xd6, 0x04, 0x90, 0xec, 477 | 0x20, 0x16, 0xfa, 0xcf, 0x0c, 0xb1, 0xac, 0x22, 0x0e, 0x2e, 0xe5, 0x73, 478 | 0x9f, 0x0b, 0x0a, 0x27, 0x1b, 0x84, 0x44, 0xd9, 0x42, 0xe7, 0xf7, 0xe0, 479 | 0xd6, 0x02, 0xe9, 0x42, 0xa4, 0x34, 0x4a, 0xf1, 0x25, 0xe8, 0x24, 0xae, 480 | 0x33, 0xec, 0x5d, 0x9f, 0x4e, 0x29, 0xe1, 0xea, 0x66, 0x96, 0xd2, 0xe7, 481 | 0x9e, 0xf6, 0xd2, 0x84, 0x75, 0x49, 0xec, 0xdb, 0xe0, 0x57, 0xb0, 0x14, 482 | 0x26, 0x2f, 0xab, 0x68, 0xe2, 0x0e, 0xe7, 0x62, 0xe7, 0xf5, 0x3f, 0x23, 483 | 0x8d, 0x6e, 0xee, 0x26, 0x77, 0xd4, 0xbe, 0x5d, 0x54, 0x5a, 0xf0, 0x18, 484 | 0x7a, 0x49, 0xdc, 0xad, 0x54, 0x6a, 0x53, 0x71, 0x3b, 0x7f, 0xd2, 0x7c, 485 | 0xa4, 0x2d, 0x37, 0x0c, 0xf7, 0x84, 0xc2, 0x6f, 0x01, 0x4d, 0x55, 0x51, 486 | 0x63, 0xcc, 0x0e, 0x3b, 0x00, 0x19, 0xf3, 0x1e, 0x7d, 0x6b, 0x66, 0x74, 487 | 0x31, 0x67, 0xed, 0xdf, 0x58, 0x26, 0xfb, 0x2f, 0xb6, 0x17, 0x9f, 0x9d, 488 | 0x33, 0xab, 0xf6, 0x74, 0x21, 0xcb, 0x3e, 0x07, 0xa5, 0xbe, 0xf2, 0xb3, 489 | 0x2c, 0xe7, 0x6b, 0xe2, 0xd3, 0x4f, 0xd4, 0x64, 0xef, 0x85, 0x83, 0xbc, 490 | 0x72, 0xa2, 0x5a, 0xb2, 0xa4, 0x36, 0xc3, 0xe9, 0x72, 0x13, 0x12, 0x5a, 491 | 0xaa, 0xe5, 0xb7, 0x5a, 0x30, 0xdc, 0x66, 0x26, 0xdb, 0x9c, 0xd5, 0x1d, 492 | 0x1f, 0x58, 0x14, 0xd4, 0x7b, 0xa8, 0x90, 0xc3, 0x47, 0x46, 0xc9, 0xaa, 493 | 0x20, 0x74, 0xe1, 0x95, 0xa6, 0x3c, 0x15, 0xeb, 0xe0, 0x7a, 0xbf, 0xf2, 494 | 0xbd, 0xc0, 0x63, 0xde, 0x13, 0x7b, 0xfd, 0xc4, 0xea, 0x49, 0xf4, 0xc9, 495 | 0x32, 0x43, 0x12, 0xd2, 0xe5, 0xeb, 0xe6, 0x7c, 0x1c, 0xad, 0x2b, 0x55, 496 | 0xc8, 0xda, 0xb0, 0xe0, 0xfb, 0x9d, 0x79, 0xca, 0x35, 0x03, 0x9d, 0x3b, 497 | 0x3d, 0x2d, 0x9b, 0xef, 0x63, 0x97, 0xe1, 0x75, 0x9e, 0x0d, 0x8a, 0xbc, 498 | 0x5d, 0xe8, 0x16, 0x08, 0x53, 0xc1, 0x36, 0x20, 0xaf, 0x36, 0xf1, 0xbe, 499 | 0xe8, 0x16, 0x61, 0xcc, 0xc9, 0x87, 0x40, 0xcf, 0x5e, 0xde, 0xf0, 0x57, 500 | 0x7d, 0xac, 0x5d, 0xde, 0xec, 0xf6, 0xae, 0xa6, 0x04, 0xdf, 0x6d, 0xf9, 501 | 0xeb, 0x9f, 0x66, 0x59, 0xae, 0x7f, 0x39, 0x3f, 0x07, 0x99, 0x48, 0x1d, 502 | 0x44, 0x94, 0xb3, 0x61, 0x2f, 0x29, 0x91, 0x08, 0x6a, 0x36, 0x4c, 0x94, 503 | 0x02, 0xc3, 0xaf, 0xea, 0x03, 0x82, 0x45, 0x3e, 0xc5, 0xdb, 0x32, 0x8c, 504 | 0x3f, 0x02, 0xe7, 0xfd, 0xe4, 0x2b, 0xb7, 0x10, 0x2e, 0x8f, 0x9e, 0xaa, 505 | 0x4f, 0x86, 0x6a, 0x5b, 0x2a, 0x11, 0xa1, 0x15, 0x08, 0x1a, 0x09, 0x57, 506 | 0xf7, 0xf2, 0xa0, 0x17, 0x7c, 0x14, 0x78, 0xb0, 0x12, 0xfd, 0x87, 0x0c, 507 | 0x79, 0xed, 0xe3, 0xf5, 0xab, 0xd4, 0xd8, 0x00, 0x4f, 0xff, 0xd4, 0x38, 508 | 0xf2, 0x74, 0x90, 0x54, 0x74, 0xcf, 0xf9, 0x04, 0x4c, 0x84, 0xb1, 0xc6, 509 | 0xe9, 0xc9, 0xc5, 0x14, 0x64, 0xa2, 0x13, 0x98, 0x86, 0x34, 0x5e, 0xd8, 510 | 0x63, 0x0a, 0x56, 0xf9, 0xa0, 0x5a, 0xff, 0xf3, 0x2d, 0x94, 0x0b, 0xbf, 511 | 0xbd, 0xc7, 0x07, 0x20, 0x2d, 0x1c, 0x94, 0x14, 0x43, 0xa1, 0x0f, 0x19, 512 | 0xfa, 0x6f, 0x78, 0x3c, 0xea, 0x8b, 0xcb, 0xe6, 0x4b, 0x1e, 0xc0, 0x46, 513 | 0x9d, 0xd5, 0x3b, 0xf5, 0x42, 0x65, 0x39, 0xe4, 0x3d, 0x37, 0xb6, 0x8e, 514 | 0xcc, 0x55, 0x48, 0xb6, 0x8d, 0x3a, 0x31, 0x7a, 0x7e, 0x3e, 0x61, 0x19, 515 | 0x95, 0x86, 0xfd, 0x3d, 0x07, 0x80, 0xb1, 0xf0, 0x8b, 0x18, 0x81, 0xb7, 516 | 0x79, 0x96, 0x4c, 0xd8, 0x26, 0x5d, 0xa8, 0xc1, 0x7e, 0x79, 0xdf, 0x5e, 517 | 0x9f, 0x15, 0x43, 0xd5, 0xa3, 0xa7, 0x61, 0x1a, 0x36, 0x68, 0x67, 0xac, 518 | 0x26, 0xd5, 0xfd, 0x46, 0x62, 0x6a, 0x84, 0xf4, 0x67, 0xca, 0x43, 0x24, 519 | 0x68, 0x18, 0xa5, 0x34, 0x35, 0x48, 0x22, 0x5b, 0xa6, 0xb7, 0xc4, 0xda, 520 | 0x44, 0xc5, 0xa7, 0xf5, 0xf2, 0x0e, 0x74, 0xc2, 0x3b, 0x90, 0xa2, 0x69, 521 | 0xb3, 0xdb, 0x31, 0xec, 0x7e, 0xc7, 0x38, 0x86, 0xeb, 0x88, 0x84, 0xf4]) 522 | 523 | h = KravatteOracle(my_message, my_key, workers=test_workers) 524 | 525 | index_ref = [(0, 1), (200, 202), (400, 404), (600, 608), (800, 816), (1000, 1032), 526 | (1200, 1264), (1400, 1528), (1600, 1856), (2000, 2512), (2600, 3624), 527 | (3800, 5848)] 528 | 529 | for x in range(12): 530 | k = 2**x 531 | p = h.random(k) 532 | start, stop = index_ref[x] 533 | m = real_output[start:stop] 534 | assert m == p 535 | -------------------------------------------------------------------------------- /kravatte/kravatte.py: -------------------------------------------------------------------------------- 1 | """ 2 | Kravatte Achouffe Cipher Suite: Encryption, Decryption, and Authentication Tools based on the Farfalle modes 3 | Copyright 2018 Michael Calvin McCoy 4 | see LICENSE file 5 | """ 6 | from multiprocessing import Pool 7 | from math import floor, ceil, log2 8 | from typing import Tuple 9 | from os import cpu_count 10 | from ctypes import memset 11 | import numpy as np 12 | 13 | KravatteTagOutput = Tuple[bytes, bytes] 14 | KravatteValidatedOutput = Tuple[bytes, bool] 15 | 16 | 17 | class Kravatte(object): 18 | """Implementation of the Farfalle Pseudo-Random Function (PRF) construct utilizing the 19 | Keccak-1600 permutation. 20 | """ 21 | KECCAK_BYTES = 200 22 | '''Number of Bytes in Keccak-1600 state''' 23 | KECCAK_LANES = 25 24 | '''Number of 8-Byte lanes in Keccak-1600 state''' 25 | 26 | KECCAK_PLANES_SLICES = 5 27 | ''' Size of x/y dimensions of Keccak lane array ''' 28 | 29 | THETA_REORDER = ((4, 0, 1, 2, 3), (1, 2, 3, 4, 0)) 30 | 31 | IOTA_CONSTANTS = np.array([0x000000000000800A, 0x800000008000000A, 0x8000000080008081, 32 | 0x8000000000008080, 0x0000000080000001, 0x8000000080008008], 33 | dtype=np.uint64) 34 | '''Iota Step Round Constants For Keccak-p(1600, 4) and Keccak-p(1600, 6)''' 35 | 36 | RHO_SHIFTS = np.array([[0, 36, 3, 41, 18], 37 | [1, 44, 10, 45, 2], 38 | [62, 6, 43, 15, 61], 39 | [28, 55, 25, 21, 56], 40 | [27, 20, 39, 8, 14]], dtype=np.uint64) 41 | '''Lane Shifts for Rho Step''' 42 | 43 | CHI_REORDER = ((1, 2, 3, 4, 0), (2, 3, 4, 0, 1)) 44 | '''Lane Re-order Mapping for Chi Step''' 45 | 46 | PI_ROW_REORDER = np.array([[0, 3, 1, 4, 2], 47 | [1, 4, 2, 0, 3], 48 | [2, 0, 3, 1, 4], 49 | [3, 1, 4, 2, 0], 50 | [4, 2, 0, 3, 1]]) 51 | '''Row Re-order Mapping for Pi Step''' 52 | 53 | PI_COLUMN_REORDER = np.array([[0, 0, 0, 0, 0], 54 | [1, 1, 1, 1, 1], 55 | [2, 2, 2, 2, 2], 56 | [3, 3, 3, 3, 3], 57 | [4, 4, 4, 4, 4]]) 58 | '''Column Re-order Mapping for Pi Step''' 59 | 60 | COMPRESS_ROW_REORDER = np.array([[0, 0, 0, 0, 1], 61 | [1, 1, 1, 1, 2], 62 | [2, 2, 2, 2, 3], 63 | [3, 3, 3, 3, 4], 64 | [4, 4, 4, 4, 0]]) 65 | '''Row Re-order Mapping for Compress Step''' 66 | 67 | COMPRESS_COLUMN_REORDER = np.array([[0, 1, 2, 3, 4], 68 | [0, 1, 2, 3, 4], 69 | [0, 1, 2, 3, 4], 70 | [0, 1, 2, 3, 4], 71 | [0, 1, 2, 3, 4]]) 72 | '''Column Re-order Mapping for Compress Step''' 73 | 74 | EXPAND_ROW_REORDER = np.array([[0, 0, 0, 1, 1], 75 | [1, 1, 1, 2, 2], 76 | [2, 2, 2, 3, 3], 77 | [3, 3, 3, 4, 4], 78 | [4, 4, 4, 0, 0]]) 79 | '''Row Re-order Mapping for Expand Step''' 80 | 81 | EXPAND_COLUMN_REORDER = np.array([[0, 1, 2, 3, 4], 82 | [0, 1, 2, 3, 4], 83 | [0, 1, 2, 3, 4], 84 | [0, 1, 2, 3, 4], 85 | [0, 1, 2, 4, 4]]) 86 | '''Column Re-order Mapping for Expand Step''' 87 | 88 | def __init__(self, key: bytes=b'', workers: int=None, mp_input: bool=True, mp_output: bool=True): 89 | """ 90 | Initialize Kravatte with user key 91 | 92 | Inputs: 93 | key (bytes): encryption/authentication key 94 | workers (int): parallel processes to use in compression/expansion operations 95 | mp_input (bool): Enable multi-processing for calculations on input data 96 | mp_output (bool): Enable multi-processing for calculations on output data 97 | """ 98 | self.update_key(key) 99 | self.reset_state() 100 | # Enable Standard or Optimized Multi-process codepaths 101 | if workers is not None: 102 | self.collect_message = self._collect_message_mp if mp_input else self._collect_message_sp 103 | self.generate_digest = self._generate_digest_mp if mp_output else self._generate_digest_sp 104 | self.workers = cpu_count() if workers == 0 else workers 105 | else: 106 | self.collect_message = self._collect_message_sp 107 | self.generate_digest = self._generate_digest_sp 108 | self.workers = None 109 | 110 | def update_key(self, key: bytes) -> None: 111 | """ 112 | Pad and compute new Kravatte base key from bytes source. 113 | 114 | Inputs: 115 | key (bytes): user provided bytes to be padded (if necessary) and computed into Kravatte base key 116 | """ 117 | key_pad = self._pad_10_append(key, self.KECCAK_BYTES) 118 | key_array = np.frombuffer(key_pad, dtype=np.uint64, count=self.KECCAK_LANES, 119 | offset=0).reshape([self.KECCAK_PLANES_SLICES, 120 | self.KECCAK_PLANES_SLICES], order='F') 121 | self.kra_key = self._keccak(key_array) 122 | 123 | def reset_state(self) -> None: 124 | """ 125 | Clear existing Farfalle/Kravatte state and prepares for new input message collection. 126 | Elements reset include: 127 | - Message block collector 128 | - Rolling key 129 | - Currently stored output digest 130 | - Digest Active and New Collector Flags 131 | 132 | Inputs: 133 | None 134 | """ 135 | self.roll_key = np.copy(self.kra_key) 136 | self.collector = np.zeros([5, 5], dtype=np.uint64) 137 | self.digest = bytearray(b'') 138 | self.digest_active = False 139 | self.new_collector = True 140 | 141 | def _generate_absorb_queue(self, absorb_steps: int, kra_msg: bytes): 142 | """ 143 | Generator for Keccak-sized blocks of input message for Farfalle compression 144 | 145 | Inputs: 146 | absorb_steps (int): Number of blocks to generate for absorption 147 | kra_msg (bytes): padded input message ready for slicing into input blocks 148 | """ 149 | for msg_block in range(absorb_steps): 150 | yield (np.frombuffer(kra_msg, dtype=np.uint64, count=25, offset=msg_block * self.KECCAK_BYTES).reshape([5, 5], order='F') ^ self.roll_key) 151 | self.roll_key = self._kravatte_roll_compress(self.roll_key) 152 | 153 | def _collect_message_sp(self, message: bytes, append_bits: int=0, append_bit_count: int=0) -> None: 154 | """ 155 | Pad and Process Blocks of Message into Kravatte collector state 156 | 157 | Inputs: 158 | message (bytes): arbitrary number of bytes to be padded into Keccak blocks and absorbed into the collector 159 | append_bits (int): bits to append to the message before padding. Required for more advanced Kravatte modes. 160 | append_bit_count (int): number of bits to append 161 | """ 162 | if self.digest_active: 163 | self.reset_state() 164 | 165 | if self.new_collector: 166 | self.new_collector = False 167 | else: 168 | self.roll_key = self._kravatte_roll_compress(self.roll_key) 169 | 170 | # Pad Message 171 | msg_len = len(message) 172 | kra_msg = self._pad_10_append(message, msg_len + (self.KECCAK_BYTES - (msg_len % self.KECCAK_BYTES)), append_bits, append_bit_count) 173 | absorb_steps = len(kra_msg) // self.KECCAK_BYTES 174 | 175 | # Absorb into Collector 176 | for msg_block in range(absorb_steps): 177 | m = np.frombuffer(kra_msg, dtype=np.uint64, count=25, offset=msg_block * self.KECCAK_BYTES).reshape([5, 5], order='F') 178 | m_k = m ^ self.roll_key 179 | self.roll_key = self._kravatte_roll_compress(self.roll_key) 180 | self.collector = self.collector ^ self._keccak(m_k) 181 | 182 | def _collect_message_mp(self, message: bytes, append_bits: int=0, append_bit_count: int=0) -> None: 183 | """ 184 | Pad and Process Blocks of Message into Kravatte collector state - Multi-process Aware Variant 185 | 186 | Inputs: 187 | message (bytes): arbitrary number of bytes to be padded into Keccak blocks and absorbed into the collector 188 | append_bits (int): bits to append to the message before padding. Required for more advanced Kravatte modes. 189 | append_bit_count (int): number of bits to append 190 | """ 191 | if self.digest_active: 192 | self.reset_state() 193 | 194 | if self.new_collector: 195 | self.new_collector = False 196 | else: 197 | self.roll_key = self._kravatte_roll_compress(self.roll_key) 198 | 199 | # Pad Message 200 | msg_len = len(message) 201 | kra_msg = self._pad_10_append(message, msg_len + (self.KECCAK_BYTES - (msg_len % self.KECCAK_BYTES)), append_bits, append_bit_count) 202 | absorb_steps = len(kra_msg) // self.KECCAK_BYTES 203 | workload = 1 if (absorb_steps // self.workers) == 0 else (absorb_steps // self.workers) 204 | with Pool(processes=self.workers) as kravatte_pool: 205 | for output_element in kravatte_pool.imap_unordered(self._keccak, self._generate_absorb_queue(absorb_steps, kra_msg), chunksize=workload): 206 | self.collector ^= output_element 207 | 208 | def _generate_digest_sp(self, output_size: int, short_kravatte: bool=False) -> None: 209 | """ 210 | Squeeze an arbitrary number of bytes from collector state 211 | 212 | Inputs: 213 | output_size (int): Number of bytes to generate and store in Kravatte digest parameter 214 | short_kravatte (bool): Enable disable short kravatte required for other Kravatte modes 215 | """ 216 | if not self.digest_active: 217 | self.collector = self.collector if short_kravatte else self._keccak(self.collector) 218 | self.roll_key = self._kravatte_roll_compress(self.roll_key) 219 | self.digest_active = True 220 | 221 | self.digest = bytearray(b'') 222 | 223 | full_output_size = output_size + (200 - (output_size % 200)) if output_size % 200 else output_size 224 | generate_steps = full_output_size // 200 225 | 226 | for _ in range(generate_steps): 227 | collector_squeeze = self._keccak(self.collector) 228 | self.collector = self._kravatte_roll_expand(self.collector) 229 | self.digest.extend((collector_squeeze ^ self.roll_key).tobytes('F')) 230 | 231 | self.digest = self.digest[:output_size] 232 | 233 | def _generate_squeeze_queue(self, generate_steps: int): 234 | """ 235 | Generator for Keccak-sized blocks of expanded collector state for output squeezing 236 | 237 | Inputs: 238 | generate_steps (int): Number of blocks to generate and for absorb 239 | """ 240 | for _ in range(generate_steps): 241 | yield self.collector 242 | self.collector = self._kravatte_roll_expand(self.collector) 243 | 244 | def _generate_digest_mp(self, output_size: int, short_kravatte: bool=False) -> None: 245 | """ 246 | Squeeze an arbitrary number of bytes from collector state - Multi-process Aware Variant 247 | 248 | Inputs: 249 | output_size (int): Number of bytes to generate and store in Kravatte digest parameter 250 | short_kravatte (bool): Enable disable short kravatte required for other Kravatte modes 251 | """ 252 | if not self.digest_active: 253 | self.collector = self.collector if short_kravatte else self._keccak(self.collector) 254 | self.roll_key = self._kravatte_roll_compress(self.roll_key) 255 | self.digest_active = True 256 | 257 | self.digest = bytearray(b'') 258 | 259 | full_output_size = output_size + (200 - (output_size % 200)) if output_size % 200 else output_size 260 | generate_steps = full_output_size // 200 261 | workload = 1 if (generate_steps // self.workers) == 0 else (generate_steps // self.workers) 262 | 263 | with Pool(processes=self.workers) as kravatte_pool: 264 | for digest_block in kravatte_pool.imap(self._keccak_xor_key, self._generate_squeeze_queue(generate_steps), chunksize=workload): 265 | self.digest.extend(digest_block.tobytes('F')) 266 | 267 | self.digest = self.digest[:output_size] 268 | 269 | def _keccak(self, input_array): 270 | """ 271 | Implementation of Keccak-1600 PRF defined in FIPS 202 272 | 273 | Inputs: 274 | input_array (numpy array): Keccak compatible state array: 200-byte as 5x5 64-bit lanes 275 | Return: 276 | numpy array: Keccak compatible state array: 200-byte as 5x5 64-bit lanes 277 | """ 278 | 279 | state = np.copy(input_array) 280 | 281 | for round_num in range(6): 282 | 283 | # theta_step: 284 | # Exclusive-or each slice-lane by state based permutation value 285 | array_shift = state << 1 | state >> 63 286 | state ^= np.bitwise_xor.reduce(state[self.THETA_REORDER[0], ], 1, keepdims=True) ^ np.bitwise_xor.reduce(array_shift[self.THETA_REORDER[1], ], 1, keepdims=True) 287 | 288 | # rho_step: 289 | # Left Rotate each lane by pre-calculated value 290 | state = state << self.RHO_SHIFTS | state >> np.uint64(64 - self.RHO_SHIFTS) 291 | 292 | # pi_step: 293 | # Shuffle lanes to pre-calculated positions 294 | state = state[self.PI_ROW_REORDER, self.PI_COLUMN_REORDER] 295 | 296 | # chi_step: 297 | # Exclusive-or each individual lane based on and/invert permutation 298 | state ^= ~state[self.CHI_REORDER[0], ] & state[self.CHI_REORDER[1], ] 299 | 300 | # iota_step: 301 | # Exclusive-or first lane of state with round constant 302 | state[0, 0] ^= self.IOTA_CONSTANTS[round_num] 303 | 304 | return state 305 | 306 | def _keccak_xor_key(self, input_array): 307 | """ 308 | Implementation of Keccak-1600 PRF defined in FIPS 202 plus an XOR with the current key state 309 | 310 | Inputs: 311 | input_array (numpy array): Keccak compatible state array: 200-byte as 5x5 64-bit lanes 312 | Return: 313 | numpy array: Keccak compatible state array: 200-byte as 5x5 64-bit lanes 314 | """ 315 | 316 | state = np.copy(input_array) 317 | 318 | for round_num in range(6): 319 | 320 | # theta_step: 321 | # Exclusive-or each slice-lane by state based permutation value 322 | array_shift = state << 1 | state >> 63 323 | state ^= np.bitwise_xor.reduce(state[self.THETA_REORDER[0], ], 1, keepdims=True) ^ np.bitwise_xor.reduce(array_shift[self.THETA_REORDER[1], ], 1, keepdims=True) 324 | 325 | # rho_step: 326 | # Left Rotate each lane by pre-calculated value 327 | state = state << self.RHO_SHIFTS | state >> np.uint64(64 - self.RHO_SHIFTS) 328 | 329 | # pi_step: 330 | # Shuffle lanes to pre-calculated positions 331 | state = state[self.PI_ROW_REORDER, self.PI_COLUMN_REORDER] 332 | 333 | # chi_step: 334 | # Exclusive-or each individual lane based on and/invert permutation 335 | state ^= ~state[self.CHI_REORDER[0], ] & state[self.CHI_REORDER[1], ] 336 | 337 | # iota_step: 338 | # Exclusive-or first lane of state with round constant 339 | state[0, 0] ^= self.IOTA_CONSTANTS[round_num] 340 | 341 | return state ^ self.roll_key 342 | 343 | def scrub(self): 344 | """ 345 | Explicitly zero out both the key and collector array states. Use prior to reinitialization of 346 | key or when finished with object to help avoid leaving secret/interim data in memory. 347 | WARNING: Does not guarantee other copies of these arrays are not present elsewhere in memory 348 | Not applicable in multi-process mode. 349 | 350 | Inputs: 351 | None 352 | Return: 353 | None 354 | """ 355 | # Clear collector array 356 | collector_location = self.collector.ctypes.data 357 | memset(collector_location, 0x00, self.KECCAK_BYTES) 358 | 359 | # Clear Kravatte base key array 360 | key_location = self.kra_key.ctypes.data 361 | memset(key_location, 0x00, self.KECCAK_BYTES) 362 | 363 | # Clear Kravatte rolling key array 364 | key_location = self.roll_key.ctypes.data 365 | memset(key_location, 0x00, self.KECCAK_BYTES) 366 | 367 | def _kravatte_roll_compress(self, input_array): 368 | """ 369 | Kravatte defined roll function for compression side of Farfalle PRF 370 | 371 | Inputs: 372 | input_array (numpy array): Keccak compatible state array: 200-byte as 5x5 64-bit lanes 373 | Return: 374 | numpy array: Keccak compatible state array: 200-byte as 5x5 64-bit lanes 375 | """ 376 | state = input_array[self.COMPRESS_ROW_REORDER, self.COMPRESS_COLUMN_REORDER] 377 | state[4, 4] = ((state[4, 4] << np.uint64(7)) | (state[4, 4] >> np.uint64(57))) ^ \ 378 | (state[0, 4]) ^ \ 379 | (state[0, 4] >> np.uint64(3)) 380 | return state 381 | 382 | def _kravatte_roll_expand(self, input_array): 383 | """ 384 | Kravatte defined roll function for expansion side of Farfalle PRF 385 | 386 | Inputs: 387 | input_array (numpy array): Keccak compatible state array: 200-byte as 5x5 64-bit lanes 388 | Return: 389 | numpy array: Keccak compatible state array: 200-byte as 5x5 64-bit lanes 390 | """ 391 | state = input_array[self.EXPAND_ROW_REORDER, self.EXPAND_COLUMN_REORDER] 392 | state[4, 4] = ((input_array[0, 3] << np.uint64(7)) | (input_array[0, 3] >> np.uint64(57))) ^ \ 393 | ((input_array[1, 3] << np.uint64(18)) | (input_array[1, 3] >> np.uint64(46))) ^ \ 394 | ((input_array[1, 3] >> np.uint64(1)) & input_array[2, 3]) 395 | return state 396 | 397 | @staticmethod 398 | def _pad_10_append(input_bytes: bytes, desired_length: int, append_bits: int=0, append_bit_count: int=0) -> bytes: 399 | """ 400 | Farfalle defined padding function. Limited to byte divisible inputs only 401 | 402 | Inputs: 403 | input_bytes (bytes): Collection of bytes 404 | desired_length (int): Number of bytes to pad input len out to 405 | append_bits (int): one or more bits to be inserted before the padding starts. Allows 406 | "appending" bits as required by several Kravatte modes 407 | append_bit_count (int): number of bits to append 408 | Return: 409 | bytes: input bytes with padding applied 410 | """ 411 | start_len = len(input_bytes) 412 | if start_len == desired_length: 413 | return input_bytes 414 | 415 | head_pad_byte = bytes([(0b01 << append_bit_count) | (((2**append_bit_count) - 1) & append_bits)]) 416 | 417 | pad_len = desired_length - (start_len % desired_length) 418 | padded_bytes = input_bytes + head_pad_byte + (b'\x00' * (pad_len - 1)) 419 | return padded_bytes 420 | 421 | @staticmethod 422 | def compare_bytes(a: bytes, b: bytes) -> bool: 423 | """ 424 | Time Constant Byte Comparison Function 425 | Inputs: 426 | a (bytes): first set of bytes 427 | b (bytes): second set of bytes 428 | Return: 429 | boolean 430 | """ 431 | compare = True 432 | if len(a) != len(b): 433 | return False 434 | for (element_a, element_b) in zip(a, b): 435 | compare = compare and (element_a == element_b) 436 | return compare 437 | 438 | 439 | def mac(key: bytes, message: bytes, output_size: int, workers: int=None, mp_input: bool=True, 440 | mp_output: bool=True) -> bytearray: 441 | """ 442 | Kravatte Message Authentication Code Generation of given length from a message 443 | based on a user provided key 444 | 445 | Args: 446 | key (bytes): User authentication key (0 - 200 bytes) 447 | message (bytes): User message 448 | output_size (int): Size of authenticated digest in bytes 449 | workers (int): parallel processes to use in compression/expansion operations 450 | mp_input (bool): Enable multi-processing for calculations on input data 451 | mp_output (bool): Enable multi-processing for calculations on output data 452 | 453 | Returns: 454 | bytes: message authentication bytes of length output_size 455 | """ 456 | kravatte_mac_gen = Kravatte(key, workers=workers, mp_input=mp_input, mp_output=mp_output) 457 | kravatte_mac_gen.collect_message(message) 458 | kravatte_mac_gen.generate_digest(output_size) 459 | kravatte_mac_gen.scrub() 460 | return kravatte_mac_gen.digest 461 | 462 | 463 | def siv_wrap(key: bytes, message: bytes, metadata: bytes, tag_size: int=32, workers: int=None, 464 | mp_input: bool=True, mp_output: bool=True) -> KravatteTagOutput: 465 | """ 466 | Authenticated Encryption with Associated Data (AEAD) of a provided plaintext using a key and 467 | metadata using the Synthetic Initialization Vector method described in the Farfalle/Kravatte 468 | spec. Generates ciphertext (of equivalent length to the plaintext) and verification tag. Inverse 469 | of siv_unwrap function. 470 | 471 | Args: 472 | key (bytes): Encryption key; 0-200 bytes in length 473 | message (bytes): Plaintext message for encryption 474 | metadata (bytes): Nonce/Seed value for authenticated encryption 475 | tag_size (int, optional): The tag size in bytes. Defaults to 32 bytes as defined in the 476 | Kravatte spec 477 | workers (int): parallel processes to use in compression/expansion operations 478 | mp_input (bool): Enable multi-processing for calculations on input data 479 | mp_output (bool): Enable multi-processing for calculations on output data 480 | 481 | Returns: 482 | tuple (bytes, bytes): Bytes of ciphertext and tag 483 | """ 484 | # Initialize Kravatte 485 | kravatte_siv_wrap = Kravatte(key, workers=workers, mp_input=mp_input, mp_output=mp_output) 486 | 487 | # Generate Tag From Metadata and Plaintext 488 | kravatte_siv_wrap.collect_message(metadata) 489 | kravatte_siv_wrap.collect_message(message) 490 | kravatte_siv_wrap.generate_digest(tag_size) 491 | siv_tag = kravatte_siv_wrap.digest 492 | 493 | # Generate Key Stream 494 | kravatte_siv_wrap.collect_message(metadata) 495 | kravatte_siv_wrap.collect_message(siv_tag) 496 | kravatte_siv_wrap.generate_digest(len(message)) 497 | ciphertext = bytes([p_text ^ key_stream for p_text, key_stream in zip(message, kravatte_siv_wrap.digest)]) 498 | kravatte_siv_wrap.scrub() 499 | return ciphertext, siv_tag 500 | 501 | 502 | def siv_unwrap(key: bytes, ciphertext: bytes, siv_tag: bytes, metadata: bytes, workers: int=None, 503 | mp_input: bool=True, mp_output: bool=True) -> KravatteValidatedOutput: 504 | """ 505 | Decryption of Synthetic Initialization Vector method described in the Farfalle/Kravatte 506 | spec. Given a key, metadata, and validation tag, generates plaintext (of equivalent length to 507 | the ciphertext) and validates message based on included tag, metadata, and key. Inverse of 508 | siv_wrap function. 509 | 510 | Args: 511 | key (bytes): Encryption key; 0-200 bytes in length 512 | ciphertext (bytes): Ciphertext SIV Message 513 | siv_tag (bytes): Authenticating byte string 514 | metadata (bytes): Metadata used to encrypt message and generate tag 515 | workers (int): parallel processes to use in compression/expansion operations 516 | mp_input (bool): Enable multi-processing for calculations on input data 517 | mp_output (bool): Enable multi-processing for calculations on output data 518 | 519 | Returns: 520 | tuple (bytes, boolean): Bytes of plaintext and message validation boolean 521 | """ 522 | 523 | # Initialize Kravatte 524 | kravatte_siv_unwrap = Kravatte(key, workers=workers, mp_input=mp_input, mp_output=mp_output) 525 | 526 | # Re-Generate Key Stream 527 | kravatte_siv_unwrap.collect_message(metadata) 528 | kravatte_siv_unwrap.collect_message(siv_tag) 529 | kravatte_siv_unwrap.generate_digest(len(ciphertext)) 530 | siv_plaintext = bytes([p_text ^ key_stream for p_text, key_stream in zip(ciphertext, kravatte_siv_unwrap.digest)]) 531 | 532 | # Re-Generate Tag From Metadata and Recovered Plaintext 533 | kravatte_siv_unwrap.collect_message(metadata) 534 | kravatte_siv_unwrap.collect_message(siv_plaintext) 535 | kravatte_siv_unwrap.generate_digest(len(siv_tag)) 536 | generated_tag = kravatte_siv_unwrap.digest 537 | 538 | # Check if tag matches provided tag matches reconstituted tag 539 | valid_tag = kravatte_siv_unwrap.compare_bytes(siv_tag, generated_tag) 540 | kravatte_siv_unwrap.scrub() 541 | return siv_plaintext, valid_tag 542 | 543 | 544 | class KravatteSAE(Kravatte): 545 | """ 546 | An authenticated encryption mode designed to track a session consisting of a series of messages 547 | and an initialization nonce. ** DEPRECATED in favor of KravatteSANE ** 548 | """ 549 | TAG_SIZE = 16 550 | OFFSET = TAG_SIZE 551 | 552 | def __init__(self, nonce: bytes, key: bytes=b'', workers: int=None, mp_input: bool=True, 553 | mp_output: bool=True): 554 | """ 555 | Initialize KravatteSAE with user key and nonce 556 | 557 | Args: 558 | nonce (bytes) - random unique value to initialize the session with 559 | key (bytes) - secret key for encrypting session messages 560 | workers (int): parallel processes to use in compression/expansion operations 561 | mp_input (bool): Enable multi-processing for calculations on input data 562 | mp_output (bool): Enable multi-processing for calculations on output data 563 | """ 564 | super(KravatteSAE, self).__init__(key, workers, mp_input, mp_output) 565 | self.initialize_history(nonce) 566 | 567 | def initialize_history(self, nonce: bytes) -> None: 568 | """ 569 | Initialize session history by storing Keccak collector state and current internal key 570 | 571 | Args: 572 | key (bytes): user provided bytes to be padded (if necessary) and computed into Kravatte base key 573 | """ 574 | self.collect_message(nonce) 575 | self.history_collector = np.copy(self.collector) 576 | self.history_key = np.copy(self.roll_key) 577 | self.generate_digest(self.TAG_SIZE) 578 | self.tag = self.digest.copy() 579 | 580 | def wrap(self, plaintext: bytes, metadata: bytes) -> KravatteTagOutput: 581 | """ 582 | Encrypt an arbitrary plaintext message using the included metadata as part of an on-going 583 | session. Creates authentication tag for validation during decryption. 584 | 585 | Args: 586 | plaintext (bytes): user plaintext of arbitrary length 587 | metadata (bytes): associated data to ensure a unique encryption permutation 588 | 589 | Returns: 590 | (bytes, bytes): encrypted cipher text and authentication tag 591 | """ 592 | # Restore Kravatte State to When Latest History was Absorbed 593 | self.collector = np.copy(self.history_collector) 594 | self.roll_key = np.copy(self.history_key) 595 | self.digest = bytearray(b'') 596 | self.digest_active = False 597 | 598 | # Generate/Apply Key Stream 599 | self.generate_digest(len(plaintext) + self.OFFSET) 600 | ciphertext = bytes([p_text ^ key_stream for p_text, key_stream in zip(plaintext, self.digest[self.OFFSET:])]) 601 | 602 | # Update History 603 | if len(metadata) > 0 or len(plaintext) == 0: 604 | self._append_to_history(metadata, 0) 605 | 606 | if len(plaintext) > 0: 607 | self._append_to_history(ciphertext, 1) 608 | 609 | self.history_collector = np.copy(self.collector) 610 | self.history_key = np.copy(self.roll_key) 611 | 612 | # Generate Tag 613 | self.generate_digest(self.TAG_SIZE) 614 | 615 | return ciphertext, self.digest 616 | 617 | def unwrap(self, ciphertext: bytes, metadata: bytes, validation_tag: bytes) -> KravatteValidatedOutput: 618 | """ 619 | Decrypt an arbitrary ciphertext message using the included metadata as part of an on-going 620 | session. Creates authentication tag for validation during decryption. 621 | 622 | Args: 623 | ciphertext (bytes): user ciphertext of arbitrary length 624 | metadata (bytes): associated data from encryption 625 | validation_tag (bytes): collection of bytes that authenticates the decrypted plaintext as 626 | being encrypted with the same secret key 627 | 628 | Returns: 629 | (bytes, bool): decrypted plaintext and boolean indicating in decryption was authenticated against secret key 630 | """ 631 | # Restore Kravatte State to When Latest History was Absorbed 632 | self.collector = np.copy(self.history_collector) 633 | self.roll_key = np.copy(self.history_key) 634 | self.digest = bytearray(b'') 635 | self.digest_active = False 636 | 637 | # Generate/Apply Key Stream 638 | self.generate_digest(len(ciphertext) + self.OFFSET) 639 | plaintext = bytes([p_text ^ key_stream for p_text, key_stream in zip(ciphertext, self.digest[self.OFFSET:])]) 640 | 641 | # Update History 642 | if len(metadata) > 0 or len(ciphertext) == 0: 643 | self._append_to_history(metadata, 0) 644 | 645 | if len(ciphertext) > 0: 646 | self._append_to_history(ciphertext, 1) 647 | 648 | self.history_collector = np.copy(self.collector) 649 | self.history_key = np.copy(self.roll_key) 650 | 651 | # Generate Tag 652 | self.generate_digest(self.TAG_SIZE) 653 | 654 | # Store Generated Tag and Validate 655 | self.tag = self.digest.copy() 656 | valid_tag = self.compare_bytes(self.tag, validation_tag) 657 | 658 | return plaintext, valid_tag 659 | 660 | def _append_to_history(self, message: bytes, pad_bit: int) -> None: 661 | """ 662 | Update history collector state with provided message. 663 | 664 | Args: 665 | message (bytes): arbitrary number of bytes to be padded into Keccak blocks and absorbed into the collector 666 | pad_bit (int): Either 1 or 0 to append to the end of the regular message before padding 667 | """ 668 | if self.digest_active: 669 | self.collector = np.copy(self.history_collector) 670 | self.roll_key = np.copy(self.history_key) 671 | self.digest = bytearray(b'') 672 | self.digest_active = False 673 | 674 | self.roll_key = self._kravatte_roll_compress(self.roll_key) 675 | 676 | # Pad Message with a single bit and then 677 | start_len = len(message) 678 | padded_len = start_len + (self.KECCAK_BYTES - (start_len % self.KECCAK_BYTES)) 679 | padded_bytes = self._pad_10_append(message, padded_len, pad_bit, 1) 680 | absorb_steps = len(padded_bytes) // self.KECCAK_BYTES 681 | 682 | # Absorb into Collector 683 | for msg_block in range(absorb_steps): 684 | m = np.frombuffer(padded_bytes, dtype=np.uint64, count=25, offset=msg_block * self.KECCAK_BYTES).reshape([5, 5], order='F') 685 | m_k = m ^ self.roll_key 686 | self.roll_key = self._kravatte_roll_compress(self.roll_key) 687 | self.collector = self.collector ^ self._keccak(m_k) 688 | 689 | 690 | class KravatteSANE(Kravatte): 691 | """ 692 | An authenticated encryption mode designed to track a session consisting of a series of messages, 693 | metadata, and an initialization nonce. A replacement for KravatteSAE 694 | """ 695 | TAG_SIZE = 16 696 | OFFSET = TAG_SIZE 697 | 698 | """ 699 | An authenticated encryption mode designed to track a session consisting of a series of messages 700 | and an initialization nonce. A replacement for KravatteSAE 701 | """ 702 | def __init__(self, nonce: bytes, key: bytes=b'', workers: int=None, mp_input: bool=True, 703 | mp_output: bool=True): 704 | """ 705 | Initialize KravatteSANE with user key and nonce 706 | 707 | Args: 708 | nonce (bytes) - random unique value to initialize the session with 709 | key (bytes) - secret key for encrypting session messages 710 | workers (int): parallel processes to use in compression/expansion operations 711 | mp_input (bool): Enable multi-processing for calculations on input data 712 | mp_output (bool): Enable multi-processing for calculations on output data 713 | """ 714 | super(KravatteSANE, self).__init__(key, workers, mp_input, mp_output) 715 | self.initialize_history(nonce, False) 716 | 717 | def initialize_history(self, nonce: bytes, reinitialize: bool=True) -> None: 718 | """ 719 | Initialize session history. Session history is stored pre-compressed within the Keccak collector 720 | and current matching internal key state. Kravatte-SANE session history starts with the user 721 | provided nonce. 722 | 723 | Args: 724 | nonce (bytes): user provided bytes to initialize the session history 725 | reinitialize (bool): perform a full reset of the Keccak state when manually restarting the history log 726 | """ 727 | if reinitialize: 728 | self.reset_state() 729 | self.collect_message(nonce) 730 | self.history_collector = np.copy(self.collector) 731 | self.history_key = np.copy(self.roll_key) 732 | self.generate_digest(self.TAG_SIZE) 733 | self.tag = self.digest.copy() 734 | self.e_attr = 0 735 | 736 | def wrap(self, plaintext: bytes, metadata: bytes) -> KravatteTagOutput: 737 | """ 738 | Encrypt an arbitrary plaintext message using the included metadata as part of an on-going 739 | session. Creates authentication tag for validation during decryption. 740 | 741 | Args: 742 | plaintext (bytes): user plaintext of arbitrary length 743 | metadata (bytes): associated data to ensure a unique encryption permutation 744 | 745 | Returns: 746 | (bytes, bytes): encrypted cipher text and authentication tag 747 | """ 748 | # Restore Kravatte State to When Latest History was Absorbed 749 | self.collector = np.copy(self.history_collector) 750 | self.roll_key = np.copy(self.history_key) 751 | self.digest = bytearray(b'') 752 | self.digest_active = False 753 | 754 | # Generate/Apply Key Stream 755 | self.generate_digest(len(plaintext) + self.OFFSET) 756 | ciphertext = bytes([p_text ^ key_stream for p_text, key_stream in zip(plaintext, self.digest[self.OFFSET:])]) 757 | 758 | # Restore/Update History States if required 759 | self._restore_history() 760 | if len(metadata) > 0 or len(plaintext) == 0: 761 | self._append_to_history(metadata, (self.e_attr << 1) | 0, 2) 762 | if len(plaintext) > 0: 763 | self._append_to_history(ciphertext, (self.e_attr << 1) | 1, 2) 764 | 765 | # Increment e toggler attribute 766 | self.e_attr ^= 1 767 | 768 | # Generate Tag 769 | self.generate_digest(self.TAG_SIZE) 770 | 771 | return ciphertext, self.digest 772 | 773 | def unwrap(self, ciphertext: bytes, metadata: bytes, validation_tag: bytes) -> KravatteValidatedOutput: 774 | """ 775 | Decrypt an arbitrary ciphertext message using the included metadata as part of an on-going 776 | session. Validates decryption based on the provided authentication tag. 777 | 778 | Args: 779 | ciphertext (bytes): user ciphertext of arbitrary length 780 | metadata (bytes): associated data from encryption 781 | validation_tag (bytes): collection of bytes that authenticates the decrypted plaintext as 782 | being encrypted with the same secret key 783 | 784 | Returns: 785 | (bytes, bool): decrypted plaintext and boolean indicating in decryption was authenticated against secret key 786 | """ 787 | # Restore Kravatte State to When Latest History was Absorbed 788 | self.collector = np.copy(self.history_collector) 789 | self.roll_key = np.copy(self.history_key) 790 | self.digest = bytearray(b'') 791 | self.digest_active = False 792 | 793 | # Generate/Apply Key Stream 794 | self.generate_digest(len(ciphertext) + self.OFFSET) 795 | plaintext = bytes([p_text ^ key_stream for p_text, key_stream in zip(ciphertext, self.digest[self.OFFSET:])]) 796 | 797 | # Restore/Update History States if required 798 | self._restore_history() 799 | if len(metadata) > 0 or len(ciphertext) == 0: 800 | self._append_to_history(metadata, (self.e_attr << 1) | 0, 2) 801 | if len(ciphertext) > 0: 802 | self._append_to_history(ciphertext, (self.e_attr << 1) | 1, 2) 803 | 804 | # Increment e toggler attribute 805 | self.e_attr ^= 1 806 | 807 | # Generate Tag 808 | self.generate_digest(self.TAG_SIZE) 809 | 810 | # Store Generated Tag and Validate 811 | self.tag = self.digest.copy() 812 | valid_tag = self.compare_bytes(self.tag, validation_tag) 813 | 814 | return plaintext, valid_tag 815 | 816 | def _append_to_history(self, message: bytes, pad_bits: int, pad_size: int) -> None: 817 | """ 818 | Update history collector state with provided message. 819 | 820 | Args: 821 | message (bytes): arbitrary number of bytes to be padded into Keccak blocks and absorbed into the collector 822 | pad_bits (int): Up to 6 additional bits added to the end of the regular message before padding 823 | pad_size (int): Number of bits to append 824 | """ 825 | self.collect_message(message, pad_bits, pad_size) 826 | self.history_collector = np.copy(self.collector) 827 | self.history_key = np.copy(self.roll_key) 828 | 829 | def _restore_history(self) -> None: 830 | """ 831 | Restore the internal kravatte state to the previously saved history state 832 | 833 | Args: 834 | None 835 | """ 836 | self.collector = np.copy(self.history_collector) 837 | self.roll_key = np.copy(self.history_key) 838 | self.digest = bytearray(b'') 839 | self.digest_active = False 840 | 841 | 842 | class KravatteSANSE(Kravatte): 843 | """ 844 | A nonce-less authenticated encryption mode designed to track a session consisting of a series of 845 | messages and metadata. A replacement for Kravatte-SIV 846 | """ 847 | TAG_SIZE = 32 848 | 849 | def __init__(self, key: bytes=b'', workers: int=None, mp_input: bool=True, mp_output: bool=True): 850 | """ 851 | Initialize KravatteSANSE with user key 852 | 853 | Args: 854 | key (bytes) - secret key for encrypting/decrypting session messages 855 | workers (int): parallel processes to use in compression/expansion operations 856 | mp_input (bool): Enable multi-processing for calculations on input data 857 | mp_output (bool): Enable multi-processing for calculations on output data 858 | """ 859 | super(KravatteSANSE, self).__init__(key, workers, mp_input, mp_output) 860 | self.initialize_history(False) 861 | 862 | def initialize_history(self, reinitialize: bool=True) -> None: 863 | """ 864 | Initialize session history. Session history is stored pre-compressed within the Keccak collector 865 | and current matching internal key state. Kravatte-SANSE session history starts empty. 866 | 867 | Args: 868 | reinitialize (bool): perform a full reset of the Keccak state when manually restarting the history log 869 | """ 870 | if reinitialize: 871 | self.reset_state() 872 | self.history_collector = np.copy(self.collector) 873 | self.history_key = np.copy(self.roll_key) 874 | self.history_collector_state = np.copy(self.new_collector) 875 | self.e_attr = 0 876 | 877 | def wrap(self, plaintext: bytes, metadata: bytes) -> KravatteTagOutput: 878 | """ 879 | Encrypt an arbitrary plaintext message using the included metadata as part of an on-going 880 | session. Creates authentication tag for validation during decryption. 881 | 882 | Args: 883 | plaintext (bytes): user plaintext of arbitrary length 884 | metadata (bytes): associated data to ensure a unique encryption permutation 885 | 886 | Returns: 887 | (bytes, bytes): encrypted cipher text and authentication tag 888 | """ 889 | # Restore Kravatte State to When Latest History was Absorbed 890 | self._restore_history() 891 | 892 | # Update History 893 | if len(metadata) > 0 or len(plaintext) == 0: 894 | self._append_to_history(metadata, (self.e_attr << 1) | 0, 2) 895 | 896 | if len(plaintext) > 0: 897 | # Generate Tag 898 | self.collect_message(plaintext, (self.e_attr << 2) | 0b10, 3) 899 | self.generate_digest(self.TAG_SIZE) 900 | tag = self.digest 901 | 902 | # Reset History State and Generate/Apply Key Stream 903 | self._restore_history() 904 | self.collect_message(tag, ((self.e_attr << 2) | 0b11), 3) 905 | self.generate_digest(len(plaintext)) 906 | ciphertext = bytes([p_text ^ key_stream for p_text, key_stream in zip(plaintext, self.digest)]) 907 | # Reset History State and Update it with Plaintext and Padding 908 | self._restore_history() 909 | self._append_to_history(plaintext, (self.e_attr << 2) | 0b10, 3) 910 | else: 911 | ciphertext = b'' 912 | self.generate_digest(self.TAG_SIZE) 913 | tag = self.digest 914 | 915 | self.e_attr ^= 1 916 | return ciphertext, tag 917 | 918 | def unwrap(self, ciphertext: bytes, metadata: bytes, validation_tag: bytes) -> KravatteValidatedOutput: 919 | """ 920 | Decrypt an arbitrary ciphertext message using the included metadata as part of an on-going 921 | session. Validates decryption based on the provided authentication tag. 922 | 923 | Args: 924 | ciphertext (bytes): user ciphertext of arbitrary length 925 | metadata (bytes): associated data from encryption 926 | validation_tag (bytes): collection of bytes that authenticates the decrypted plaintext as 927 | being encrypted with the same secret key 928 | 929 | Returns: 930 | (bytes, bool): decrypted plaintext and boolean indicating in decryption was authenticated against secret key 931 | """ 932 | # Restore Kravatte State to When Latest History was Absorbed 933 | self._restore_history() 934 | 935 | if len(metadata) > 0 or len(ciphertext) == 0: 936 | self._append_to_history(metadata, (self.e_attr << 1) | 0, 2) 937 | 938 | if len(ciphertext) > 0: 939 | self.collect_message(validation_tag, ((self.e_attr << 2) | 0b11), 3) 940 | self.generate_digest(len(ciphertext)) 941 | plaintext = bytes([p_text ^ key_stream for p_text, key_stream in zip(ciphertext, self.digest)]) 942 | 943 | # Update History 944 | self._restore_history() 945 | self._append_to_history(plaintext, (self.e_attr << 2) | 0b10, 3) 946 | else: 947 | plaintext = b'' 948 | 949 | # Generate Tag 950 | self.generate_digest(self.TAG_SIZE) 951 | self.e_attr ^= 1 952 | 953 | # Store Generated Tag and Validate 954 | self.tag = self.digest.copy() 955 | valid_tag = self.compare_bytes(self.tag, validation_tag) 956 | 957 | return plaintext, valid_tag 958 | 959 | def _append_to_history(self, message: bytes, pad_bits: int, pad_size: int) -> None: 960 | """ 961 | Update history collector state with provided message. Save the new history state. 962 | 963 | Args: 964 | message (bytes): arbitrary number of bytes to be padded into Keccak blocks and absorbed into the collector 965 | pad_bits (int): Up to 6 additional bits added to the end of the regular message before padding 966 | pad_size (int): Number of bits to append 967 | """ 968 | self.collect_message(message, pad_bits, pad_size) 969 | self.history_collector = np.copy(self.collector) 970 | self.history_key = np.copy(self.roll_key) 971 | self.history_collector_state = np.copy(self.new_collector) 972 | 973 | def _restore_history(self) -> None: 974 | """ 975 | Restore the internal kravatte state to the previously saved history state 976 | 977 | Args: 978 | None 979 | """ 980 | self.collector = np.copy(self.history_collector) 981 | self.roll_key = np.copy(self.history_key) 982 | self.new_collector = np.copy(self.history_collector_state) 983 | self.digest = bytearray(b'') 984 | self.digest_active = False 985 | 986 | 987 | class KravatteWBC(Kravatte): 988 | """ Configurable Wide Block Cipher encryption mode with customization tweak """ 989 | SPLIT_THRESHOLD = 398 990 | 991 | def __init__(self, block_cipher_size: int, tweak: bytes=b'', key: bytes=b'', workers: int=None, 992 | mp_input: bool=True, mp_output: bool=True): 993 | """ 994 | Initialize KravatteWBC object 995 | 996 | Inputs: 997 | block_cipher_size (int) - size of block cipher in bytes 998 | tweak (bytes) - arbitrary value to customize cipher output 999 | key (bytes) - secret key for encrypting message blocks 1000 | workers (int): parallel processes to use in compression/expansion operations 1001 | mp_input (bool): Enable multi-processing for calculations on input data 1002 | mp_output (bool): Enable multi-processing for calculations on output data 1003 | """ 1004 | super(KravatteWBC, self).__init__(key, workers, mp_input, mp_output) 1005 | self.split_bytes(block_cipher_size) 1006 | self.tweak = tweak 1007 | 1008 | def split_bytes(self, message_size_bytes: int) -> None: 1009 | """ 1010 | Calculates the size (in bytes) of the "left" and "right" components of the block encryption 1011 | decryption process. Based on algorithm given in Farfalle spec. 1012 | 1013 | Input 1014 | message_size_bytes (int): user defined block size for this instance of KravatteWBC 1015 | """ 1016 | if message_size_bytes <= self.SPLIT_THRESHOLD: 1017 | nL = ceil(message_size_bytes / 2) 1018 | else: 1019 | q = floor(((message_size_bytes + 1) / self.KECCAK_BYTES)) + 1 1020 | x = floor(log2(q - 1)) 1021 | nL = ((q - (2**x)) * self.KECCAK_BYTES) - 1 1022 | self.size_L = nL 1023 | self.size_R = message_size_bytes - nL 1024 | 1025 | def encrypt(self, message: bytes) -> bytes: 1026 | """ 1027 | Encrypt a user message using KravatteWBC mode 1028 | Inputs: 1029 | message (bytes): plaintext message to encrypt. Length should be <= the block cipher size 1030 | defined in the KravatteWBC object 1031 | Returns: 1032 | bytes: encrypted block same length as message 1033 | """ 1034 | L = message[0:self.size_L] 1035 | R = message[self.size_L:] 1036 | 1037 | # R0 ← R0 + HK(L||0), with R0 the first min(b, |R|) bits of R 1038 | self.collect_message(L, append_bits=0b0, append_bit_count=1) 1039 | self.generate_digest(min(self.KECCAK_BYTES, self.size_R), short_kravatte=True) 1040 | extended_digest = self.digest + ((self.size_R - len(self.digest)) * b'\x00') 1041 | R = bytes([p_text ^ key_stream for p_text, key_stream in zip(R, extended_digest)]) 1042 | 1043 | # L ← L + GK (R||1 ◦ W) 1044 | self.collect_message(self.tweak) 1045 | self.collect_message(R, append_bits=0b1, append_bit_count=1) 1046 | self.generate_digest(self.size_L) 1047 | L = bytes([p_text ^ key_stream for p_text, key_stream in zip(L, self.digest)]) 1048 | 1049 | # R ← R + GK (L||0 ◦ W) 1050 | self.collect_message(self.tweak) 1051 | self.collect_message(L, append_bits=0b0, append_bit_count=1) 1052 | self.generate_digest(self.size_R) 1053 | R = bytes([p_text ^ key_stream for p_text, key_stream in zip(R, self.digest)]) 1054 | 1055 | # L0 ← L0 + HK(R||1), with L0 the first min(b, |L|) bits of L 1056 | self.collect_message(R, append_bits=0b1, append_bit_count=1) 1057 | self.generate_digest(min(self.KECCAK_BYTES, self.size_L), short_kravatte=True) 1058 | extended_digest = self.digest + ((self.size_L - len(self.digest)) * b'\x00') 1059 | L = bytes([p_text ^ key_stream for p_text, key_stream in zip(L, extended_digest)]) 1060 | 1061 | # C ← the concatenation of L and R 1062 | return L + R 1063 | 1064 | def decrypt(self, ciphertext: bytes) -> bytes: 1065 | """ 1066 | Decrypt a user message using KravatteWBC mode 1067 | Args: 1068 | message (bytes): ciphertext message to decrypt. 1069 | Returns: 1070 | bytes: decrypted block same length as ciphertext 1071 | """ 1072 | L = ciphertext[0:self.size_L] 1073 | R = ciphertext[self.size_L:] 1074 | 1075 | # L0 ← L0 + HK(R||1), with L0 the first min(b, |L|) bits of L 1076 | self.collect_message(R, append_bits=0b1, append_bit_count=1) 1077 | self.generate_digest(min(self.KECCAK_BYTES, self.size_L), short_kravatte=True) 1078 | extended_digest = self.digest + ((self.size_L - len(self.digest)) * b'\x00') 1079 | L = bytes([c_text ^ key_stream for c_text, key_stream in zip(L, extended_digest)]) 1080 | 1081 | # R ← R + GK (L||0 ◦ W) 1082 | self.collect_message(self.tweak) 1083 | self.collect_message(L, append_bits=0b0, append_bit_count=1) 1084 | self.generate_digest(self.size_R) 1085 | R = bytes([c_text ^ key_stream for c_text, key_stream in zip(R, self.digest)]) 1086 | 1087 | # L ← L + GK (R||1 ◦ W) 1088 | self.collect_message(self.tweak) 1089 | self.collect_message(R, append_bits=0b1, append_bit_count=1) 1090 | self.generate_digest(self.size_L) 1091 | L = bytes([c_text ^ key_stream for c_text, key_stream in zip(L, self.digest)]) 1092 | 1093 | # R0 ← R0 + HK(L||0), with R0 the first min(b, |R|) bits of R 1094 | self.collect_message(L, append_bits=0b0, append_bit_count=1) 1095 | self.generate_digest(min(self.KECCAK_BYTES, self.size_R), short_kravatte=True) 1096 | extended_digest = self.digest + ((self.size_R - len(self.digest)) * b'\x00') 1097 | R = bytes([c_text ^ key_stream for c_text, key_stream in zip(R, extended_digest)]) 1098 | 1099 | # P ← the concatenation of L and R 1100 | return L + R 1101 | 1102 | 1103 | class KravatteWBC_AE(KravatteWBC): 1104 | """ Authentication with associated metadata version Kravatte Wide Block Cipher encryption mode """ 1105 | WBC_AE_TAG_LEN = 16 1106 | 1107 | def __init__(self, block_cipher_size: int, key: bytes=b'', workers: int=None, 1108 | mp_input: bool=True, mp_output: bool=True): 1109 | """ 1110 | Initialize KravatteWBC_AE object 1111 | 1112 | Args: 1113 | block_cipher_size (int) - size of block cipher in bytes 1114 | key (bytes) - secret key for encrypting message blocks 1115 | workers (int): parallel processes to use in compression/expansion operations 1116 | mp_input (bool): Enable multi-processing for calculations on input data 1117 | mp_output (bool): Enable multi-processing for calculations on output data 1118 | """ 1119 | super(KravatteWBC_AE, self).__init__(block_cipher_size + self.WBC_AE_TAG_LEN, b'', key=key, 1120 | workers=workers, mp_input=mp_input, 1121 | mp_output=mp_output) 1122 | 1123 | def wrap(self, message: bytes, metadata: bytes) -> bytes: 1124 | """ 1125 | Encrypt a user message and generate included authenticated data. Requires metadata input 1126 | in lieu of customization tweak. 1127 | 1128 | Args: 1129 | message (bytes): User message same length as configured object block size 1130 | metadata (bytes): associated metadata to ensure unique output 1131 | 1132 | Returns: 1133 | bytes: authenticated encrypted block 1134 | """ 1135 | 1136 | self.tweak = metadata # metadata treated as tweak 1137 | padded_message = message + (self.WBC_AE_TAG_LEN * b'\x00') 1138 | return self.encrypt(padded_message) 1139 | 1140 | def unwrap(self, ciphertext: bytes, metadata: bytes) -> KravatteValidatedOutput: 1141 | """ 1142 | Decrypt a ciphertext block and validate included authenticated data. Requires metadata input 1143 | in lieu of customization tweak. 1144 | 1145 | Args: 1146 | message (bytes): ciphertext same length as configured object block size 1147 | metadata (bytes): associated metadata to ensure unique output 1148 | 1149 | Returns: 1150 | (bytes, bool): plaintext byes and decryption valid flag 1151 | """ 1152 | L = ciphertext[0:self.size_L] 1153 | R = ciphertext[self.size_L:] 1154 | self.tweak = metadata 1155 | 1156 | # L0 ← L0 + HK(R||1), with L0 the first min(b, |L|) bits of L 1157 | self.collect_message(R, append_bits=0b1, append_bit_count=1) 1158 | self.generate_digest(min(self.KECCAK_BYTES, self.size_L), short_kravatte=True) 1159 | extended_digest = self.digest + ((self.size_L - len(self.digest)) * b'\x00') 1160 | L = bytes([c_text ^ key_stream for c_text, key_stream in zip(L, extended_digest)]) 1161 | 1162 | # R ← R + GK (L||0 ◦ A) 1163 | self.collect_message(self.tweak) 1164 | self.collect_message(L, append_bits=0b0, append_bit_count=1) 1165 | self.generate_digest(self.size_R) 1166 | R = bytes([c_text ^ key_stream for c_text, key_stream in zip(R, self.digest)]) 1167 | 1168 | # |R| ≥ b+t 1169 | if self.size_R >= self.KECCAK_BYTES + self.WBC_AE_TAG_LEN: 1170 | # if the last t bytes of R ̸= 0t then return error! 1171 | valid_plaintext = True if R[-self.WBC_AE_TAG_LEN:] == (self.WBC_AE_TAG_LEN * b'\x00') else False 1172 | 1173 | # L ← L + GK (R||1 ◦ A) 1174 | self.collect_message(self.tweak) 1175 | self.collect_message(R, append_bits=0b1, append_bit_count=1) 1176 | self.generate_digest(self.size_L) 1177 | L = bytes([c_text ^ key_stream for c_text, key_stream in zip(L, self.digest)]) 1178 | 1179 | # R0 ← R0 + HK(L||0), with R0 the first b bytes of R 1180 | self.collect_message(L, append_bits=0b0, append_bit_count=1) 1181 | self.generate_digest(self.KECCAK_BYTES, short_kravatte=True) 1182 | extended_digest = self.digest + ((self.size_R - len(self.digest)) * b'\x00') 1183 | R = bytes([c_text ^ key_stream for c_text, key_stream in zip(R, extended_digest)]) 1184 | 1185 | else: 1186 | # L ← L + GK (R||1 ◦ A) 1187 | self.collect_message(self.tweak) 1188 | self.collect_message(R, append_bits=0b1, append_bit_count=1) 1189 | self.generate_digest(self.size_L) 1190 | L = bytes([c_text ^ key_stream for c_text, key_stream in zip(L, self.digest)]) 1191 | 1192 | # R0 ← R0 + HK(L||0), with R0 the first min(b, |R|) bytes of R 1193 | self.collect_message(L, append_bits=0b0, append_bit_count=1) 1194 | self.generate_digest(min(self.KECCAK_BYTES, self.size_R), short_kravatte=True) 1195 | extended_digest = self.digest + ((self.size_R - len(self.digest)) * b'\x00') 1196 | R = bytes([c_text ^ key_stream for c_text, key_stream in zip(R, extended_digest)]) 1197 | 1198 | # if the last t bytes of L||R ̸= 0t then return error! 1199 | valid_plaintext = True if (L + R)[-self.WBC_AE_TAG_LEN:] == (self.WBC_AE_TAG_LEN * b'\x00') else False 1200 | 1201 | # P′ ← L||R 1202 | return (L + R)[:-self.WBC_AE_TAG_LEN], valid_plaintext 1203 | 1204 | 1205 | class KravatteOracle(Kravatte): 1206 | """Pseudo-random byte stream generator. Accepts an authentication key and arbitrary sized seed 1207 | input. Once initialized, the random method can return an arbitrary amount of random output bytes 1208 | for each call. Generator collector state can be reinitialized at anytime with the seed_generator 1209 | method 1210 | """ 1211 | 1212 | def __init__(self, seed: bytes=b'', key: bytes=b'', workers: int=None, mp_input: bool=True, 1213 | mp_output: bool=True): 1214 | """ 1215 | Initialize KravatteOracle with user key and seed. 1216 | 1217 | Inputs: 1218 | seed (bytes) - random unique value to initialize the oracle object with 1219 | key (bytes) - secret key for authenticating generator 1220 | workers (int): parallel processes to use in compression/expansion operations 1221 | mp_input (bool): Enable multi-processing for calculations on input data 1222 | mp_output (bool): Enable multi-processing for calculations on output data 1223 | """ 1224 | super(KravatteOracle, self).__init__(key, workers, mp_input, mp_input) 1225 | self.seed_generator(seed) 1226 | 1227 | def seed_generator(self, seed: bytes): 1228 | """ 1229 | Re-seed Kravatte collector state with new seed data. 1230 | 1231 | Input: 1232 | seed (bytes): Collection of seed bytes that are absorbed as single message 1233 | """ 1234 | self.collect_message(seed) 1235 | 1236 | def random(self, output_size: int) -> bytearray: 1237 | """ 1238 | Generates a stream of pseudo-random bytes from the current state of the Kravatte collector 1239 | state 1240 | 1241 | Input: 1242 | output_size (bytes): Number of bytes to return 1243 | 1244 | Returns: 1245 | bytearray: Pseudo-random Kravatte squeezed collector output 1246 | """ 1247 | self.generate_digest(output_size) 1248 | return self.digest 1249 | 1250 | 1251 | if __name__ == "__main__": 1252 | from time import perf_counter 1253 | import hashlib 1254 | from binascii import hexlify 1255 | import os 1256 | my_key = b'\xFF' * 32 1257 | my_message = bytes([x % 256 for x in range(4 * 1024 * 1024)]) 1258 | 1259 | print("Normal Message MAC Generation") 1260 | start = perf_counter() 1261 | my_kra = mac(my_key, my_message, 1024 * 1024 * 4) 1262 | stop = perf_counter() 1263 | print("Process Time:", stop - start) 1264 | a1 = hashlib.md5() 1265 | a1.update(my_kra) 1266 | print(hexlify(a1.digest())) 1267 | 1268 | print("%d Process/Core Message MAC Generation" % os.cpu_count()) 1269 | start = perf_counter() 1270 | my_kra = mac(my_key, my_message, 1024 * 1024 * 4, workers=os.cpu_count()) 1271 | stop = perf_counter() 1272 | print("Process Time:", stop - start) 1273 | a2 = hashlib.md5() 1274 | a2.update(my_kra) 1275 | print(hexlify(a2.digest())) 1276 | assert a1.digest() == a2.digest() 1277 | --------------------------------------------------------------------------------