Endpoints/NetworkStorageCryptoHelpers.cs
using System;
using System.Text;

namespace Sandbox;

public static partial class NetworkStorage
{
	private static string Base64UrlEncode( byte[] value )
		=> Convert.ToBase64String( value ).TrimEnd( '=' ).Replace( '+', '-' ).Replace( '/', '_' );

	private static string TruncateForLog( string value, int maxLength )
	{
		if ( string.IsNullOrEmpty( value ) || value.Length <= maxLength )
			return value;

		return $"{value[..maxLength]}...";
	}

	private static string ToHex( byte[] value )
	{
		var sb = new StringBuilder( value.Length * 2 );
		foreach ( var b in value )
			sb.Append( b.ToString( "x2" ) );
		return sb.ToString();
	}

	private static (byte[] Ciphertext, byte[] Tag) Aes256GcmEncrypt( byte[] key, byte[] iv, byte[] plaintext, byte[] aad )
	{
		var roundKeys = Aes256ExpandKey( key );
		var h = AesEncryptBlock( new byte[16], roundKeys );
		var j0 = new byte[16];
		Array.Copy( iv, 0, j0, 0, 12 );
		j0[15] = 1;

		var ciphertext = new byte[plaintext.Length];
		var counter = CopyBytes( j0 );
		var offset = 0;
		while ( offset < plaintext.Length )
		{
			IncrementCounter32( counter );
			var stream = AesEncryptBlock( counter, roundKeys );
			var count = Math.Min( 16, plaintext.Length - offset );
			for ( var i = 0; i < count; i++ )
				ciphertext[offset + i] = (byte)(plaintext[offset + i] ^ stream[i]);
			offset += count;
		}

		var ghash = GHash( h, aad, ciphertext );
		var s = AesEncryptBlock( j0, roundKeys );
		var tag = new byte[16];
		for ( var i = 0; i < 16; i++ )
			tag[i] = (byte)(s[i] ^ ghash[i]);
		return (ciphertext, tag);
	}

	private static void IncrementCounter32( byte[] counter )
	{
		for ( var i = 15; i >= 12; i-- )
		{
			counter[i]++;
			if ( counter[i] != 0 )
				break;
		}
	}

	private static byte[] GHash( byte[] h, byte[] aad, byte[] ciphertext )
	{
		var y = new byte[16];
		GHashBlocks( y, h, aad );
		GHashBlocks( y, h, ciphertext );
		var len = new byte[16];
		WriteUInt64BE( len, 0, (ulong)aad.Length * 8 );
		WriteUInt64BE( len, 8, (ulong)ciphertext.Length * 8 );
		XorBlock( y, len );
		return GfMultiply( y, h );
	}

	private static void GHashBlocks( byte[] y, byte[] h, byte[] data )
	{
		for ( var offset = 0; offset < data.Length; offset += 16 )
		{
			var block = new byte[16];
			Array.Copy( data, offset, block, 0, Math.Min( 16, data.Length - offset ) );
			XorBlock( y, block );
			var next = GfMultiply( y, h );
			Array.Copy( next, y, 16 );
		}
	}

	private static byte[] GfMultiply( byte[] x, byte[] y )
	{
		var z = new byte[16];
		var v = CopyBytes( y );
		for ( var i = 0; i < 128; i++ )
		{
			if ( (x[i / 8] & (1 << (7 - (i % 8)))) != 0 )
				XorBlock( z, v );
			var lsb = (v[15] & 1) != 0;
			ShiftRightOne( v );
			if ( lsb )
				v[0] ^= 0xe1;
		}
		return z;
	}

	private static void XorBlock( byte[] target, byte[] value )
	{
		for ( var i = 0; i < 16; i++ )
			target[i] ^= value[i];
	}

	private static void ShiftRightOne( byte[] value )
	{
		var carry = 0;
		for ( var i = 0; i < value.Length; i++ )
		{
			var nextCarry = value[i] & 1;
			value[i] = (byte)((value[i] >> 1) | (carry << 7));
			carry = nextCarry;
		}
	}

	private static void WriteUInt64BE( byte[] buffer, int offset, ulong value )
	{
		for ( var i = 7; i >= 0; i-- )
		{
			buffer[offset + i] = (byte)value;
			value >>= 8;
		}
	}

}