Skip to content

Commit

Permalink
Merge pull request #20 from PandaTechAM/development
Browse files Browse the repository at this point in the history
Updated Aes256 class
  • Loading branch information
HaikAsatryan authored Jun 13, 2024
2 parents 2ff8ede + 6851913 commit a01ef67
Show file tree
Hide file tree
Showing 5 changed files with 99 additions and 87 deletions.
4 changes: 2 additions & 2 deletions Readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -101,8 +101,8 @@ string plainText = aes256.Decrypt(cipherText);
#### 1.4.2.3. Encryption/Decryption methods without hashing

```csharp
byte[] cipherText = aes256.Encrypt("your-plaintext", false);
string plainText = aes256.Decrypt(cipherText, false);
byte[] cipherText = aes256.EncryptWithout("your-plaintext");
string plainText = aes256.DecryptWithout(cipherText);
```

#### 1.4.2.4. Encryption/Decryption methods with custom key (overriding options for one time)
Expand Down
130 changes: 78 additions & 52 deletions src/Pandatech.Crypto/Aes256.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,34 +9,56 @@ public class Aes256(Aes256Options options)
private const int IvSize = 16;
private const int HashSize = 64;

public byte[] Encrypt(string? plainText, bool addHashToBytes = true)
public byte[] Encrypt(string plainText)
{
if (string.IsNullOrEmpty(plainText)) return [];
return addHashToBytes ? EncryptWithHash(plainText) : Encrypt(plainText);
return EncryptWithHashInner(plainText);
}

public byte[] Encrypt(string? plainText, string key, bool addHashToBytes = true)
public byte[] EncryptWithoutHash(string plainText)
{
return EncryptWithoutHashInner(plainText, null);
}

public byte[] Encrypt(string plainText, string key)
{
ValidateKey(key);
if (string.IsNullOrEmpty(plainText)) return [];
return addHashToBytes ? EncryptWithHash(plainText, key) : Encrypt(plainText, key);

return EncryptWithHashInner(plainText, key);
}

public string? Decrypt(byte[]? cipherText, bool includesHash = true)
public byte[] EncryptWithoutHash(string plainText, string key)
{
if (cipherText == null || cipherText.Length == 0) return "";
return includesHash ? DecryptIgnoringHash(cipherText) : Decrypt(cipherText);
ValidateKey(key);

return EncryptWithoutHashInner(plainText, key);
}

public string Decrypt(byte[] cipherText, string key, bool bytesIncludeHash = true)
public void Encrypt(Stream inputStream, Stream outputStream, string? key = null)
{
key ??= _options.Key;
ValidateKey(key);
if (cipherText.Length == 0) return "";
return bytesIncludeHash ? DecryptIgnoringHash(cipherText, key) : Decrypt(cipherText, key);
using var aesAlg = Aes.Create();
aesAlg.KeySize = KeySize;
aesAlg.Padding = PaddingMode.PKCS7;
aesAlg.Key = Convert.FromBase64String(key);
aesAlg.GenerateIV();

outputStream.Write(aesAlg.IV, 0, aesAlg.IV.Length);

using var encryptor = aesAlg.CreateEncryptor(aesAlg.Key, aesAlg.IV);
using var cryptoStream = new CryptoStream(outputStream, encryptor, CryptoStreamMode.Write, leaveOpen: true);
inputStream.CopyTo(cryptoStream);
}

private byte[] EncryptWithHashInner(string plainText, string? key = null)
{
key ??= _options.Key;
var encryptedBytes = EncryptWithoutHashInner(plainText, key);
var hashBytes = Sha3.Hash(plainText);
return hashBytes.Concat(encryptedBytes).ToArray();
}

private byte[] Encrypt(string plainText, string? key)
private byte[] EncryptWithoutHashInner(string plainText, string? key)
{
key ??= _options.Key;
ValidateText(plainText);
Expand All @@ -59,26 +81,58 @@ private byte[] Encrypt(string plainText, string? key)
var result = aesAlg.IV.Concat(encryptedPasswordByte).ToArray();
return result;
}

public string Decrypt(byte[] cipherText)
{
return cipherText.Length == 0
? ""
: DecryptSkippingHashInner(cipherText);
}

public string DecryptWithoutHash(byte[] cipherText)
{
return cipherText.Length == 0
? ""
: DecryptWithoutSkippingHashInner(cipherText, null);
}

public string Decrypt(byte[] cipherText, string key)
{
ValidateKey(key);
return cipherText.Length == 0
? ""
: DecryptSkippingHashInner(cipherText, key);
}

public string DecryptWithoutHash(byte[] cipherText, string key)
{
ValidateKey(key);
return cipherText.Length == 0
? ""
: DecryptWithoutSkippingHashInner(cipherText, key);
}

public void EncryptStream(Stream inputStream, Stream outputStream, string? key = null)
public void Decrypt(Stream inputStream, Stream outputStream, string? key = null)
{
key ??= _options.Key;
ValidateKey(key);

var iv = new byte[IvSize];
if (inputStream.Read(iv, 0, IvSize) != IvSize)
throw new ArgumentException("Input stream does not contain a complete IV.");

using var aesAlg = Aes.Create();
aesAlg.KeySize = KeySize;
aesAlg.Padding = PaddingMode.PKCS7;
aesAlg.Key = Convert.FromBase64String(key);
aesAlg.GenerateIV();

outputStream.Write(aesAlg.IV, 0, aesAlg.IV.Length);
aesAlg.IV = iv;

using var encryptor = aesAlg.CreateEncryptor(aesAlg.Key, aesAlg.IV);
using var cryptoStream = new CryptoStream(outputStream, encryptor, CryptoStreamMode.Write, leaveOpen: true);
inputStream.CopyTo(cryptoStream);
using var decryptor = aesAlg.CreateDecryptor(aesAlg.Key, aesAlg.IV);
using var cryptoStream = new CryptoStream(inputStream, decryptor, CryptoStreamMode.Read, leaveOpen: true);
cryptoStream.CopyTo(outputStream);
}


private string Decrypt(byte[] cipherText, string? key)
private string DecryptWithoutSkippingHashInner(byte[] cipherText, string? key)
{
key ??= _options.Key;
ValidateCipherText(cipherText);
Expand All @@ -99,39 +153,11 @@ private string Decrypt(byte[] cipherText, string? key)
return srDecrypt.ReadToEnd();
}

private byte[] EncryptWithHash(string plainText, string? key = null)
{
key ??= _options.Key;
var encryptedBytes = Encrypt(plainText, key);
var hashBytes = Sha3.Hash(plainText);
return hashBytes.Concat(encryptedBytes).ToArray();
}

private string DecryptIgnoringHash(IEnumerable<byte> cipherTextWithHash, string? key = null)
private string DecryptSkippingHashInner(IEnumerable<byte> cipherTextWithHash, string? key = null)
{
key ??= _options.Key;
var cipherText = cipherTextWithHash.Skip(HashSize).ToArray();
return Decrypt(cipherText, key);
}

public void DecryptStream(Stream inputStream, Stream outputStream, string? key = null)
{
key ??= _options.Key;
ValidateKey(key);

var iv = new byte[IvSize];
if (inputStream.Read(iv, 0, IvSize) != IvSize)
throw new ArgumentException("Input stream does not contain a complete IV.");

using var aesAlg = Aes.Create();
aesAlg.KeySize = KeySize;
aesAlg.Padding = PaddingMode.PKCS7;
aesAlg.Key = Convert.FromBase64String(key);
aesAlg.IV = iv;

using var decryptor = aesAlg.CreateDecryptor(aesAlg.Key, aesAlg.IV);
using var cryptoStream = new CryptoStream(inputStream, decryptor, CryptoStreamMode.Read, leaveOpen: true);
cryptoStream.CopyTo(outputStream);
return DecryptWithoutSkippingHashInner(cipherText, key);
}

private static void ValidateKey(string key)
Expand Down
4 changes: 2 additions & 2 deletions src/Pandatech.Crypto/Pandatech.Crypto.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,12 @@
<Copyright>MIT</Copyright>
<PackageIcon>pandatech.png</PackageIcon>
<PackageReadmeFile>Readme.md</PackageReadmeFile>
<Version>2.3.2</Version>
<Version>2.4.0</Version>
<Title>Pandatech.Crypto</Title>
<PackageTags>Pandatech, library, encryption, hash, algorythms, security</PackageTags>
<Description>PandaTech.Crypto is a .NET library simplifying common cryptograhic functions.</Description>
<RepositoryUrl>https://github.com/PandaTechAM/be-lib-pandatech-crypto</RepositoryUrl>
<PackageReleaseNotes>Zip and Aes stream overload</PackageReleaseNotes>
<PackageReleaseNotes>Aes256 public api has changed which causes breaking changes. Now instead of parameter of adding hash it is introduced totally new method</PackageReleaseNotes>
</PropertyGroup>

<ItemGroup>
Expand Down
42 changes: 14 additions & 28 deletions test/Pandatech.Crypto.Tests/Aes256Tests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,33 +5,32 @@ namespace Pandatech.Crypto.Tests;
public class Aes256Tests
{
[Fact]
public void EncryptDecryptWithParameter_ShouldReturnOriginalString()
public void EncryptDecryptWithHash_ShouldReturnOriginalString()
{
var aes256 = new Aes256(new Aes256Options());

var key = Random.GenerateAes256KeyString();
const string original = "MySensitiveData";
var encrypted = aes256.Encrypt(original, key, false);
var decrypted = aes256.Decrypt(encrypted, key, false);
var encrypted = aes256.Encrypt(original, key);
var decrypted = aes256.Decrypt(encrypted, key);

Assert.Equal(original, decrypted);
}

[Fact]
public void EncryptDecryptWithoutParameter_ShouldReturnOriginalString()
public void EncryptDecryptWithoutHash_ShouldReturnOriginalString()
{
var aes256Options = new Aes256Options { Key = Random.GenerateAes256KeyString() };
var aes256 = new Aes256(aes256Options);
Environment.SetEnvironmentVariable("AES_KEY", Random.GenerateAes256KeyString());
const string original = "MySensitiveData";
var encrypted = aes256.Encrypt(original, false);
var decrypted = aes256.Decrypt(encrypted, false);
var encrypted = aes256.EncryptWithoutHash(original);
var decrypted = aes256.DecryptWithoutHash(encrypted);

Assert.Equal(original, decrypted);
}

[Fact]
public void EncryptWithParameterAndHash_ShouldReturnByteArrayWithHash()
public void EncryptWithHash_ShouldReturnByteArrayWithHash()
{
var aes256 = new Aes256(new Aes256Options());
var key = Random.GenerateAes256KeyString();
Expand All @@ -44,11 +43,10 @@ public void EncryptWithParameterAndHash_ShouldReturnByteArrayWithHash()
}

[Fact]
public void EncryptWithoutParameterAndHash_ShouldReturnByteArrayWithHash()
public void EncryptAndHash_ShouldReturnByteArrayWithHash()
{
var aes256Options = new Aes256Options { Key = Random.GenerateAes256KeyString() };
var aes256 = new Aes256(aes256Options);
Environment.SetEnvironmentVariable("AES_KEY", Random.GenerateAes256KeyString());
const string original = "MySensitiveData";
var encryptedWithHash = aes256.Encrypt(original);

Expand All @@ -74,7 +72,6 @@ public void DecryptWithoutParameterAndIgnoringHash_ShouldReturnOriginalString()
{
var aes256Options = new Aes256Options { Key = Random.GenerateAes256KeyString() };
var aes256 = new Aes256(aes256Options);
Environment.SetEnvironmentVariable("AES_KEY", Random.GenerateAes256KeyString());
const string original = "MySensitiveData";
var encryptedWithHash = aes256.Encrypt(original);
var decrypted = aes256.Decrypt(encryptedWithHash);
Expand Down Expand Up @@ -113,17 +110,6 @@ public void EncryptDecryptWithShortKey_ShouldThrowException()
Assert.Throws<ArgumentException>(() => aes256.Decrypt([], shortKey));
}

[Fact]
public void EncryptDecryptWithEmptyText_ShouldReturnEmptyString()
{
var aes256 = new Aes256(new Aes256Options());
var key = Random.GenerateAes256KeyString();
var original = string.Empty;
var encrypted = aes256.Encrypt(original, key);
var decrypted = aes256.Decrypt(encrypted, key);
Assert.Equal(original, decrypted);
}

[Fact]
public void EncryptDecryptWithNullCipher_ShouldReturnEmptyString()
{
Expand Down Expand Up @@ -159,11 +145,11 @@ public void EncryptDecryptStream_ShouldReturnOriginalData()
var outputStream = new MemoryStream();

// Act
aes256.EncryptStream(inputStream, outputStream, aes256Options.Key);
aes256.Encrypt(inputStream, outputStream, aes256Options.Key);
outputStream.Seek(0, SeekOrigin.Begin);

var resultStream = new MemoryStream();
aes256.DecryptStream(outputStream, resultStream, aes256Options.Key);
aes256.Decrypt(outputStream, resultStream, aes256Options.Key);
resultStream.Seek(0, SeekOrigin.Begin);
var decryptedData = new StreamReader(resultStream).ReadToEnd();

Expand All @@ -181,11 +167,11 @@ public void EncryptDecryptStreamWithEmptyContent_ShouldHandleGracefully()
var outputStream = new MemoryStream();

// Act
aes256.EncryptStream(inputStream, outputStream, aes256Options.Key);
aes256.Encrypt(inputStream, outputStream, aes256Options.Key);
outputStream.Seek(0, SeekOrigin.Begin); // Reset the position for reading.

var resultStream = new MemoryStream();
aes256.DecryptStream(outputStream, resultStream, aes256Options.Key);
aes256.Decrypt(outputStream, resultStream, aes256Options.Key);
resultStream.Seek(0, SeekOrigin.Begin);
var decryptedData = new StreamReader(resultStream).ReadToEnd();

Expand All @@ -203,7 +189,7 @@ public void EncryptStreamWithInvalidKey_ShouldThrowException()
var outputStream = new MemoryStream();

// Act & Assert
Assert.Throws<ArgumentException>(() => aes256.EncryptStream(inputStream, outputStream, invalidKey));
Assert.Throws<ArgumentException>(() => aes256.Encrypt(inputStream, outputStream, invalidKey));
}

[Fact]
Expand All @@ -216,6 +202,6 @@ public void DecryptStreamWithInvalidKey_ShouldThrowException()
var outputStream = new MemoryStream();

// Act & Assert
Assert.Throws<ArgumentException>(() => aes256.DecryptStream(inputStream, outputStream, invalidKey));
Assert.Throws<ArgumentException>(() => aes256.Decrypt(inputStream, outputStream, invalidKey));
}
}
6 changes: 3 additions & 3 deletions test/Pandatech.Crypto.Tests/Pandatech.Crypto.Tests.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,9 @@

<ItemGroup>
<PackageReference Include="FluentAssertions" Version="6.12.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.9.0" />
<PackageReference Include="xunit" Version="2.8.0" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.8.0">
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.10.0" />
<PackageReference Include="xunit" Version="2.8.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.8.1">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
Expand Down

0 comments on commit a01ef67

Please sign in to comment.