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())
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$