Initial implementation
This commit is contained in:
parent
a5d69c82be
commit
18da264119
@ -10,7 +10,11 @@
|
|||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="nunit" Version="3.12.0" />
|
<PackageReference Include="nunit" Version="3.12.0" />
|
||||||
<PackageReference Include="NUnit3TestAdapter" Version="3.15.1" />
|
<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>
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</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>
|
<PackageLicenseFile>LICENSE</PackageLicenseFile>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
|
||||||
|
<DocumentationFile>C:\Users\Thorsten\Downloads\repos\Encrypter\Encrypter\Encrypter.xml</DocumentationFile>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<None Include="..\LICENSE">
|
<None Include="..\LICENSE">
|
||||||
<Pack>True</Pack>
|
<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