Zhenga

Private P2P network

Cicada cipher

Zhenga traffic is secured by the Vernam cipher or One-time pad encryption - the simplest and lightweight and at the same time the strongest encryption ever.

The most important part of the one-time pad cipher is the pad itself. Zhenga uses original schema to generate pseudo-random pads. It multiplies several shorter keys of prime length to get much longer non-repeating key sequence without heavy CPU utilization. This approach is inspired by periodical cicadas that emerge every 13 and 17 years which make their populations meet once in every 221 years.

The below steps quickly describe the Cicada cipher.

1. Block split

All the data is split into blocks of arbitrary length. Each block is encrypted with a unique key. Required pad length is limited to the length of the block.

2. Random salt

In order to make all keys unique a random 128 byte salt is generated for every encrypted block. The salt is transferred unencryped in the beginning of every block. All subsequent data is encrypted.

blockSalt = makeRandomString(128);

3. Block secret

A combination of the salt and the pre-shared configurable secret is the unique block secret. Uniqueness of the salt guarantees the uniqueness of the block secret.

blockSecret = blockSalt + secret

4. Key generation

The key is merely a SHA256 of the block secret. It is impossible to decrypt the message without knowing the key. It is impossible to get the secret out of the key provided that SHA256 hash is irreversible.

key = SHA256(blockSecret)

5. Longer keys

An array of predefined random suffixes is generated. The suffixes are not secret and are known to both counterparties.

static const char* SUFFIX[] =
{
  "\x4a\xcb\xaf\x4f\xc8\x41\xd4\x30",
  "\xee\x4e\xfb\xe7\x52\xec\x7b\xb6",
  ...

An arbitrary length key can now be generated from the sequence of SHAs based on the block secret and the suffixes. It is impossible to get the secret out of the combined key as the key is a sequence of SHAs of different strings.

for (j = 0; j < (keyLength mod SHA256_LENGTH); ++j)
  longKey += sha256(blockSecret + suffix[j])

6. Multiple keys

An array of predefined random prefixes is generated. The prefixes like suffixes are not secret and are known to both counterparties.

static const char* PREFIX[] =
{
  "\xd7\xd4\x8d\x93\xb6\x93\x90\x9e\x10\x37\x5d\x50\x22\x97\x5b\xd0",
  "\xa4\x15\x61\xfe\x60\xab\x7a\xe7\xd2\x1b\x84\x62\x36\x3c\x42\x08",
  ...

A number of keys of prime length can now be generated from the block key and the sequences of prefixes and suffixes. It is still impossible to get the secret out of the keys as all the keys are sequences of SHAs of different strings.

int primes{53, 59,...};
for (i = 0; i < ARRAY_SIZE(primes); ++i)
  for (j = 0; j < primes[i] mod SHA256_LENGTH; ++j)
    key[i] += sha256(prefix[i] + blockSecret + suffix[j])

7. Encryption and decryption

The data is encrypted and decrypted by cyclically XORing it with all the generated keys. Prime key lengths guarantee that there will be no key repeats until (prime[0] * prime[1] *…) operations. This gives a much longer non-repeated key sequence with a shorter base keys.

for (k = 0; k < dataSize; ++k)
  data[k] = data[k] ^ key0 ^ key1 ^...

Key multiplication

Implementation

Currently Zhenga uses SHA256 hash function and 3 keys of lengths 521, 523 and 541 bytes, which gives block size of about 140 megabytes with only 51 SHA256 generated for every block. Key and prefix/suffix lengths are easily adjusted and other hash functions can be used to get even better protection in the future.