diff --git a/Encrypter Tests/Encrypter Tests.csproj b/Encrypter Tests/Encrypter Tests.csproj index 5680f21..072f091 100644 --- a/Encrypter Tests/Encrypter Tests.csproj +++ b/Encrypter Tests/Encrypter Tests.csproj @@ -10,7 +10,11 @@ - + + + + + diff --git a/Encrypter Tests/EncrypterTests.cs b/Encrypter Tests/EncrypterTests.cs new file mode 100644 index 0000000..bc1d3b8 --- /dev/null +++ b/Encrypter Tests/EncrypterTests.cs @@ -0,0 +1,25 @@ +using System; +using System.Collections.Generic; +using System.Text; +using System.Threading.Tasks; +using Encrypter; +using NUnit.Framework; + +namespace Encrypter_Tests +{ + public sealed class EncrypterTests + { + [Test] + public async Task TestSimpleEnAndDecryption() + { + var message = "This is a test with umlauts äüö."; + var password = "test password"; + + var encryptedData = await CryptoProcessor.EncryptString(message, password); + Assert.That(encryptedData.Length, Is.AtLeast(message.Length)); // Note: Encrypted data contains salt as well! + + var decryptedMessage = await CryptoProcessor.DecryptString(encryptedData, password); + Assert.That(decryptedMessage, Is.EqualTo(message)); + } + } +} diff --git a/Encrypter/CryptoProcessor.cs b/Encrypter/CryptoProcessor.cs new file mode 100644 index 0000000..8d20ed9 --- /dev/null +++ b/Encrypter/CryptoProcessor.cs @@ -0,0 +1,158 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Security.Cryptography; +using System.Text; +using System.Threading.Tasks; + +namespace Encrypter +{ + public static class CryptoProcessor + { + private const int ITERATIONS = 6_000_000; + + /// + /// 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). + /// This method suits for small data such as telegrams, JSON data, text notes, passwords, etc. For larger + /// data, might use the stream overload. Rule of thumb: If the data could be stored three times in + /// the present memory, this method could be used. + /// + /// The UTF8 encoded string to encrypt. + /// The password. Must consists of 6 chars or more. + /// The base64 encoded and encrypted string. The string is ASCII encoding. + public static async Task EncryptString(string data, string password) + { + if (string.IsNullOrWhiteSpace(password) || password.Length < 6) + throw new CryptographicException("The password was empty or shorter than 6 characters."); + + if(data == null) + throw new CryptographicException("The data cannot be null."); + + // Generate new random salt: + var saltBytes = Guid.NewGuid().ToByteArray(); + + // Derive key and iv vector: + var key = new byte[32]; + var iv = new byte[16]; + + // The following operations take several seconds. Thus, using a task: + await Task.Run(() => + { + 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 + }); + + // Create AES encryption: + using var aes = Aes.Create(); + aes.Padding = PaddingMode.PKCS7; + aes.Key = key; + aes.IV = iv; + + using var encryption = aes.CreateEncryptor(); + + // Copy the given string data into a memory stream + await using var plainDataStream = new MemoryStream(Encoding.UTF8.GetBytes(data)); + + // A memory stream for the final, encrypted data: + await using var encryptedAndEncodedData = new MemoryStream(); + + // A base64 stream for the encoding: + await using var base64Stream = new CryptoStream(encryptedAndEncodedData, new ToBase64Transform(), CryptoStreamMode.Write); + + // Write the salt into the base64 stream: + await base64Stream.WriteAsync(saltBytes); + + // Create the encryption stream: + await using var cryptoStream = new CryptoStream(base64Stream, encryption, CryptoStreamMode.Write); + + // Write the payload into the encryption stream: + await plainDataStream.CopyToAsync(cryptoStream); + + // Flush the final block. Please note, that it is not enough to call the regular flush method! + cryptoStream.FlushFinalBlock(); + + // Clears all sensitive information: + aes.Clear(); + Array.Clear(key, 0, key.Length); + Array.Clear(iv, 0, iv.Length); + password = string.Empty; + + // Convert the base64 encoded data back into a string. Uses GetBuffer due to the advantage, that + // it does not create another copy of the data. ToArray would create another copy of the data! + return Encoding.ASCII.GetString(encryptedAndEncodedData.GetBuffer()[..(int)encryptedAndEncodedData.Length]); + } + + /// + /// 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). + /// This method suits for small data such as telegrams, JSON data, text notes, passwords, etc. For larger + /// data, might use the stream overload. Rule of thumb: If the data could be stored three times in + /// the present memory, this method could be used. + /// + /// The base64 encoded and AES encrypted string. This string must be ASCII encoded. + /// The password. Must consists of 6 chars or more. + /// The decrypted UTF8 encoded string. + public static async Task DecryptString(string base64EncodedAndEncryptedData, string password) + { + if (string.IsNullOrWhiteSpace(password) || password.Length < 6) + throw new CryptographicException("The password was empty or shorter than 6 characters."); + + if (base64EncodedAndEncryptedData == null) + throw new CryptographicException("The data cannot be null."); + + // Build a memory stream to access the given base64 encoded data: + await using var encodedEncryptedStream = new MemoryStream(Encoding.ASCII.GetBytes(base64EncodedAndEncryptedData)); + + // Wrap around the base64 decoder stream: + await using var base64Stream = new CryptoStream(encodedEncryptedStream, new FromBase64Transform(), CryptoStreamMode.Read); + + // A buffer for the salt's bytes: + var saltBytes = new byte[16]; // 16 bytes = Guid + + // Read the salt's bytes out of the stream: + await base64Stream.ReadAsync(saltBytes, 0, saltBytes.Length); + + // Derive key and iv vector: + var key = new byte[32]; + var iv = new byte[16]; + + // The following operations take several seconds. Thus, using a task: + await Task.Run(() => + { + 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 + }); + + // Create AES decryption: + using var aes = Aes.Create(); + aes.Padding = PaddingMode.PKCS7; + aes.Key = key; + aes.IV = iv; + + using var decryption = aes.CreateDecryptor(); + + // A memory stream for the final, decrypted data: + await using var decryptedData = new MemoryStream(); + + // The crypto stream: + await using var cryptoStream = new CryptoStream(base64Stream, decryption, CryptoStreamMode.Read); + + // Reads all remaining data trough the decrypt stream. Note, that this operation + // starts at the current position, i.e. after the salt bytes: + await cryptoStream.CopyToAsync(decryptedData); + + // Clears all sensitive information: + aes.Clear(); + Array.Clear(key, 0, key.Length); + Array.Clear(iv, 0, iv.Length); + password = string.Empty; + + // Convert the decrypted data back into a string. Uses GetBuffer due to the advantage, that + // 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]); + } + } +} diff --git a/Encrypter/Encrypter.csproj b/Encrypter/Encrypter.csproj index 0b15e76..b4016db 100644 --- a/Encrypter/Encrypter.csproj +++ b/Encrypter/Encrypter.csproj @@ -13,6 +13,10 @@ LICENSE + + C:\Users\Thorsten\Downloads\repos\Encrypter\Encrypter\Encrypter.xml + + True diff --git a/Encrypter/Encrypter.xml b/Encrypter/Encrypter.xml new file mode 100644 index 0000000..b5dfcbd --- /dev/null +++ b/Encrypter/Encrypter.xml @@ -0,0 +1,56 @@ + + + + Encrypter + + + + + 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). + This method suits for small data such as telegrams, JSON data, text notes, passwords, etc. For larger + data, might use the stream overload. Rule of thumb: If the data could be stored three times in + the present memory, this method could be used. + + The UTF8 encoded string to encrypt. + The password. Must consists of 6 chars or more. + 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). + This method suits for small data such as telegrams, JSON data, text notes, passwords, etc. For larger + data, might use the stream overload. Rule of thumb: If the data could be stored three times in + the present memory, this method could be used. + + The base64 encoded and AES encrypted string. This string must be ASCII encoded. + The password. Must consists of 6 chars or more. + The decrypted UTF8 encoded string. + + + + Encrypts this 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). + This method suits for small data such as telegrams, JSON data, text notes, passwords, etc. For larger + data, might use the stream overload. Rule of thumb: If the data could be stored three times in + the present memory, this method could be used. + + This UTF8 encoded string to encrypt. + The password. Must consists of 6 chars or more. + 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). + This method suits for small data such as telegrams, JSON data, text notes, passwords, etc. For larger + data, might use the stream overload. Rule of thumb: If the data could be stored three times in + the present memory, this method could be used. + + The base64 encoded and AES encrypted string. This string must be ASCII encoded. + The password. Must consists of 6 chars or more. + The decrypted UTF8 encoded string. + + + diff --git a/Encrypter/Extensions.cs b/Encrypter/Extensions.cs new file mode 100644 index 0000000..534a655 --- /dev/null +++ b/Encrypter/Extensions.cs @@ -0,0 +1,40 @@ +using System; +using System.Collections.Generic; +using System.Text; +using System.Threading.Tasks; + +namespace Encrypter +{ + public static class Extensions + { + /// + /// Encrypts this 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). + /// This method suits for small data such as telegrams, JSON data, text notes, passwords, etc. For larger + /// data, might use the stream overload. Rule of thumb: If the data could be stored three times in + /// the present memory, this method could be used. + /// + /// This UTF8 encoded string to encrypt. + /// The password. Must consists of 6 chars or more. + /// The base64 encoded and encrypted string. The string is ASCII encoding. + public static async Task Encrypt(this string data, string password) + { + return await CryptoProcessor.EncryptString(data, password); + } + + /// + /// 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). + /// This method suits for small data such as telegrams, JSON data, text notes, passwords, etc. For larger + /// data, might use the stream overload. Rule of thumb: If the data could be stored three times in + /// the present memory, this method could be used. + /// + /// The base64 encoded and AES encrypted string. This string must be ASCII encoded. + /// The password. Must consists of 6 chars or more. + /// The decrypted UTF8 encoded string. + public static async Task Decrypt(this string data, string password) + { + return await CryptoProcessor.DecryptString(data, password); + } + } +}