See down below for a complete example of file encryption/decryption using the secretstream API.
The crypto_secretstream_*_push() functions set creates an encrypted stream. The crypto_secretstream_*_pull() functions set is the decryption counterpart.
An encrypted stream starts with a short header, whose size is crypto_secretstream_xchacha20poly1305_HEADERBYTES bytes. That header must be sent/stored before the sequence of encrypted messages, as it is required to decrypt the stream. The header content doesn't have to be secret and decryption with a different header would fail.
A tag is attached to each message. That tag can be any of:
0, or crypto_secretstream_xchacha20poly1305_TAG_MESSAGE: the most common tag, that doesn't add any information about the nature of the message.
crypto_secretstream_xchacha20poly1305_TAG_FINAL: indicates that the message marks the end of the stream, and erases the secret key used to encrypt the previous sequence.
crypto_secretstream_xchacha20poly1305_TAG_PUSH: indicates that the message marks the end of a set of messages, but not the end of the stream. For example, a huge JSON string sent as multiple chunks can use this tag to indicate to the application that the string is complete and that it can be decoded. But the stream itself is not closed, and more data may follow.
crypto_secretstream_xchacha20poly1305_TAG_REKEY: "forget" the key used to encrypt this message and the previous ones, and derive a new secret key.
A typical encrypted stream simply attaches 0 as a tag to all messages, except the last one which is tagged as TAG_FINAL.
Note that tags are encrypted; encrypted streams do not reveal any information about sequence boundaries (PUSH and REKEY tags).
For each message, additional data can be included in the computation of the authentication tag. With this API, additional data is rarely required, and most applications can just use NULL and a length of 0 instead.
Creates a random, secret key to encrypt a stream, and stores it into k.
Note that using this function is not required to obtain a suitable key: the secretstream API can use any secret key whose size is crypto_secretstream_xchacha20poly1305_KEYBYTES bytes.
Network protocols can leverage the key exchange API in order to get a shared key that can be used to encrypt streams. Similarly, file encryption applications can use the password hashing API to get a key that can be used with the functions below.
The crypto_secretstream_xchacha20poly1305_init_push() function initializes a state state using the key k and an internal, automatically generated initialization vector. It then stores the stream header into header (crypto_secretstream_xchacha20poly1305_HEADERBYTES bytes).
This is the first function to call in order to create an encrypted stream. The key k will not be required any more for subsequent operations.
The crypto_secretstream_xchacha20poly1305_push() function encrypts a message m of length mlen bytes using the state state and the tag tag.
Additional data ad of length adlen can be included in the computation of the authentication tag. If no additional data is required, ad can be NULL and adlen set to 0.
The ciphertext is put into c.
If clen_p is not NULL, the ciphertext length will be stored at that address. But with this particular construction, the ciphertext length is guaranteed to always be mlen + crypto_secretstream_xchacha20poly1305_ABYTES.
The maximum length of an individual message is crypto_secretstream_xchacha20poly1305_MESSAGEBYTES_MAX bytes (~ 256 GB).
The crypto_secretstream_xchacha20poly1305_pull() function verifies that c (a sequence of clen bytes) contains a valid ciphertext and authentication tag for the given state state and optional authenticated data ad of length adlen bytes.
If the ciphertext appears to be invalid, the function returns -1.
If the authentication tag appears to be correct, the decrypted message is put into m.
If tag_p is not NULL, the tag attached to the message is stored at that address.
If mlen_p is not NULL, the message length is stored at that address. But with this particular construction, it is guaranteed to always be clen - crypto_secretstream_xchacha20poly1305_ABYTES bytes.
Applications will typically call this function in a loop, until a message with the crypto_secretstream_xchacha20poly1305_TAG_FINAL tag is found.
Rekeying happens automatically and transparently, before the internal counter of the underlying cipher wraps. Therefore, streams can be arbitrary large.
Optionally, applications for which forward secrecy is critical can attach the crypto_secretstream_xchacha20poly1305_TAG_REKEY tag to a message in order to trigger an explicit rekeying. The decryption API will automatically update the key if this tag is found attached to a message.
Explicit rekeying can also be performed without adding a tag, by calling the crypto_secretstream_xchacha20poly1305_rekey() function:
This updates the state, but doesn't add any information about the key change to the stream. If this function is used to create an encrypted stream, the decryption process must call that function at the exact same stream location.