Added upgrade method to increase the number of iterations

This commit is contained in:
Thorsten Sommer 2020-01-05 20:03:52 +01:00
parent ed83878039
commit 8b117c5e6c
3 changed files with 82 additions and 7 deletions

View File

@ -128,5 +128,41 @@ namespace Encrypter_Tests
var decryptedMessage = await encryptedData.Decrypt(password); var decryptedMessage = await encryptedData.Decrypt(password);
Assert.That(decryptedMessage, Is.EqualTo(message)); 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);
}
}
} }
} }

View File

@ -9,7 +9,10 @@ namespace Encrypter
{ {
public static class CryptoProcessor 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> /// <summary>
/// Encrypts a string by means of AES. The result gets base64 encoded. /// Encrypts a string by means of AES. The result gets base64 encoded.
@ -20,8 +23,9 @@ namespace Encrypter
/// </summary> /// </summary>
/// <param name="data">The UTF8 encoded string to encrypt.</param> /// <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="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> /// <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) if (string.IsNullOrWhiteSpace(password) || password.Length < 6)
throw new CryptographicException("The password was empty or shorter than 6 characters."); throw new CryptographicException("The password was empty or shorter than 6 characters.");
@ -39,7 +43,7 @@ namespace Encrypter
// The following operations take several seconds. Thus, using a task: // The following operations take several seconds. Thus, using a task:
await Task.Run(() => 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 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 iv = keyVectorObj.GetBytes(16); // the only valid block size = 128 bit = 16 bytes
}); });
@ -93,8 +97,9 @@ namespace Encrypter
/// </summary> /// </summary>
/// <param name="base64EncodedAndEncryptedData">The base64 encoded and AES encrypted string. This string must be ASCII encoded.</param> /// <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="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> /// <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) if (string.IsNullOrWhiteSpace(password) || password.Length < 6)
throw new CryptographicException("The password was empty or shorter than 6 characters."); throw new CryptographicException("The password was empty or shorter than 6 characters.");
@ -121,7 +126,7 @@ namespace Encrypter
// The following operations take several seconds. Thus, using a task: // The following operations take several seconds. Thus, using a task:
await Task.Run(() => 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 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 iv = keyVectorObj.GetBytes(16); // the only valid block size = 128 bit = 16 bytes
}); });
@ -154,5 +159,22 @@ namespace Encrypter
// it does not create another copy of the data. ToArray would create another copy of the data! // 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]); 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);
}
} }
} }

View File

@ -4,7 +4,12 @@
<name>Encrypter</name> <name>Encrypter</name>
</assembly> </assembly>
<members> <members>
<member name="M:Encrypter.CryptoProcessor.EncryptString(System.String,System.String)"> <member name="F:Encrypter.CryptoProcessor.ITERATIONS_YEAR_2020">
<summary>
The number of iterations for the year 2020.
</summary>
</member>
<member name="M:Encrypter.CryptoProcessor.EncryptString(System.String,System.String,System.Int32)">
<summary> <summary>
Encrypts a string by means of AES. The result gets base64 encoded. Encrypts a string by means of AES. The result gets base64 encoded.
Due to the necessary millions of SHA512 iterations, the methods runs at least several seconds in the year 2020 (approx. 5-7s). Due to the necessary millions of SHA512 iterations, the methods runs at least several seconds in the year 2020 (approx. 5-7s).
@ -14,9 +19,10 @@
</summary> </summary>
<param name="data">The UTF8 encoded string to encrypt.</param> <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="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> <returns>The base64 encoded and encrypted string. The string is ASCII encoding.</returns>
</member> </member>
<member name="M:Encrypter.CryptoProcessor.DecryptString(System.String,System.String)"> <member name="M:Encrypter.CryptoProcessor.DecryptString(System.String,System.String,System.Int32)">
<summary> <summary>
Decrypts an base64 encoded and encrypted string. Due to the necessary millions of SHA512 iterations, Decrypts an base64 encoded and encrypted string. Due to the necessary millions of SHA512 iterations,
the methods runs at least several seconds in the year 2020 (approx. 5-7s). the methods runs at least several seconds in the year 2020 (approx. 5-7s).
@ -26,8 +32,19 @@
</summary> </summary>
<param name="base64EncodedAndEncryptedData">The base64 encoded and AES encrypted string. This string must be ASCII encoded.</param> <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="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> <returns>The decrypted UTF8 encoded string.</returns>
</member> </member>
<member name="M:Encrypter.CryptoProcessor.UpgradeIterations(System.String,System.String,System.Int32,System.Int32)">
<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>
</member>
<member name="M:Encrypter.Extensions.Encrypt(System.String,System.String)"> <member name="M:Encrypter.Extensions.Encrypt(System.String,System.String)">
<summary> <summary>
Encrypts this string by means of AES. The result gets base64 encoded. Encrypts this string by means of AES. The result gets base64 encoded.