Skip to content

Commit

Permalink
Add Aes256Gcm encryption with detached tags
Browse files Browse the repository at this point in the history
  • Loading branch information
ektrah committed Jan 1, 2025
1 parent f19f9e3 commit 6d93de0
Show file tree
Hide file tree
Showing 4 changed files with 163 additions and 0 deletions.
108 changes: 108 additions & 0 deletions src/Cryptography/Aes256Gcm.cs
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,62 @@ internal override void CreateKey(
keyHandle = SecureMemoryHandle.CreateFrom(seed);
}

public void EncryptDetached(
Key key,
ReadOnlySpan<byte> nonce,
ReadOnlySpan<byte> associatedData,
ReadOnlySpan<byte> plaintext,
Span<byte> ciphertext,
Span<byte> authenticationTag)
{
if (key == null)
{
throw Error.ArgumentNull_Key(nameof(key));
}
if (key.Algorithm != this)
{
throw Error.Argument_KeyAlgorithmMismatch(nameof(key), nameof(key));
}
if (nonce.Length != NonceSize)
{
throw Error.Argument_NonceLength(nameof(nonce), NonceSize);
}
if (ciphertext.Length != plaintext.Length)
{
throw new ArgumentException();
}
if (ciphertext.Overlaps(plaintext, out int offset) && offset != 0)
{
throw Error.Argument_OverlapCiphertext(nameof(ciphertext));
}
if (authenticationTag.Length != TagSize)
{
throw new ArgumentException();
}

SecureMemoryHandle keyHandle = key.Handle;

Debug.Assert(keyHandle.Size == crypto_aead_aes256gcm_KEYBYTES);
Debug.Assert(nonce.Length == crypto_aead_aes256gcm_NPUBBYTES);
Debug.Assert(ciphertext.Length == plaintext.Length);
Debug.Assert(authenticationTag.Length == crypto_aead_aes256gcm_ABYTES);

int error = crypto_aead_aes256gcm_encrypt_detached(
ciphertext,
authenticationTag,
out ulong maclen,
plaintext,
(ulong)plaintext.Length,
associatedData,
(ulong)associatedData.Length,
IntPtr.Zero,
nonce,
keyHandle);

Debug.Assert(error == 0);
Debug.Assert((ulong)authenticationTag.Length == maclen);
}

private protected override void EncryptCore(
SecureMemoryHandle keyHandle,
ReadOnlySpan<byte> nonce,
Expand Down Expand Up @@ -120,6 +176,58 @@ internal override int GetSeedSize()
return crypto_aead_aes256gcm_KEYBYTES;
}

public bool DecryptDetached(
Key key,
ReadOnlySpan<byte> nonce,
ReadOnlySpan<byte> associatedData,
ReadOnlySpan<byte> ciphertext,
ReadOnlySpan<byte> authenticationTag,
Span<byte> plaintext)
{
if (key == null)
{
throw Error.ArgumentNull_Key(nameof(key));
}
if (key.Algorithm != this)
{
throw Error.Argument_KeyAlgorithmMismatch(nameof(key), nameof(key));
}
if (nonce.Length != NonceSize || authenticationTag.Length != TagSize)
{
return false;
}
if (plaintext.Length != ciphertext.Length)
{
throw new ArgumentException();
}
if (plaintext.Overlaps(ciphertext, out int offset) && offset != 0)
{
throw Error.Argument_OverlapPlaintext(nameof(plaintext));
}

SecureMemoryHandle keyHandle = key.Handle;

Debug.Assert(keyHandle.Size == crypto_aead_aes256gcm_KEYBYTES);
Debug.Assert(nonce.Length == crypto_aead_aes256gcm_NPUBBYTES);
Debug.Assert(ciphertext.Length == plaintext.Length);
Debug.Assert(authenticationTag.Length == crypto_aead_aes256gcm_ABYTES);

int error = crypto_aead_aes256gcm_decrypt_detached(
plaintext,
IntPtr.Zero,
ciphertext,
(ulong)ciphertext.Length,
authenticationTag,
associatedData,
(ulong)associatedData.Length,
nonce,
keyHandle);

// libsodium clears plaintext if decryption fails

return error == 0;
}

private protected override bool DecryptCore(
SecureMemoryHandle keyHandle,
ReadOnlySpan<byte> nonce,
Expand Down
27 changes: 27 additions & 0 deletions src/Interop/Interop.Aead.Aes256Gcm.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,19 @@ internal static partial int crypto_aead_aes256gcm_decrypt(
ReadOnlySpan<byte> npub,
SecureMemoryHandle k);

[LibraryImport(Libraries.Libsodium)]
[UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
internal static partial int crypto_aead_aes256gcm_decrypt_detached(
Span<byte> m,
IntPtr nsec,
ReadOnlySpan<byte> c,
ulong clen,
ReadOnlySpan<byte> mac,
ReadOnlySpan<byte> ad,
ulong adlen,
ReadOnlySpan<byte> npub,
SecureMemoryHandle k);

[LibraryImport(Libraries.Libsodium)]
[UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
internal static partial int crypto_aead_aes256gcm_encrypt(
Expand All @@ -41,6 +54,20 @@ internal static partial int crypto_aead_aes256gcm_encrypt(
ReadOnlySpan<byte> npub,
SecureMemoryHandle k);

[LibraryImport(Libraries.Libsodium)]
[UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
internal static partial int crypto_aead_aes256gcm_encrypt_detached(
Span<byte> c,
Span<byte> mac,
out ulong maclen_p,
ReadOnlySpan<byte> m,
ulong mlen,
ReadOnlySpan<byte> ad,
ulong adlen,
IntPtr nsec,
ReadOnlySpan<byte> npub,
SecureMemoryHandle k);

[LibraryImport(Libraries.Libsodium)]
[UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
internal static partial int crypto_aead_aes256gcm_is_available();
Expand Down
2 changes: 2 additions & 0 deletions src/Interop/Interop.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,9 @@ Interop.Aead.Aes256Gcm.cs:
functions:
- crypto_aead_aes256gcm_abytes
- crypto_aead_aes256gcm_decrypt (out ulong mlen_p, IntPtr nsec, SecureMemoryHandle k)
- crypto_aead_aes256gcm_decrypt_detached (IntPtr nsec, SecureMemoryHandle k)
- crypto_aead_aes256gcm_encrypt (out ulong clen_p, IntPtr nsec, SecureMemoryHandle k)
- crypto_aead_aes256gcm_encrypt_detached (out ulong maclen_p, IntPtr nsec, SecureMemoryHandle k)
- crypto_aead_aes256gcm_is_available
- crypto_aead_aes256gcm_keybytes
- crypto_aead_aes256gcm_npubbytes
Expand Down
26 changes: 26 additions & 0 deletions tests/Algorithms/Aes256GcmTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -52,5 +52,31 @@ public static void EncryptDecrypt(int length)
}

#endregion

#region Encrypt/Decrypt Detached

[Theory]
[MemberData(nameof(PlaintextLengths))]
public static void EncryptDecryptDetached(int length)
{
var a = AeadAlgorithm.Aes256Gcm;

using var k = new Key(a);
var n = Utilities.RandomBytes[..a.NonceSize];
var ad = Utilities.RandomBytes[..100];
var c = new byte[length];
var t = new byte[a.TagSize];

var expected = Utilities.RandomBytes[..length].ToArray();

a.EncryptDetached(k, n, ad, expected, c, t);

var actual = new byte[length];

Assert.True(a.DecryptDetached(k, n, ad, c, t, actual));
Assert.Equal(expected, actual);
}

#endregion
}
}

0 comments on commit 6d93de0

Please sign in to comment.