Skip to main content

Kravatte Encryption Authentication Tools

Project description

An implementation, in Python3, of the Kravatte pseudo-random function and associated modes based on the Farfalle PRF system. At its core, Kravatte accepts a user defined secret key and a sequence of input bytes to generate pseudo-random output of arbitrary size.

Kravatte makes use of the Keccak permutation, most notably used in NIST’s FIPS 202 SHA-3 algorithm. Because the underlying structure of Keccak function works on a three-dimensional state of 1600 bits, it maps well to a 5x5 matrix of 64-bit unsigned integers. As such, the NumPy computational library is a natural fit to quickly manipulate such a structure and thus is a hard requirement.

This implementation reflects the updated, more secure Kravatte Achouffe released in late 2017. The older “Kravatte 6644” logic is available within this repo as well.

Installation

Kravatte can be easily installed from pypi via pip:

$ pip install kravatte

If pip is unavailable, this repo can be cloned and setup can be done manually:

$ python setup.py install

Kravatte Object

The basic Kravatte object operates on two Keecak-1600 state matrices; the collector state and the key state. Instantiating a Kravatte object initializes the key state with provided user key and sets the collector state to zeros.

In [1]: from kravatte import Kravatte
In [2]: my_krav = Kravatte(b'1234567890')

The newly initialized Kravatte object is now ready to accept input strings of bytes for absorption into the collector state via the collect_message method. Repeated calls to collect_message are equivalent to B ◦ A sequences as described in the the Farfalle spec:

In [3]: input_a = b'The quick brown fox jumps over the lazy dog'
In [4]: my_krav.collect_message(input_a)
In [5]: input_b = b'3533392d36302d35313235'
In [6]: my_krav.collect_message(input_b)

Once absorbing message strings is complete, the Kravatte object can produce an arbitrary number of pseudo-random output bytes via the generate_digest method. Those bytes are then available in the digest attribute:

In [7]: output_bytes = 64
In [8]: my_krav.generate_digest(output_bytes)
In [9]: from binascii import hexlify
In [10]: hexlify(my_krav.digest)
Out[10]: b'8a0fc89899e058dedd368b60111bf4958f4f24216bbac76936471e6f7c3958b881c38c8e829ff07bf137701917b3e49ab392e93f3b2abfc714f90c0ca023124d'

The absorb/output sequence can be restarted with another call to collect_message. This clears the collector state and resets the key state to its initialized value. Alternatively, the user may change to a new secret key with the update_key method to reinitialize the key state used at the start of message absorption.

MAC

The most basic mode of Kravatte is an authenticated pseudo-random function (PRF). Kravatte can absorb an arbitrary sized user message and key, and output an arbitrary collection of pseudo-random bytes that can act as a message authentication code. The mac does this in a single step:

In [1] from kravatte import mac
In [2] from binascii import hexlify
In [3] message = b'Attack at Dawn!'
In [4] key = b'something_secret'
In [5] mac_size = 64
In [6] g = mac(key, message, mac_size)
In [7] hexlify(g)
Out[7] b'24f61fc5fd38fef7f3d799ed72b24578c4479e1c035c70d8bc55ce23d74124255d5e8a0c5dd33aa36d5289f1e4e995a19be804d97bb338fa875e01e3c2d2dd51'

Kravatte-SIV

Kravatte-SIV mode is a method of authenticated encryption with associated metadata (AEAD) that allows for encrypting a provided plaintext with a secret shared key and an arbitrary metadata value. Encryption generates an equal length ciphertext and fixed length tag that can be used to validate the plaintext at decryption. Metadata values can be shared for different key/message combinations with understanding that the more a value is used, the greater the chance of a tag collision.

Encrypt

In [1] from kravatte import siv_wrap, siv_unwrap
In [2] from binascii import hexlify
In [3] from datetime import datetime
In [4] message = b'Attack at Dawn!'
In [5] key = b'something_secret'
In [6] metadata = str(datetime.now()).encode()
In [7] ciphertext, tag = siv_wrap(key, message, metadata)
In [8] hexlify(ciphertext)
Out[8] b'79f7bd89a7cb7af1892ea51c531f4b'
In [9] hexlify(tag)
Out[9] b'37c7e11f0c9c744e7c113590fdfba7737cb38b629ef6901df22d6994340e89eas'

Decrypt

In [10] plaintext, tag_valid = siv_unwrap(key, ciphertext, tag, metadata)
In [11] plaintext
Out[11] b'Attack at Dawn!'
In [12] tag_valid
Out[12] True

Kravatte-SAE

Kravatte-SAE mode is a session based method of AEAD. Given a random nonce and secret key, this mode encrypts a sequence of plaintext messages and/or metadata into equal size ciphertexts and a validation tag. The sequence of plaintext/metadata is tracked as a history that builds a chain of authentication from message to message and requires all generated ciphertexts to be processed to fully decrypt and verify.

A separate KravatteSAE class is provided that adds the history tracking for each encryption operation done via the sae_wrap method.

Encrypt

In [1]: from kravatte import KravatteSAE
In [2]: from datetime import datetime
In [3]: from binascii import hexlify
In [4]: message_1 = b'Directions to my house:'
In [5]: metadata_1 = str(datetime.now()).encode()
In [6]: message_2 = b'Turn right on main street'
In [7]: metadata_2 = str(datetime.now()).encode()
In [8]: message_3 = b'Continue straight for 3500 miles'
In [9]: metadata_3 = str(datetime.now()).encode()
In [10]: message_4 = b'You have arrived at your destination'
In [11]: metadata_4 = str(datetime.now()).encode()
In [12]: nonce = b'a well chosen random number'
In [13]: key = b'an even better random number'
In [14]: KravSAE_wrapper = KravatteSAE(nonce, key)
In [15]: ciphertext_1, tag_1 = KravSAE_wrapper.sae_wrap(message_1, metadata_1)
In [16]: hexlify(ciphertext_1)
Out[16]: b'7b8932a1c3673fcfe752631ef5b867843951514335de61'
In [17]: hexlify(tag_1)
Out[17]: b'3384885ca293925cc65a03fa10790420'
In [18]: ciphertext_2, tag_2 = KravSAE_wrapper.sae_wrap(message_2, metadata_2)
In [19]: hexlify(ciphertext_2)
Out[19]: b'ab48882d4339c6def9d5d06f608db5318a87a417566c0b20bd'
In [20]: hexlify(tag_2)
Out[20]: b'347f5a152dcc9ccc3c19fa936067c3d2'
In [21]: ciphertext_3, tag_3 = KravSAE_wrapper.sae_wrap(message_3, metadata_3)
In [22]: hexlify(ciphertext_3)
Out[22]: b'bc461f40db74705c10b1400b6a9967dd7164cbf774c196d5b649faf2bd792339'
In [23]: hexlify(tag_3)
Out[23]: b'6ba2faee4d2aa5654a054222a049d926'
In [24]: ciphertext_4, tag_4 = KravSAE_wrapper.sae_wrap(message_4, metadata_4)
In [25]: hexlify(ciphertext_4)
Out[25]: b'1f451f51d9882f9f7674c37dace4036efd9efe39d6b58ccdf6b012ef988e4e1f2617479f'
In [26]: hexlify(tag_4)
Out[26]: b'5f3511f140b4ea36412c0e4b22d1c218'

For decryption and validation, the sae_unwrap method accepts the ciphertext, original metadata, and validation tag to not only decrypt the plaintext, but return a boolean if the decrypted plaintext is valid within the chain of messages.

Decrypt

In [27]: KravSAE_unwrapper = KravatteSAE(nonce, key)
In [28]: plaintext_1, check_tag_1 = KravSAE_unwrapper.sae_unwrap(ciphertext_1, metadata_1, tag_1)
In [29]: plaintext_1
Out[29]: b'Directions to my house:'
In [30]: check_tag_1
Out[30]: True
In [31]: plaintext_2, check_tag_2 = KravSAE_unwrapper.sae_unwrap(ciphertext_2, metadata_2, tag_2)
In [32]: plaintext_2
Out[32]: b'Turn right on main street'
In [33]: check_tag_2
Out[33]: True
In [34]: plaintext_3, check_tag_3 = KravSAE_unwrapper.sae_unwrap(ciphertext_3, metadata_3, tag_3)
In [35]: plaintext_3
Out[35]: b'Continue straight for 3500 miles'
In [36]: check_tag_3
Out[36]: True
In [37]: plaintext_4, check_tag_4 = KravSAE_unwrapper.sae_unwrap(ciphertext_4, metadata_4, tag_4)
In [38]: plaintext_4
Out[38]: b'You have arrived at your destination'
In [39]: check_tag_4
Out[39]: True

KravatteWBC

Kravatte Wide Block Cipher mode is a symmetric block cipher mode where the user can specify the size of the block, an arbitrary tweak value input, and arbitrary secret key. The KravatteWBC object, once initialized, can encrypt/decrypt messages of the given block size (or smaller). KravatteWBC is splits messages into left and right components and uses a 4-stage Feistal sequence to encrypt/decrypt.

Encrypt and Decrypt

In [1]: from kravatte import KravatteWBC
In [2]: block_size = 64
In [3]: my_tweak = b'tweak can be anything'
In [4]: my_key = b'\x00' * 24
In [5]: my_wbc = KravatteWBC(block_size, my_tweak, my_key)
In [6]: c_block = my_wbc.encrypt(b'This is some random 64-byte text string to use in this example!!')
In [7]: from binascii import hexlify
In [8]: hexlify(c_block)
Out[8]: b'2368fae1271e5c784537df331586d5d4daeeb34a6fe4ebea03cc1df7f9c0d79fcc709a9ff2199514f431da685e27658dbf6c5afed11ce5c8172f7615c19db1b9'
In [9]: my_wbc.decrypt(c_block)
Out[9]: b'This is some random 64-byte text string to use in this example!!'

KravatteWBC-AE

KravatteWBC-AE is a variant of KravatteWBC that extends the desired block size by 16 bytes and embeds authentication data. The tweak is replaced with arbitrary associated metadata. When the block is decrypted it is also validated as being encrypted with same secret key.

Encrypt and Decrypt

In [1]: from datetime import datetime
In [2]: from binascii import hexlify
In [3]: my_key = b"Doesn't look like anything to me"
In [4]: metadata = str(datetime.now()).encode()
In [5]: message = b'These violent delights have violent ends'
In [6]: len(message)
Out[6]: 40
In [7]: my_WBC_AE = KravatteWBC_AE(40, my_key)
In [8]: ctext_ae = my_WBC_AE.wrap(message, metadata)
In [9]: len(ctext_ae)
Out[9]: 56
In [10]: hexlify(ctext_ae)
Out[10]: b'388623f7a7d3c044cda574063b4ff16edbdfc95cb449f335a1c5ad5ed37897aa2470f3575825a55df04cc1dab34b4feb03aa6d35f6190d62'
In [11]: plaintext, validated = my_WBC_AE.unwrap(ctext_ae, metadata)
In [12]: plaintext
Out[12]: b'These violent delights have violent ends'
In [13]: validated
Out[13]: True

KravatteOracle

KravatteOracle is simple pseduo-random number generator built from the Kravatte PRF primitive. Initialized with an authentication key, the KravatteOracle object absorbs an arbitrarily sized seed value into the collector state. From there, streams of random bytes can be generated on demand via the random method. The generator can be re-seeded at any point with the seed_generator method.

Generate Random Numbers

In [1]: my_psrng = KravatteOracle(my_seed, my_key)
In [2]: my_key = b'1234'
In [3]: my_seed = b'watermelon'
In [4]: my_psrng = KravatteOracle(my_seed, my_key)
In [5]: random_bytes = my_psrng.random(24)
In [6]: hexlify(random_bytes)
Out[6]: b'14a42ab5756efe61eae73893570b6736b392d0031a87e36d'
In [7]: random_bytes = my_psrng.random(42)
In [8]: hexlify(random_bytes)
Out[8]: b'77d6308e18d57fb124e75602ced2e863e7de34c69ea57bec47efae84e85d0075c3ebbf7e535ec0fb096f'

Re-seed Generator

In [9]: my_psrng.seed_generator(b'apple')
In [10]: random_bytes = my_psrng.random(18)
In [11]: hexlify(random_bytes)
Out[11]: b'3e108c3f627f561943893b6a3184e5b76472'

Multi-Process Performance Mode

The Farfalle PRF allows for significant parallelism in both the compression and expansion phases when consuming or generating large numbers of blocks. We can exploit that fact for increased performance via Python’s multiprocessing module. This allows us to spawn any number of identical worker subprocesses that can consume additional CPU core resources. Enabling the multi-process mode is done at object creation time for Kravatte, or any of its operating modes, with the workers arguments:

In [1]: new_kravatte = Kravatte(my_key, workers=8)
In [2]: my_kra_mac = mac(my_key, my_message, my_output_size, workers=16)
In [3]: my_wbc = KravatteWBC(block_size, my_tweak, my_key, workers=4)

For optimal performance, the number of workers should match the number of CPU cores reported by os.cpu_count. This is set automatically if workers is set to 0:

# Equivalent objects
In [4]: my_psrng = KravatteOracle(my_seed, my_key, workers=0)
In [5]: my_psrng = KravatteOracle(my_seed, my_key, workers=os.cpu_count())

Multi-process mode can be explicitly disabled by setting workers to None:

In [5]: my_psrng = KravatteOracle(my_seed, my_key, workers=None)

There is a non-trivial performance cost associated with generating new Python processes. For small, generally < 100KB, inputs and outputs, it can be faster to use the single process variant.

Testing

A full test suite is available in test_kravatte.py. Assuming the kravatte module is installed, tests can be invoked with pytest:

$ pytest -xvvv test_kravatte.py

The same tests are run against the standard codepath and the multiprocess code path utilizing all available CPU cores. Test vectors were generated using the KeccakTools C++ library available from the Keccak Team

Caveats

  • Being a Python implementation, performance on large files or data sets may be inadequate (even with multi-processing enabeled).
  • The inputs and outputs of this implementation are limited to byte (8-bit) divisible sizes
  • While security was top of mind during development, this implementation has not been fully audited for timing attacks, side channel attacks or other vulnerabilities. Other bugs not caught by the test cases may be present. Use in a production environment is not encouraged.

If any of above are of concern, please check out the official KeccakTools and Keccak Code Package

Project details


Release history Release notifications

This version
History Node

1.0.0

History Node

0.9.2

History Node

0.9.1

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Filename, size & hash SHA256 hash help File type Python version Upload date
kravatte-1.0.0-py2.py3-none-any.whl (21.7 kB) Copy SHA256 hash SHA256 Wheel py2.py3 May 19, 2018
kravatte-1.0.0.tar.gz (20.6 kB) Copy SHA256 hash SHA256 Source None May 19, 2018

Supported by

Elastic Elastic Search Pingdom Pingdom Monitoring Google Google BigQuery Sentry Sentry Error logging CloudAMQP CloudAMQP RabbitMQ AWS AWS Cloud computing DataDog DataDog Monitoring Fastly Fastly CDN DigiCert DigiCert EV certificate StatusPage StatusPage Status page