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?
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:
determinisitic nonce based on leading 24-hex of 64-hex key:
resulting chacha20 ciphertext:
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())