Skip to content

Instantly share code, notes, and snippets.

@chgeuer
Last active September 13, 2024 16:52
Show Gist options
  • Save chgeuer/85a373663384d6ddd4089b01e84fe78c to your computer and use it in GitHub Desktop.
Save chgeuer/85a373663384d6ddd4089b01e84fe78c to your computer and use it in GitHub Desktop.

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())}\")");
    }
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment