3
$\begingroup$

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())
$\endgroup$
16
  • 1
    $\begingroup$ The nonce can be deterministic and it is common practice to use a counter for that. Does ChaCha20-Poly1305 need random nonce? $\endgroup$
    – kelalaka
    Sep 10, 2019 at 8:51
  • $\begingroup$ Thanks @kelalaka for the feedback, but specifically can it be derived from the private key in a safe manner like I proposed? I see there are many nonce-related chacha20 questions on Crypto SE but couldn't find an answer to this. $\endgroup$ Sep 10, 2019 at 8:54
  • 1
    $\begingroup$ "the key is only used once for a given plaintext" means that a key can be used for different plaintexts. If that is not what you mean, please update your question. $\endgroup$
    – A. Hersean
    Sep 10, 2019 at 12:07
  • 2
    $\begingroup$ When using one-time-key, you can safely derive the nonce from the private key only if there's no way to derive the key or a part of the key from a nonce. In this case, nonce=SHA3(key) would be safe. If the nonce is never made public, the hashing step might not be necessary, but since the algorithm was designed assuming that the nonce is public, this is not sure without further analysis, and it would surprise me that such an analysis would ever be performed. $\endgroup$
    – A. Hersean
    Sep 10, 2019 at 13:21
  • 2
    $\begingroup$ What does ‘sharing with others via…DSA’ mean? I think you should take a step back from this particular problem, find a textbook on cryptography like Cryptography Engineering by Bruce Schneier, Niels Ferguson, and Tadayoshi Kohno, and start from your goals before concocting schemes out of ingredients you don't understand. $\endgroup$ Sep 10, 2019 at 13:50

1 Answer 1

7
$\begingroup$

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.

$\endgroup$
5
  • $\begingroup$ Thanks @Squeamish_Ossifrage, very insightful answer! So just to confirm, if the key is cryptographically-secure in terms of its randomness, the nonce can be zero as in a 96-bit string of zeroes? The library I was using is from here: github.com/ph4r05/py-chacha20poly1305 (I will paste the python app code in the question now). $\endgroup$ Sep 10, 2019 at 15:45
  • 1
    $\begingroup$ @StevenHatzakis If the key is chosen uniformly at random, and is kept secret, and is chosen independently for each message, then you can safely use an all zero nonce. Note: The ChaCha/Poly1305 authenticated cipher, which has a 96-bit nonce and a $2^{38}$-byte message length limit, is not the same as the ChaCha stream cipher as originally described, which has a 64-bit nonce and a $2^{70}$-byte message length limit. Neither one can safely take a random nonce, and also the software you cited should not be used because it is full of timing side channels. $\endgroup$ Sep 10, 2019 at 20:27
  • $\begingroup$ Thanks for the pointer @Squeamish_Ossifrage, also was aware of the side-channel risks described on that repo (although I imagine one could still use safely on a cold-storage device that doesn't have internet connectivity, even if some leakage could be left in memory etc.) although I am looking for a safer Python implementation. $\endgroup$ Sep 11, 2019 at 12:35
  • $\begingroup$ Strictly speaking, crypto_secretbox_xsalsa20poly1305 uses Salsa20, not ChaCha. ChaCha is more secure (not that it is likely to matter for 20 rounds of either, though). I'd recommend XChaCha20-Poly1305 instead. $\endgroup$
    – forest
    Sep 12, 2019 at 7:46
  • 1
    $\begingroup$ I recommend crypto_secretbox_xsalsa20poly1305 because it is widely available via NaCl and derivatives, and it has been so for a decade. Nothing wrong with using XChaCha instead of XSalsa20, but it's not as widely available. $\endgroup$ Sep 12, 2019 at 15:19

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.