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