[Cryptography] Simple key derivation function based on card shuffling

Leo list at gkbrk.com
Fri Jul 23 12:56:21 EDT 2021


Hello Cryptography mailing list,

* I apologize if you received this twice, my email client crashed while
* sending it the first time.

I have been using the following method as a makeshift key derivation
function for some projects where I don't want to bother with depending
on more traditional cryptographic hashes or sponges. It is easier to
understand but slower to execute, because there is too much
computational power and not everything needs to run on smartcards.

The purpose is to turn a variable-length key (and usually a nonce)
into a fixed-size binary buffer. Below is some context about how this
is used.

- The input might have some predictable patterns in the binary
representation, like a lowercase ASCII password.

- The output should be a more uniform bitstring that doesn't have the
same patterns as the input.

- It should not be straightforward to get the input key from the
output buffer.

- Ideally, it should have the same resistance when you have the output
buffers from multiple related keys (such as `key || counter` or `key
|| date`).

- The output buffer can be used to seed PRNGs like xoroshiro, or
ciphers such as ChaCha20.

The algorithm works like a card shuffle. There are multiple methods of
shuffling a deck using a stream of bits, and some methods might be
better than others. This is just a random one I've picked.

The state is initialized as an array of alternating `0` and `1` bits.
The length of the array is twice the number of output bits.

Every round, the state is split into two halves. One bit is taken from
the key. If the bit is set, an element from the first, and then the
second half is appended to the new state. If the bit is 0, the second
and then the first half is used instead.

After enough iterations, it is hoped that the state is shuffled
sufficiently. The output buffer is the first half of the state.

Below is a simple implementation in Python that hasn't been optimized
much. Any opinions? Is this a valid method? Can anything useful be
salvaged from this or is it hopeless?

```
def key_bits():
while True:
for byte in key:
for i in range(8):
yield (byte >> i) & 1
key_bits = key_bits()

state = [0, 1] * 512
mid = int(len(state) / 2)

# 2^16 rounds
for _ in range(2 ** 16):
l = state[:mid]
r = state[mid:]
state = []

while l or r:
if next(key_bits):
state.append(l.pop())
state.append(r.pop())
else:
state.append(r.pop())
state.append(l.pop())
state = list(state)[:512]
```

-- 
Leo


More information about the cryptography mailing list