Encrypted streams and file encryption
This high-level API encrypts a sequence of messages, or a single message split into an arbitrary number of chunks, using a secret key, with the following properties:
Messages cannot be truncated, removed, reordered, duplicated or modified without this being detected by the decryption functions.
The same sequence encrypted twice will produce different ciphertexts.
An authentication tag is added to each encrypted message: stream corruption will be detected early, without having to read the stream until the end.
Each message can include additional data (ex: timestamp, protocol version) in the computation of the authentication tag.
Messages can have different sizes.
There are no practical limits to the total length of the stream, or to the total number of individual messages.
Ratcheting: at any point in the stream, it is possible to “forget” the key used to encrypt the previous messages, and switch to a new key.
This API can be used to securely send an ordered sequence of messages to a peer. Since the length of the stream is not limited, it can also be used to encrypt files regardless of their size.
It transparently generates nonces and automatically handles key rotation.
The crypto_secretstream_*()
API was introduced in libsodium 1.0.14.
Example (stream encryption)
Example (stream decryption)
See down below for a complete example of file encryption/decryption using the secretstream API.
Usage
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
, orcrypto_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.
Encryption
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).
Decryption
The crypto_secretstream_xchacha20poly1305_init_pull()
function initializes a state given a secret key k
and a header header
. The key k
will not be required any more for subsequent operations.
It returns 0
on success, or -1
if the header is invalid.
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
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.
Constants
crypto_secretstream_xchacha20poly1305_ABYTES
crypto_secretstream_xchacha20poly1305_HEADERBYTES
crypto_secretstream_xchacha20poly1305_KEYBYTES
crypto_secretstream_xchacha20poly1305_MESSAGEBYTES_MAX
crypto_secretstream_xchacha20poly1305_TAG_MESSAGE
crypto_secretstream_xchacha20poly1305_TAG_PUSH
crypto_secretstream_xchacha20poly1305_TAG_REKEY
crypto_secretstream_xchacha20poly1305_TAG_FINAL
Algorithm
Initialization (secretstream_init
): a subkey k
and a 64-bit nonce n
are derived from a key K
and a 192-bit random nonce N
, using the same algorithm as XChaCha20. i
is a 32-bit counter.
secretstream_init_push()
outputs N
.
Encryption:
For every message M
with a tag T
:
secretstream_push()
outputs c
with the first block truncated to the tag size: c[0] || c[64..] || mac
Encrypting a unique message using secretstream
is equivalent to ChaCha20Poly1305-IETF(key = k, nonce = 1 || n, T || {0} * 63 || M)
.
Rekeying:
A FINAL
tag performs an implicit rekeying.
File encryption example code
Last updated