Skip to content

Commit

Permalink
Added upgrade method to increase the number of iterations
Browse files Browse the repository at this point in the history
  • Loading branch information
SommerEngineering committed Jan 5, 2020
1 parent ed83878 commit 8b117c5
Show file tree
Hide file tree
Showing 3 changed files with 82 additions and 7 deletions.
36 changes: 36 additions & 0 deletions Encrypter Tests/EncrypterTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -128,5 +128,41 @@ public async Task TestSimpleExtensionMethods()
var decryptedMessage = await encryptedData.Decrypt(password);
Assert.That(decryptedMessage, Is.EqualTo(message));
}

[Test]
public async Task TestUpgradedIterationsBehaviour()
{
var message = "This is a test with umlauts äüö.";
var password = "test password";
var previousIterations = 1_000;
var upgradedIterations = 1_000_000;

var previousEncryptedData = await CryptoProcessor.EncryptString(message, password, previousIterations);
var reEncryptedData = await CryptoProcessor.UpgradeIterations(previousEncryptedData, password, previousIterations, upgradedIterations);
Assert.That(previousEncryptedData, Is.Not.EqualTo(reEncryptedData));

var decryptedMessage = await CryptoProcessor.DecryptString(reEncryptedData, password, upgradedIterations);
Assert.That(decryptedMessage, Is.EqualTo(message));

try
{
var decryptedMessage2 = await CryptoProcessor.DecryptString(reEncryptedData, password, previousIterations);
Assert.Fail("Should not be reached!");
}
catch (CryptographicException e)
{
Assert.That(true);
}

try
{
var decryptedMessage2 = await CryptoProcessor.DecryptString(previousEncryptedData, password, upgradedIterations);
Assert.Fail("Should not be reached!");
}
catch (CryptographicException e)
{
Assert.That(true);
}
}
}
}
32 changes: 27 additions & 5 deletions Encrypter/CryptoProcessor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,10 @@ namespace Encrypter
{
public static class CryptoProcessor
{
private const int ITERATIONS = 6_000_000;
/// <summary>
/// The number of iterations for the year 2020.
/// </summary>
public const int ITERATIONS_YEAR_2020 = 6_000_000;

/// <summary>
/// Encrypts a string by means of AES. The result gets base64 encoded.
Expand All @@ -20,8 +23,9 @@ public static class CryptoProcessor
/// </summary>
/// <param name="data">The UTF8 encoded string to encrypt.</param>
/// <param name="password">The password. Must consists of 6 chars or more.</param>
/// <param name="iterations">The number of iterations to derive the key. Should not be adjusted. The default is secure for the current time.</param>
/// <returns>The base64 encoded and encrypted string. The string is ASCII encoding.</returns>
public static async Task<string> EncryptString(string data, string password)
public static async Task<string> EncryptString(string data, string password, int iterations = ITERATIONS_YEAR_2020)
{
if (string.IsNullOrWhiteSpace(password) || password.Length < 6)
throw new CryptographicException("The password was empty or shorter than 6 characters.");
Expand All @@ -39,7 +43,7 @@ public static async Task<string> EncryptString(string data, string password)
// The following operations take several seconds. Thus, using a task:
await Task.Run(() =>
{
using var keyVectorObj = new Rfc2898DeriveBytes(password, saltBytes, ITERATIONS, HashAlgorithmName.SHA512);
using var keyVectorObj = new Rfc2898DeriveBytes(password, saltBytes, iterations, HashAlgorithmName.SHA512);
key = keyVectorObj.GetBytes(32); // the max valid key length = 256 bit = 32 bytes
iv = keyVectorObj.GetBytes(16); // the only valid block size = 128 bit = 16 bytes
});
Expand Down Expand Up @@ -93,8 +97,9 @@ await Task.Run(() =>
/// </summary>
/// <param name="base64EncodedAndEncryptedData">The base64 encoded and AES encrypted string. This string must be ASCII encoded.</param>
/// <param name="password">The password. Must consists of 6 chars or more.</param>
/// <param name="iterations">The number of iterations to derive the key. Should not be adjusted. The default is secure for the current time.</param>
/// <returns>The decrypted UTF8 encoded string.</returns>
public static async Task<string> DecryptString(string base64EncodedAndEncryptedData, string password)
public static async Task<string> DecryptString(string base64EncodedAndEncryptedData, string password, int iterations = ITERATIONS_YEAR_2020)
{
if (string.IsNullOrWhiteSpace(password) || password.Length < 6)
throw new CryptographicException("The password was empty or shorter than 6 characters.");
Expand All @@ -121,7 +126,7 @@ public static async Task<string> DecryptString(string base64EncodedAndEncryptedD
// The following operations take several seconds. Thus, using a task:
await Task.Run(() =>
{
using var keyVectorObj = new Rfc2898DeriveBytes(password, saltBytes, ITERATIONS, HashAlgorithmName.SHA512);
using var keyVectorObj = new Rfc2898DeriveBytes(password, saltBytes, iterations, HashAlgorithmName.SHA512);
key = keyVectorObj.GetBytes(32); // the max valid key length = 256 bit = 32 bytes
iv = keyVectorObj.GetBytes(16); // the only valid block size = 128 bit = 16 bytes
});
Expand Down Expand Up @@ -154,5 +159,22 @@ await Task.Run(() =>
// it does not create another copy of the data. ToArray would create another copy of the data!
return Encoding.UTF8.GetString(decryptedData.GetBuffer()[..(int)decryptedData.Length]);
}

/// <summary>
/// Upgrades the encryption regarding the used iterations for the key.
/// </summary>
/// <param name="encryptedDataBeforeUpgrade">The encrypted data with the previous settings.</param>
/// <param name="password">The password.</param>
/// <param name="previousIterations">The previous number of iterations.</param>
/// <param name="upgradedIterations">The upgraded number of iterations.</param>
/// <returns>The re-encrypted data.</returns>
public static async Task<string> UpgradeIterations(string encryptedDataBeforeUpgrade, string password, int previousIterations, int upgradedIterations)
{
// Decrypt the data with the previous settings:
var decryptedData = await CryptoProcessor.DecryptString(encryptedDataBeforeUpgrade, password, previousIterations);

// Encrypt the data with the new settings:
return await CryptoProcessor.EncryptString(decryptedData, password, upgradedIterations);
}
}
}
21 changes: 19 additions & 2 deletions Encrypter/Encrypter.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit 8b117c5

Please sign in to comment.