Skip to content

Commit 6d93de0

Browse files
committed
Add Aes256Gcm encryption with detached tags
1 parent f19f9e3 commit 6d93de0

File tree

4 files changed

+163
-0
lines changed

4 files changed

+163
-0
lines changed

src/Cryptography/Aes256Gcm.cs

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,62 @@ internal override void CreateKey(
8989
keyHandle = SecureMemoryHandle.CreateFrom(seed);
9090
}
9191

92+
public void EncryptDetached(
93+
Key key,
94+
ReadOnlySpan<byte> nonce,
95+
ReadOnlySpan<byte> associatedData,
96+
ReadOnlySpan<byte> plaintext,
97+
Span<byte> ciphertext,
98+
Span<byte> authenticationTag)
99+
{
100+
if (key == null)
101+
{
102+
throw Error.ArgumentNull_Key(nameof(key));
103+
}
104+
if (key.Algorithm != this)
105+
{
106+
throw Error.Argument_KeyAlgorithmMismatch(nameof(key), nameof(key));
107+
}
108+
if (nonce.Length != NonceSize)
109+
{
110+
throw Error.Argument_NonceLength(nameof(nonce), NonceSize);
111+
}
112+
if (ciphertext.Length != plaintext.Length)
113+
{
114+
throw new ArgumentException();
115+
}
116+
if (ciphertext.Overlaps(plaintext, out int offset) && offset != 0)
117+
{
118+
throw Error.Argument_OverlapCiphertext(nameof(ciphertext));
119+
}
120+
if (authenticationTag.Length != TagSize)
121+
{
122+
throw new ArgumentException();
123+
}
124+
125+
SecureMemoryHandle keyHandle = key.Handle;
126+
127+
Debug.Assert(keyHandle.Size == crypto_aead_aes256gcm_KEYBYTES);
128+
Debug.Assert(nonce.Length == crypto_aead_aes256gcm_NPUBBYTES);
129+
Debug.Assert(ciphertext.Length == plaintext.Length);
130+
Debug.Assert(authenticationTag.Length == crypto_aead_aes256gcm_ABYTES);
131+
132+
int error = crypto_aead_aes256gcm_encrypt_detached(
133+
ciphertext,
134+
authenticationTag,
135+
out ulong maclen,
136+
plaintext,
137+
(ulong)plaintext.Length,
138+
associatedData,
139+
(ulong)associatedData.Length,
140+
IntPtr.Zero,
141+
nonce,
142+
keyHandle);
143+
144+
Debug.Assert(error == 0);
145+
Debug.Assert((ulong)authenticationTag.Length == maclen);
146+
}
147+
92148
private protected override void EncryptCore(
93149
SecureMemoryHandle keyHandle,
94150
ReadOnlySpan<byte> nonce,
@@ -120,6 +176,58 @@ internal override int GetSeedSize()
120176
return crypto_aead_aes256gcm_KEYBYTES;
121177
}
122178

179+
public bool DecryptDetached(
180+
Key key,
181+
ReadOnlySpan<byte> nonce,
182+
ReadOnlySpan<byte> associatedData,
183+
ReadOnlySpan<byte> ciphertext,
184+
ReadOnlySpan<byte> authenticationTag,
185+
Span<byte> plaintext)
186+
{
187+
if (key == null)
188+
{
189+
throw Error.ArgumentNull_Key(nameof(key));
190+
}
191+
if (key.Algorithm != this)
192+
{
193+
throw Error.Argument_KeyAlgorithmMismatch(nameof(key), nameof(key));
194+
}
195+
if (nonce.Length != NonceSize || authenticationTag.Length != TagSize)
196+
{
197+
return false;
198+
}
199+
if (plaintext.Length != ciphertext.Length)
200+
{
201+
throw new ArgumentException();
202+
}
203+
if (plaintext.Overlaps(ciphertext, out int offset) && offset != 0)
204+
{
205+
throw Error.Argument_OverlapPlaintext(nameof(plaintext));
206+
}
207+
208+
SecureMemoryHandle keyHandle = key.Handle;
209+
210+
Debug.Assert(keyHandle.Size == crypto_aead_aes256gcm_KEYBYTES);
211+
Debug.Assert(nonce.Length == crypto_aead_aes256gcm_NPUBBYTES);
212+
Debug.Assert(ciphertext.Length == plaintext.Length);
213+
Debug.Assert(authenticationTag.Length == crypto_aead_aes256gcm_ABYTES);
214+
215+
int error = crypto_aead_aes256gcm_decrypt_detached(
216+
plaintext,
217+
IntPtr.Zero,
218+
ciphertext,
219+
(ulong)ciphertext.Length,
220+
authenticationTag,
221+
associatedData,
222+
(ulong)associatedData.Length,
223+
nonce,
224+
keyHandle);
225+
226+
// libsodium clears plaintext if decryption fails
227+
228+
return error == 0;
229+
}
230+
123231
private protected override bool DecryptCore(
124232
SecureMemoryHandle keyHandle,
125233
ReadOnlySpan<byte> nonce,

src/Interop/Interop.Aead.Aes256Gcm.cs

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,19 @@ internal static partial int crypto_aead_aes256gcm_decrypt(
2828
ReadOnlySpan<byte> npub,
2929
SecureMemoryHandle k);
3030

31+
[LibraryImport(Libraries.Libsodium)]
32+
[UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
33+
internal static partial int crypto_aead_aes256gcm_decrypt_detached(
34+
Span<byte> m,
35+
IntPtr nsec,
36+
ReadOnlySpan<byte> c,
37+
ulong clen,
38+
ReadOnlySpan<byte> mac,
39+
ReadOnlySpan<byte> ad,
40+
ulong adlen,
41+
ReadOnlySpan<byte> npub,
42+
SecureMemoryHandle k);
43+
3144
[LibraryImport(Libraries.Libsodium)]
3245
[UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
3346
internal static partial int crypto_aead_aes256gcm_encrypt(
@@ -41,6 +54,20 @@ internal static partial int crypto_aead_aes256gcm_encrypt(
4154
ReadOnlySpan<byte> npub,
4255
SecureMemoryHandle k);
4356

57+
[LibraryImport(Libraries.Libsodium)]
58+
[UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
59+
internal static partial int crypto_aead_aes256gcm_encrypt_detached(
60+
Span<byte> c,
61+
Span<byte> mac,
62+
out ulong maclen_p,
63+
ReadOnlySpan<byte> m,
64+
ulong mlen,
65+
ReadOnlySpan<byte> ad,
66+
ulong adlen,
67+
IntPtr nsec,
68+
ReadOnlySpan<byte> npub,
69+
SecureMemoryHandle k);
70+
4471
[LibraryImport(Libraries.Libsodium)]
4572
[UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
4673
internal static partial int crypto_aead_aes256gcm_is_available();

src/Interop/Interop.yaml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,9 @@ Interop.Aead.Aes256Gcm.cs:
4646
functions:
4747
- crypto_aead_aes256gcm_abytes
4848
- crypto_aead_aes256gcm_decrypt (out ulong mlen_p, IntPtr nsec, SecureMemoryHandle k)
49+
- crypto_aead_aes256gcm_decrypt_detached (IntPtr nsec, SecureMemoryHandle k)
4950
- crypto_aead_aes256gcm_encrypt (out ulong clen_p, IntPtr nsec, SecureMemoryHandle k)
51+
- crypto_aead_aes256gcm_encrypt_detached (out ulong maclen_p, IntPtr nsec, SecureMemoryHandle k)
5052
- crypto_aead_aes256gcm_is_available
5153
- crypto_aead_aes256gcm_keybytes
5254
- crypto_aead_aes256gcm_npubbytes

tests/Algorithms/Aes256GcmTests.cs

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,5 +52,31 @@ public static void EncryptDecrypt(int length)
5252
}
5353

5454
#endregion
55+
56+
#region Encrypt/Decrypt Detached
57+
58+
[Theory]
59+
[MemberData(nameof(PlaintextLengths))]
60+
public static void EncryptDecryptDetached(int length)
61+
{
62+
var a = AeadAlgorithm.Aes256Gcm;
63+
64+
using var k = new Key(a);
65+
var n = Utilities.RandomBytes[..a.NonceSize];
66+
var ad = Utilities.RandomBytes[..100];
67+
var c = new byte[length];
68+
var t = new byte[a.TagSize];
69+
70+
var expected = Utilities.RandomBytes[..length].ToArray();
71+
72+
a.EncryptDetached(k, n, ad, expected, c, t);
73+
74+
var actual = new byte[length];
75+
76+
Assert.True(a.DecryptDetached(k, n, ad, c, t, actual));
77+
Assert.Equal(expected, actual);
78+
}
79+
80+
#endregion
5581
}
5682
}

0 commit comments

Comments
 (0)