feat: implement AEAD encryption using AES api
This commit is contained in:
+52
-5
@@ -1,20 +1,67 @@
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
|
||||
namespace StevanFreeborn.Extensions.Configuration.Secure.Cryptography;
|
||||
|
||||
internal sealed class AesCryptoProvider(IEncryptionKeyProvider keyProvider) : ICryptoProvider
|
||||
{
|
||||
private const int NonceSize = 12;
|
||||
private const int TagSize = 16;
|
||||
|
||||
private readonly IEncryptionKeyProvider _keyProvider = 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)
|
||||
{
|
||||
_ = _keyProvider.GetKey();
|
||||
throw new NotImplementedException();
|
||||
if (string.IsNullOrWhiteSpace(cipherText))
|
||||
{
|
||||
return cipherText;
|
||||
}
|
||||
|
||||
public string Encrypt(string plainText)
|
||||
var key = _keyProvider.GetKey();
|
||||
var combinedBytes = Convert.FromBase64String(cipherText).AsSpan();
|
||||
|
||||
if (combinedBytes.Length < NonceSize + TagSize)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
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 StevanFreeborn.Extensions.Configuration.Secure.Cryptography;
|
||||
@@ -11,6 +13,10 @@ public class AesCryptoProviderTests
|
||||
|
||||
public AesCryptoProviderTests()
|
||||
{
|
||||
var key = new byte[32];
|
||||
RandomNumberGenerator.Fill(key);
|
||||
_mockKeyProvider.Setup(static m => m.GetKey()).Returns(key);
|
||||
|
||||
_sut = new(_mockKeyProvider.Object);
|
||||
}
|
||||
|
||||
@@ -25,4 +31,29 @@ public class AesCryptoProviderTests
|
||||
encryptedText.Should().NotBe(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