A stream cipher based on BLAKE2b. There are two modes:
- Tango12d: deterministic mode. No nonce is required.
- Tango12p: probabilistic mode. A nonce is required.
- Install the Sodium.Core NuGet package in Visual Studio.
- Download the latest release.
- Move the downloaded DLL file into your Visual Studio project folder.
- Click on the
Project
tab andAdd Project Reference...
in Visual Studio. - Go to
Browse
, click theBrowse
button, and select the downloaded DLL file.
Note that the libsodium library requires the Visual C++ Redistributable for Visual Studio 2015-2019 to work on Windows. If you want your program to be portable, then you must keep the relevant (x86 or x64) vcruntime140.dll
file in the same folder as your executable on Windows.
const string message = "This is a test.";
const int keyLength = 64;
// The message could be a file
byte[] message = Encoding.UTF8.GetBytes(message);
// The key should be random for each message
byte[] key = SodiumCore.GetRandomBytes(keyLength);
// Encrypt the message
byte[] ciphertext = Tango12d.Encrypt(message, key);
// Decrypt the ciphertext
byte[] plaintext = Tango12d.Decrypt(ciphertext, key);
const string message = "This is a test.";
const int nonceLength = 24;
const int keyLength = 32;
// The message could be a file
byte[] message = Encoding.UTF8.GetBytes(message);
// The nonce can be random. Increment the nonce for each message encrypted using the same key
byte[] nonce = SodiumCore.GetRandomBytes(nonceLength);
// The key can be random or derived using a KDF (e.g. Argon2, HKDF, etc)
byte[] key = SodiumCore.GetRandomBytes(keyLength);
// Encrypt the message
byte[] ciphertext = Tango12p.Encrypt(message, nonce, key);
// Decrypt the ciphertext
byte[] plaintext = Tango12p.Decrypt(ciphertext, nonce, key);
internal const int BlockSize = 64;
internal const int CounterLength = 64;
internal const int NonceLength = 24;
internal const int KeyLength = 64;
- An empty 64 byte counter is created.
var counter = new byte[Constants.CounterLength];
- The message is read in blocks of 64 bytes.
var buffer = new byte[Constants.BlockSize];
using var memoryStream = new MemoryStream(message);
while ((bytesRead = memoryStream.Read(buffer, offset: 0, buffer.Length)) > 0)
- For each block, BLAKE2b-512, with the counter as the message and the key as the key, is used to generate a keystream block.
byte[] keystreamBlock = GenericHash.Hash(counter, key, Constants.BlockSize);
- The message block and keystream block are XORed together to produce the ciphertext block.
internal static byte[] Xor(byte[] message, byte[] keystream)
{
var ciphertext = new byte[message.Length];
for (int i = 0; i < ciphertext.Length; i++)
{
ciphertext[i] = (byte)(message[i] ^ keystream[i]);
}
return ciphertext;
}
- The counter is incremented.
counter = Utilities.Increment(counter);
- This continues until the end of the message is reached.
- An empty 64 byte counter is created.
var counter = new byte[Constants.CounterLength];
- The counter and 24 byte nonce are concatenated together.
var counter = new byte[Constants.CounterLength];
counter = Arrays.Concat(counter, nonce);
- The message is read in blocks of 64 bytes.
var buffer = new byte[Constants.BlockSize];
using var memoryStream = new MemoryStream(message);
while ((bytesRead = memoryStream.Read(buffer, offset: 0, buffer.Length)) > 0)
- For each block, BLAKE2b-512, with the counter and nonce as the message and the key as the key, is used to generate a keystream block.
byte[] keystreamBlock = GenericHash.Hash(counter, key, Constants.BlockSize);
- The message block and keystream block are XORed together to produce the ciphertext block.
internal static byte[] Xor(byte[] message, byte[] keystream)
{
var ciphertext = new byte[message.Length];
for (int i = 0; i < ciphertext.Length; i++)
{
ciphertext[i] = (byte)(message[i] ^ keystream[i]);
}
return ciphertext;
}
- The counter is incremented.
counter = Utilities.Increment(counter);
- This continues until the end of the message is reached.