Initial implementation
This commit is contained in:
parent
a5d69c82be
commit
18da264119
@ -10,7 +10,11 @@
|
||||
<ItemGroup>
|
||||
<PackageReference Include="nunit" Version="3.12.0" />
|
||||
<PackageReference Include="NUnit3TestAdapter" Version="3.15.1" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.2.0"/>
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.2.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Encrypter\Encrypter.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
25
Encrypter Tests/EncrypterTests.cs
Normal file
25
Encrypter Tests/EncrypterTests.cs
Normal file
@ -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));
|
||||
}
|
||||
}
|
||||
}
|
158
Encrypter/CryptoProcessor.cs
Normal file
158
Encrypter/CryptoProcessor.cs
Normal file
@ -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;
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
/// <param name="data">The UTF8 encoded string to encrypt.</param>
|
||||
/// <param name="password">The password. Must consists of 6 chars or more.</param>
|
||||
/// <returns>The base64 encoded and encrypted string. The string is ASCII encoding.</returns>
|
||||
public static async Task<string> 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]);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </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>
|
||||
/// <returns>The decrypted UTF8 encoded string.</returns>
|
||||
public static async Task<string> 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]);
|
||||
}
|
||||
}
|
||||
}
|
@ -13,6 +13,10 @@
|
||||
<PackageLicenseFile>LICENSE</PackageLicenseFile>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
|
||||
<DocumentationFile>C:\Users\Thorsten\Downloads\repos\Encrypter\Encrypter\Encrypter.xml</DocumentationFile>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Include="..\LICENSE">
|
||||
<Pack>True</Pack>
|
||||
|
56
Encrypter/Encrypter.xml
Normal file
56
Encrypter/Encrypter.xml
Normal file
@ -0,0 +1,56 @@
|
||||
<?xml version="1.0"?>
|
||||
<doc>
|
||||
<assembly>
|
||||
<name>Encrypter</name>
|
||||
</assembly>
|
||||
<members>
|
||||
<member name="M:Encrypter.CryptoProcessor.EncryptString(System.String,System.String)">
|
||||
<summary>
|
||||
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.
|
||||
</summary>
|
||||
<param name="data">The UTF8 encoded string to encrypt.</param>
|
||||
<param name="password">The password. Must consists of 6 chars or more.</param>
|
||||
<returns>The base64 encoded and encrypted string. The string is ASCII encoding.</returns>
|
||||
</member>
|
||||
<member name="M:Encrypter.CryptoProcessor.DecryptString(System.String,System.String)">
|
||||
<summary>
|
||||
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.
|
||||
</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>
|
||||
<returns>The decrypted UTF8 encoded string.</returns>
|
||||
</member>
|
||||
<member name="M:Encrypter.Extensions.Encrypt(System.String,System.String)">
|
||||
<summary>
|
||||
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.
|
||||
</summary>
|
||||
<param name="data">This UTF8 encoded string to encrypt.</param>
|
||||
<param name="password">The password. Must consists of 6 chars or more.</param>
|
||||
<returns>The base64 encoded and encrypted string. The string is ASCII encoding.</returns>
|
||||
</member>
|
||||
<member name="M:Encrypter.Extensions.Decrypt(System.String,System.String)">
|
||||
<summary>
|
||||
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.
|
||||
</summary>
|
||||
<param name="data">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>
|
||||
<returns>The decrypted UTF8 encoded string.</returns>
|
||||
</member>
|
||||
</members>
|
||||
</doc>
|
40
Encrypter/Extensions.cs
Normal file
40
Encrypter/Extensions.cs
Normal file
@ -0,0 +1,40 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Encrypter
|
||||
{
|
||||
public static class Extensions
|
||||
{
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
/// <param name="data">This UTF8 encoded string to encrypt.</param>
|
||||
/// <param name="password">The password. Must consists of 6 chars or more.</param>
|
||||
/// <returns>The base64 encoded and encrypted string. The string is ASCII encoding.</returns>
|
||||
public static async Task<string> Encrypt(this string data, string password)
|
||||
{
|
||||
return await CryptoProcessor.EncryptString(data, password);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
/// <param name="data">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>
|
||||
/// <returns>The decrypted UTF8 encoded string.</returns>
|
||||
public static async Task<string> Decrypt(this string data, string password)
|
||||
{
|
||||
return await CryptoProcessor.DecryptString(data, password);
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user