A simple .NET Core 8 sample running on Linux to show AES GCM encryption...
using System.Security.Cryptography;
using System.Text;
// https://learn.microsoft.com/en-us/dotnet/standard/security/cross-platform-cryptography#authenticated-encryption
Console.Out.WriteLine($"Is supported: {AesGcm.IsSupported}");
byte[] key = RandomNumberGenerator.GetBytes(32);
byte[] plaintext = Encoding.UTF8.GetBytes("Hello world, this is very cool!");
// The real thing
int blockSize = 2;
var ciphertext = plaintext.Encrypt(key, blockSize: blockSize);
var decrypted = ciphertext.Decrypt(key, blockSize: 2);
Console.Out.WriteLine(Encoding.UTF8.GetString(decrypted));
// Just a single block
byte[] transferBlock = plaintext.EncryptBlock(key, blockId: 1);
transferBlock.PrintDebugCiphertext();
byte[] decryptedBlock = transferBlock.DecryptBlock(key);
decryptedBlock.PrintDebugPlaintext();
public static class Extensions
{
const int NonceLength = 12;
const int TagLength = 16;
private static void AddNonce(this Span<byte> transfer, int block_id, int nonceLength = 12)
{
int remainingNonceBytes = nonceLength - sizeof(long);
new byte[] { 0, 0, 0, 0 }.CopyTo(transfer[..remainingNonceBytes]);
BitConverter.GetBytes((long)block_id).CopyTo(transfer.Slice(remainingNonceBytes, sizeof(long)));
}
public static byte[] Encrypt(this byte[] plaintext, byte[] key, int blockSize)
{
return plaintext
.Chunk(blockSize)
.Select((plaintextBlock, blockId) => plaintextBlock.EncryptBlock(key, blockId))
.SelectMany(b => b).ToArray();
}
public static byte[] Decrypt(this byte[] ciphertext, byte[] key, int blockSize)
{
return ciphertext
.Chunk(NonceLength + blockSize + TagLength)
.Select(block => block.DecryptBlock(key))
.SelectMany(b => b).ToArray();
}
public static byte[] EncryptBlock(this byte[] plaintext, byte[] key, int blockId)
{
Span<byte> transfer = new(new byte[NonceLength + plaintext.Length + TagLength]);
transfer.AddNonce(blockId, NonceLength);
using AesGcm cipher = new(key: key, tagSizeInBytes: TagLength);
cipher.Encrypt(
nonce: transfer[..NonceLength],
plaintext: plaintext,
ciphertext: transfer[NonceLength..(NonceLength + plaintext.Length)],
tag: transfer[(NonceLength + plaintext.Length)..],
associatedData: []);
return transfer.ToArray();
}
public static byte[] DecryptBlock(this byte[] transfer, byte[] key)
{
Span<byte> decrypted = new byte[transfer.Length - NonceLength - TagLength].AsSpan();
using AesGcm cipher = new(key: key, tagSizeInBytes: TagLength);
cipher.Decrypt(
nonce: transfer[..NonceLength],
ciphertext: new Span<byte>(transfer).Slice(start: NonceLength, length: transfer.Length - NonceLength - TagLength),
tag: transfer[^TagLength..],
plaintext: decrypted,
associatedData: null);
return decrypted.ToArray();
}
public static void PrintDebugCiphertext(this byte[] transfer)
{
Console.Out.WriteLine($"Transfer: {BitConverter.ToString(transfer.ToArray())}");
Console.Out.WriteLine($"Nonce: {BitConverter.ToString(transfer[..NonceLength].ToArray())}");
Console.Out.WriteLine($"Ciphertext: {BitConverter.ToString(new Span<byte>(transfer).Slice(start: NonceLength, length: transfer.Length - NonceLength - TagLength).ToArray())}");
Console.Out.WriteLine($"Tag: {BitConverter.ToString(transfer[^TagLength..].ToArray())}");
}
public static void PrintDebugPlaintext(this byte[] decrypted)
{
Console.Out.WriteLine($"Decrypted: {BitConverter.ToString(decrypted.ToArray())} (\"{Encoding.UTF8.GetString(decrypted.ToArray())}\")");
}
}