// Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; using System.Linq; using System.Security.Cryptography; using System.Text; using Microsoft.AspNetCore.Cryptography.Cng; using Microsoft.AspNetCore.DataProtection.Test.Shared; using Microsoft.AspNetCore.Testing.xunit; using Xunit; namespace Microsoft.AspNetCore.DataProtection.Cng { public class GcmAuthenticatedEncryptorTests { [ConditionalFact] [ConditionalRunTestOnlyOnWindows] public void Encrypt_Decrypt_RoundTrips() { // Arrange Secret kdk = new Secret(new byte[512 / 8]); GcmAuthenticatedEncryptor encryptor = new GcmAuthenticatedEncryptor(kdk, CachedAlgorithmHandles.AES_GCM, symmetricAlgorithmKeySizeInBytes: 256 / 8); ArraySegment plaintext = new ArraySegment(Encoding.UTF8.GetBytes("plaintext")); ArraySegment aad = new ArraySegment(Encoding.UTF8.GetBytes("aad")); // Act byte[] ciphertext = encryptor.Encrypt(plaintext, aad); byte[] decipheredtext = encryptor.Decrypt(new ArraySegment(ciphertext), aad); // Assert Assert.Equal(plaintext, decipheredtext); } [ConditionalFact] [ConditionalRunTestOnlyOnWindows] public void Encrypt_Decrypt_Tampering_Fails() { // Arrange Secret kdk = new Secret(new byte[512 / 8]); GcmAuthenticatedEncryptor encryptor = new GcmAuthenticatedEncryptor(kdk, CachedAlgorithmHandles.AES_GCM, symmetricAlgorithmKeySizeInBytes: 256 / 8); ArraySegment plaintext = new ArraySegment(Encoding.UTF8.GetBytes("plaintext")); ArraySegment aad = new ArraySegment(Encoding.UTF8.GetBytes("aad")); byte[] validCiphertext = encryptor.Encrypt(plaintext, aad); // Act & assert - 1 // Ciphertext is too short to be a valid payload byte[] invalidCiphertext_tooShort = new byte[10]; Assert.Throws(() => { encryptor.Decrypt(new ArraySegment(invalidCiphertext_tooShort), aad); }); // Act & assert - 2 // Ciphertext has been manipulated byte[] invalidCiphertext_manipulated = (byte[])validCiphertext.Clone(); invalidCiphertext_manipulated[0] ^= 0x01; Assert.Throws(() => { encryptor.Decrypt(new ArraySegment(invalidCiphertext_manipulated), aad); }); // Act & assert - 3 // Ciphertext is too long byte[] invalidCiphertext_tooLong = validCiphertext.Concat(new byte[] { 0 }).ToArray(); Assert.Throws(() => { encryptor.Decrypt(new ArraySegment(invalidCiphertext_tooLong), aad); }); // Act & assert - 4 // AAD is incorrect Assert.Throws(() => { encryptor.Decrypt(new ArraySegment(validCiphertext), new ArraySegment(Encoding.UTF8.GetBytes("different aad"))); }); } [ConditionalFact] [ConditionalRunTestOnlyOnWindows] public void Encrypt_KnownKey() { // Arrange Secret kdk = new Secret(Encoding.UTF8.GetBytes("master key")); GcmAuthenticatedEncryptor encryptor = new GcmAuthenticatedEncryptor(kdk, CachedAlgorithmHandles.AES_GCM, symmetricAlgorithmKeySizeInBytes: 128 / 8, genRandom: new SequentialGenRandom()); ArraySegment plaintext = new ArraySegment(new byte[] { 0, 1, 2, 3, 4, 5, 6, 7 }, 2, 3); ArraySegment aad = new ArraySegment(new byte[] { 7, 6, 5, 4, 3, 2, 1, 0 }, 1, 4); // Act byte[] retVal = encryptor.Encrypt( plaintext: plaintext, additionalAuthenticatedData: aad, preBufferSize: 3, postBufferSize: 4); // Assert // retVal := 00 00 00 (preBuffer) // | 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F (keyModifier) // | 10 11 12 13 14 15 16 17 18 19 1A 1B (nonce) // | 43 B6 91 (encryptedData) // | 8D 0D 66 D9 A1 D9 44 2D 5D 8E 41 DA 39 60 9C E8 (authTag) // | 00 00 00 00 (postBuffer) string retValAsString = Convert.ToBase64String(retVal); Assert.Equal("AAAAAAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaG0O2kY0NZtmh2UQtXY5B2jlgnOgAAAAA", retValAsString); } } }