-
Notifications
You must be signed in to change notification settings - Fork 0
XChaChaBufferedStream
Parameter | Value |
---|---|
Encryption Algorithm | XChaCha20 |
Authentication Algorithm | Poly1305-MAC |
Key Size | 32 bytes |
Nonce Size | 24 bytes |
Max Message Size | 2^64-1 bytes |
The XChaChaBufferedStream
class wraps the crypto_secretstream_xchacha20poly1305_*
functions provided in libsodium.
The following description is taken from the libsodium documentation [2018-05-30]:
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.
It [the API] transparently generates nonces and automatically handles key rotation.
Note: manual key ratcheting has not been implemented
The stream encrypts/decrypts data in fixed sized blocks, but allows you to read and write data without worrying about chunking the data manually. The default block size is 128KB. This is particularly useful if you are compressing data before encrypting it, as you can compose the compression stream with the cipher stream without needing to break the compressed data into chunks.
The following example demonstrates how to encrypt/decrypt a pair of messages:
using (var ciphertextStream = new MemoryStream())
using (var key = XChaChaKey.Generate())
{
var block1 = Encoding.UTF8.GetBytes("The quick brown fox");
var block2 = Encoding.UTF8.GetBytes("jumps over the lazy dog");
using (var encryptionStream = new XChaChaBufferedStream(ciphertextStream, key, EncryptionMode.Encrypt))
{
encryptionStream.Write(block1);
encryptionStream.Write(block2);
}
var ciphertext = ciphertextStream.ToArray();
}
var ciphertext = Convert.FromBase64String("...");
var keyBytes = Convert.FromBase64String("v7ymK2liU1EX64LE/6lJYEA8uW9bjcbe3Y/TdjWQYQk=");
using (var ciphertextStream = new MemoryStream(ciphertext))
using (var key = new XChaChaKey(keyBytes))
{
var plaintext = new byte[42];
using (var decryptionStream = new XChaChaBufferedStream(ciphertextStream, key, EncryptionMode.Decrypt))
{
decryptionStream.Read(plaintext);
}
}
Any amount of plaintext can be read out of the stream. It doesn't have to be read out in fixed sized blocks.
The following examples demonstrate how to encrypt/decrypt a file in 128KB blocks:
using (var inFileStream = File.OpenRead(@"C:\input.txt"))
using (var outFileStream = File.OpenWrite(@"C:\output.enc"))
using (var key = XChaChaKey.Generate())
using (var encryptionStream = new XChaChaBufferedStream(outFileStream, key, EncryptionMode.Encrypt))
{
var buffer = new byte[128 * 1024];
int bytesRead;
do
{
bytesRead = inFileStream.Read(buffer);
encryptionStream.Write(buffer);
} while (bytesRead != 0);
}
var keyBytes = ...; // load the raw key
using (var inFileStream = File.OpenRead(@"C:\input.enc"))
using (var outFileStream = File.OpenWrite(@"C:\output.txt"))
using (var key = new XChaChaKey(keyBytes))
using (var decryptionStream = new XChaChaBufferedStream(inFileStream, key, EncryptionMode.Decrypt))
{
var buffer = new byte[128 * 1024];
int bytesRead;
do
{
bytesRead = decryptionStream.Read(buffer);
outFileStream.Write(buffer, 0, bytesRead);
} while (bytesRead != 0);
}
The following examples demonstrate how to encrypt/decrypt a file while simultaneously compressing/decompressing the data:
using (var inFileStream = File.OpenRead(@"C:\input.txt"))
using (var outFileStream = File.OpenWrite(@"C:\output.enc"))
using (var key = XChaChaKey.Generate())
using (var encryptionStream = new XChaChaBufferedStream(outFileStream, key, EncryptionMode.Encrypt))
using (var compressionStream = new BrotliStream(encryptionStream, CompressionMode.Compress))
{
var buffer = new byte[8192];
int bytesRead;
do
{
bytesRead = inFileStream.Read(buffer);
compressionStream.Write(buffer, 0, bytesRead);
} while (bytesRead != 0);
}
var keyBytes = ...; // load the raw key
using (var inFileStream = File.OpenRead(@"C:\input.enc"))
using (var outFileStream = File.OpenWrite(@"C:\output.txt"))
using (var key = new XChaChaKey(keyBytes))
using (var decryptionStream = new XChaChaBufferedStream(inFileStream, key, EncryptionMode.Decrypt))
using (var decompressionStream = new BrotliStream(decryptionStream, CompressionMode.Decompress))
{
var buffer = new byte[128 * 1024];
int bytesRead;
do
{
bytesRead = decompressionStream.Read(buffer);
outFileStream.Write(buffer, 0, bytesRead);
} while (bytesRead != 0);
}
By default XChaChaBufferedStream
encrypts in blocks of 128KB. An overload of the constructor exists to provide a custom block size. Note that if you use a custom block size during encryption, the same block size must be used during decryption.
During encryption the final block is appended with a special tag, marking the end of the message. During decryption this method will return whether the most recently decrypted block was the last one in the message, i.e. that the message has been fully decrypted.
bool endOfMessage = decryptionStream.VerifyEndOfMessage();