3

Note: I realize in ChaCha20 the nonce should be random and unique each time follow certain constraints but am trying to determine whether there could be a safe way to use it just once if other constraints were in place as follows:

Could the nonce be entirely deterministic and derived from the key so long as the key is only used exclusively once for a distinct plaintext (akin to the one-time-pad). I realize this is risky if the key/nonce pair is re-used on a different plaintext, but wouldn't it be as safe as the one-time-pad if the pair is never reused on a different plaintext message?

Related questions: Does ChaCha20 counter actually increment through iterations?

What happens if a nonce is reused in ChaCha20-Poly1305?

Can I reuse a nonce to retransmit the same packet using ChaCha20-Poly1305?

In other words, if a user wanted to only retain a 256-bit key using ChaCha20 and not have to retain the random nonce, could the nonce, for example, be derived from the leading 12 bytes (i.e 96 bits) of the 32-byte (256-bit) key, provided that they are both never re-used to encrypt a different message than the original one?

[Update: Another further constraint to this scenario, the 'nonce' is NOT made public and the algorithm is running locally on the user's device, such as in a cold storage or internet-gapped environment, even though the resulting ciphertext may later be shared online]

Example secret key and deterministic nonce:

secret key: ba665a007182e68a40aa23c781ed8dcd23b0e3673c7067a9df9cdad13b1d4880

determinisitic nonce based on leading 24-hex of 64-hex key: ba665a007182e68a40aa23c7

plaintext: 68656c6c6f

resulting chacha20 ciphertext:702da4b13e682d4b840b19cde305583b415765f936

Are there any known attacks given the above constraints or can it be considered secure (provided again the key & nonce are never reused on a different plaintext)?

''' ChaCha20 code from https://github.com/ph4r05/py-chacha20poly1305 '''


import os

from chacha20poly1305 import ChaCha20Poly1305
input_hex=str(input('paste 64 hex priv key without 0x pad'))
input_bytes = bytes.fromhex(input_hex)

private_key = input_bytes #os.urandom(32) # equal to 256 bits or 32 bytes
print('private key bytes:',private_key)
cip = ChaCha20Poly1305(private_key)
format_plain=input('paste plaint text data to encrypt')
plain=bytes.fromhex(format_plain)
nonce = os.urandom(12) # Random nonce that is equal to 96 bits or 12 bytes
print("nonce:",bytes(nonce).hex())
ciphertext = cip.encrypt(nonce,plain)
print('ciphertext with random nonce:',bytes(ciphertext).hex())
plaintext = cip.decrypt(nonce, ciphertext)
print('plaintext:',bytearray(plaintext).hex())


### DETERMINISTIC NONCE BELOW DERIVED FROM PRIVATE KEY (Just for reference)


private_key1 = private_key
print('private key1:',bytes(private_key).hex())
cip1 = ChaCha20Poly1305(private_key)

nonce = private_key1[:12] # nonce derived from leading 12 bytes of priv key
print("determinisitic nonce:",bytes(nonce).hex())
ciphertext1 = cip1.encrypt(nonce, plain)
print('ciphertext1 with determ nonce:',bytes(ciphertext1).hex())
plaintext1 = cip1.decrypt(nonce, ciphertext1)
print('plaintext1:',bytearray(plaintext1).hex())
Steven Hatzakis
  • 401
  • 4
  • 14

1 Answers1

7

I realize in ChaCha20 the nonce should be random and unique each time…

Nope!

For the ChaCha stream cipher, it is not safe to choose the nonce at random. At 64 bits long, the ChaCha nonce is too short to be chosen at random. In general, you should use a sequential message number under any single key as the nonce.

For the XChaCha variant, like the XSalsa20 variant of Salsa20, it is safe to choose the nonce at random because the extended nonce is 192 bits long so the collision probability becomes nonnegligible only after an unimaginable $2^{96}$ messages. But your question was about ChaCha without an extended nonce.

Could the nonce be entirely deterministic and derived from the key so long as the key is only used exclusively once for a distinct plaintext (akin to the one-time-pad). I realize this is risky if the key/nonce pair is re-used on a different plaintext, but wouldn't it be as safe as the one-time-pad if the pair is never reused on a different plaintext message?

If the key is chosen independently and uniformly at random for each message, then you can safely use the fixed nonce zero. Don't mess with hare-brained schemes like copying bits of the key around into other places.

plaintext: 68656c6c6f

resulting chacha20 ciphertext: 702da4b13e682d4b840b19cde305583b415765f936

Something is wrong with your use of ChaCha because your ciphertext length is not exactly the same as your plaintext length. [Update: This is because the question is actually about the ChaCha/Poly1305 authenticated cipher of RFC 7539, not about the ChaCha stream cipher originally described by Dan Bernstein with a different nonce size.]


Also: Write down what your protocol is and how it fits into your application so that a reviewer can confirm you're following the rules of ChaCha.

Also: Don't just use ChaCha and leave it at that. Use an authenticated cipher (or AEAD, authenticated encryption with associated data) like crypto_secretbox_xsalsa20poly1305.

Squeamish Ossifrage
  • 49,816
  • 3
  • 122
  • 230