From 8b117c5e6cf74654ce81ae5de315f5292ffe34ae Mon Sep 17 00:00:00 2001 From: Thorsten Sommer Date: Sun, 5 Jan 2020 20:03:52 +0100 Subject: [PATCH] Added upgrade method to increase the number of iterations --- Encrypter Tests/EncrypterTests.cs | 36 +++++++++++++++++++++++++++++++ Encrypter/CryptoProcessor.cs | 32 ++++++++++++++++++++++----- Encrypter/Encrypter.xml | 21 ++++++++++++++++-- 3 files changed, 82 insertions(+), 7 deletions(-) diff --git a/Encrypter Tests/EncrypterTests.cs b/Encrypter Tests/EncrypterTests.cs index b6874e2..27e4fbf 100644 --- a/Encrypter Tests/EncrypterTests.cs +++ b/Encrypter Tests/EncrypterTests.cs @@ -128,5 +128,41 @@ namespace Encrypter_Tests 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); + } + } } } diff --git a/Encrypter/CryptoProcessor.cs b/Encrypter/CryptoProcessor.cs index 8d20ed9..6f9523e 100644 --- a/Encrypter/CryptoProcessor.cs +++ b/Encrypter/CryptoProcessor.cs @@ -9,7 +9,10 @@ namespace Encrypter { public static class CryptoProcessor { - private const int ITERATIONS = 6_000_000; + /// + /// The number of iterations for the year 2020. + /// + public const int ITERATIONS_YEAR_2020 = 6_000_000; /// /// Encrypts a string by means of AES. The result gets base64 encoded. @@ -20,8 +23,9 @@ namespace Encrypter /// /// The UTF8 encoded string to encrypt. /// The password. Must consists of 6 chars or more. + /// The number of iterations to derive the key. Should not be adjusted. The default is secure for the current time. /// The base64 encoded and encrypted string. The string is ASCII encoding. - public static async Task EncryptString(string data, string password) + public static async Task 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."); @@ -39,7 +43,7 @@ namespace Encrypter // 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 }); @@ -93,8 +97,9 @@ namespace Encrypter /// /// The base64 encoded and AES encrypted string. This string must be ASCII encoded. /// The password. Must consists of 6 chars or more. + /// The number of iterations to derive the key. Should not be adjusted. The default is secure for the current time. /// The decrypted UTF8 encoded string. - public static async Task DecryptString(string base64EncodedAndEncryptedData, string password) + public static async Task 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."); @@ -121,7 +126,7 @@ namespace Encrypter // 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 }); @@ -154,5 +159,22 @@ namespace Encrypter // 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]); } + + /// + /// Upgrades the encryption regarding the used iterations for the key. + /// + /// The encrypted data with the previous settings. + /// The password. + /// The previous number of iterations. + /// The upgraded number of iterations. + /// The re-encrypted data. + public static async Task 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); + } } } diff --git a/Encrypter/Encrypter.xml b/Encrypter/Encrypter.xml index b5dfcbd..6c708f1 100644 --- a/Encrypter/Encrypter.xml +++ b/Encrypter/Encrypter.xml @@ -4,7 +4,12 @@ Encrypter - + + + The number of iterations for the year 2020. + + + 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). @@ -14,9 +19,10 @@ The UTF8 encoded string to encrypt. The password. Must consists of 6 chars or more. + The number of iterations to derive the key. Should not be adjusted. The default is secure for the current time. The base64 encoded and encrypted string. The string is ASCII encoding. - + 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). @@ -26,8 +32,19 @@ The base64 encoded and AES encrypted string. This string must be ASCII encoded. The password. Must consists of 6 chars or more. + The number of iterations to derive the key. Should not be adjusted. The default is secure for the current time. The decrypted UTF8 encoded string. + + + Upgrades the encryption regarding the used iterations for the key. + + The encrypted data with the previous settings. + The password. + The previous number of iterations. + The upgraded number of iterations. + The re-encrypted data. + Encrypts this string by means of AES. The result gets base64 encoded.