feat: implement AEAD encryption using AES api
This commit is contained in:
+54
-7
@@ -1,20 +1,67 @@
|
|||||||
|
using System.Security.Cryptography;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
namespace StevanFreeborn.Extensions.Configuration.Secure.Cryptography;
|
namespace StevanFreeborn.Extensions.Configuration.Secure.Cryptography;
|
||||||
|
|
||||||
internal sealed class AesCryptoProvider(IEncryptionKeyProvider keyProvider) : ICryptoProvider
|
internal sealed class AesCryptoProvider(IEncryptionKeyProvider keyProvider) : ICryptoProvider
|
||||||
{
|
{
|
||||||
|
private const int NonceSize = 12;
|
||||||
|
private const int TagSize = 16;
|
||||||
|
|
||||||
private readonly IEncryptionKeyProvider _keyProvider = keyProvider
|
private readonly IEncryptionKeyProvider _keyProvider = keyProvider
|
||||||
?? throw new ArgumentNullException(nameof(keyProvider));
|
?? throw new ArgumentNullException(nameof(keyProvider));
|
||||||
|
|
||||||
// TODO: Implement this shit
|
public string Encrypt(string plainText)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(plainText))
|
||||||
|
{
|
||||||
|
return plainText;
|
||||||
|
}
|
||||||
|
|
||||||
|
var key = _keyProvider.GetKey();
|
||||||
|
var plainBytes = Encoding.UTF8.GetBytes(plainText);
|
||||||
|
|
||||||
|
var nonce = new byte[NonceSize].AsSpan();
|
||||||
|
RandomNumberGenerator.Fill(nonce);
|
||||||
|
|
||||||
|
var tag = new byte[TagSize].AsSpan();
|
||||||
|
|
||||||
|
var cipherBytes = new byte[plainBytes.Length];
|
||||||
|
|
||||||
|
using var aesGcm = new AesGcm(key);
|
||||||
|
aesGcm.Encrypt(nonce, plainBytes, cipherBytes, tag);
|
||||||
|
|
||||||
|
var combinedBytes = new byte[NonceSize + TagSize + plainBytes.Length];
|
||||||
|
nonce.CopyTo(combinedBytes.AsSpan(0, NonceSize));
|
||||||
|
tag.CopyTo(combinedBytes.AsSpan(NonceSize, TagSize));
|
||||||
|
cipherBytes.CopyTo(combinedBytes.AsSpan(NonceSize + TagSize));
|
||||||
|
|
||||||
|
return Convert.ToBase64String(combinedBytes);
|
||||||
|
}
|
||||||
|
|
||||||
public string Decrypt(string cipherText)
|
public string Decrypt(string cipherText)
|
||||||
{
|
{
|
||||||
_ = _keyProvider.GetKey();
|
if (string.IsNullOrWhiteSpace(cipherText))
|
||||||
throw new NotImplementedException();
|
{
|
||||||
}
|
return cipherText;
|
||||||
|
}
|
||||||
|
|
||||||
public string Encrypt(string plainText)
|
var key = _keyProvider.GetKey();
|
||||||
{
|
var combinedBytes = Convert.FromBase64String(cipherText).AsSpan();
|
||||||
throw new NotImplementedException();
|
|
||||||
|
if (combinedBytes.Length < NonceSize + TagSize)
|
||||||
|
{
|
||||||
|
throw new CryptographicException($"Invalid playload. {nameof(cipherText)} is not of expected length");
|
||||||
|
}
|
||||||
|
|
||||||
|
var nonce = combinedBytes[..NonceSize];
|
||||||
|
var tag = combinedBytes.Slice(NonceSize, TagSize);
|
||||||
|
var cipherBytes = combinedBytes[(NonceSize + TagSize)..];
|
||||||
|
var plainBytes = new byte[cipherBytes.Length];
|
||||||
|
|
||||||
|
using var aesGcm = new AesGcm(key);
|
||||||
|
aesGcm.Decrypt(nonce, cipherBytes, tag, plainBytes);
|
||||||
|
|
||||||
|
return Encoding.UTF8.GetString(plainBytes);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+31
@@ -1,3 +1,5 @@
|
|||||||
|
using System.Security.Cryptography;
|
||||||
|
|
||||||
using Moq;
|
using Moq;
|
||||||
|
|
||||||
using StevanFreeborn.Extensions.Configuration.Secure.Cryptography;
|
using StevanFreeborn.Extensions.Configuration.Secure.Cryptography;
|
||||||
@@ -11,6 +13,10 @@ public class AesCryptoProviderTests
|
|||||||
|
|
||||||
public AesCryptoProviderTests()
|
public AesCryptoProviderTests()
|
||||||
{
|
{
|
||||||
|
var key = new byte[32];
|
||||||
|
RandomNumberGenerator.Fill(key);
|
||||||
|
_mockKeyProvider.Setup(static m => m.GetKey()).Returns(key);
|
||||||
|
|
||||||
_sut = new(_mockKeyProvider.Object);
|
_sut = new(_mockKeyProvider.Object);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -25,4 +31,29 @@ public class AesCryptoProviderTests
|
|||||||
encryptedText.Should().NotBe(originalText);
|
encryptedText.Should().NotBe(originalText);
|
||||||
decryptedText.Should().Be(originalText);
|
decryptedText.Should().Be(originalText);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Encrypt_WhenCalledWithSameInputTwitch_ItShouldProduceDifferentCipherText()
|
||||||
|
{
|
||||||
|
var plainText = "identicalInput";
|
||||||
|
|
||||||
|
var cipherTextOne = _sut.Encrypt(plainText);
|
||||||
|
var cipherTextTwo = _sut.Encrypt(plainText);
|
||||||
|
|
||||||
|
cipherTextOne.Should().NotBe(cipherTextTwo);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Decrypt_WhenCalledWithTamperedData_ItShouldThrowCrytographicException()
|
||||||
|
{
|
||||||
|
var cipherText = _sut.Encrypt("some data");
|
||||||
|
var rawBytes = Convert.FromBase64String(cipherText);
|
||||||
|
rawBytes[^1] = (byte)(rawBytes[^1] ^ 0xFF);
|
||||||
|
|
||||||
|
var tamperedText = Convert.ToBase64String(rawBytes);
|
||||||
|
|
||||||
|
var act = () => _sut.Decrypt(tamperedText);
|
||||||
|
|
||||||
|
act.Should().Throw<CryptographicException>();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user