Skip to content

Commit

Permalink
Aes256 public api has changed which causes breaking changes. Now inst…
Browse files Browse the repository at this point in the history
…ead of parameter of adding hash it is introduced totally new method
  • Loading branch information
HaikAsatryan committed Jun 13, 2024
1 parent 4dd04d5 commit 6851913
Show file tree
Hide file tree
Showing 4 changed files with 96 additions and 84 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));
}
}

0 comments on commit 6851913

Please sign in to comment.