Added stream support
This commit is contained in:
parent
fa4ce31491
commit
b803611bd0
@ -1,5 +1,8 @@
|
|||||||
using System;
|
using System;
|
||||||
|
using System.Collections;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
using System.Security.Cryptography;
|
using System.Security.Cryptography;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
@ -200,5 +203,141 @@ namespace Encrypter_Tests
|
|||||||
Assert.That(true);
|
Assert.That(true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public async Task TestSimpleStream()
|
||||||
|
{
|
||||||
|
var message = "This is a test with umlauts äüö.";
|
||||||
|
var tempSourceFile = Path.GetTempFileName();
|
||||||
|
var tempDestFile = Path.GetTempFileName();
|
||||||
|
var tempFinalFile = Path.GetTempFileName();
|
||||||
|
var password = "test password";
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await File.WriteAllTextAsync(tempSourceFile, message);
|
||||||
|
await CryptoProcessor.EncryptStream(File.OpenRead(tempSourceFile), File.OpenWrite(tempDestFile), password);
|
||||||
|
await CryptoProcessor.DecryptStream(File.OpenRead(tempDestFile), File.OpenWrite(tempFinalFile), password);
|
||||||
|
|
||||||
|
Assert.That(File.Exists(tempDestFile), Is.True);
|
||||||
|
Assert.That(File.Exists(tempFinalFile), Is.True);
|
||||||
|
Assert.That(File.ReadAllText(tempFinalFile), Is.EqualTo(message));
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
File.Delete(tempSourceFile);
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
File.Delete(tempDestFile);
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
File.Delete(tempFinalFile);
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public async Task Test32GBStream()
|
||||||
|
{
|
||||||
|
var tempSourceFile = Path.GetTempFileName();
|
||||||
|
var tempDestFile = Path.GetTempFileName();
|
||||||
|
var tempFinalFile = Path.GetTempFileName();
|
||||||
|
var password = "test password";
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Write 32 GB random data:
|
||||||
|
await using (var stream = File.OpenWrite(tempSourceFile))
|
||||||
|
{
|
||||||
|
var rnd = new Random();
|
||||||
|
var buffer = new byte[512_000];
|
||||||
|
var iterations = 32_000_000_000 / buffer.Length;
|
||||||
|
for(var n=0; n < iterations; n++)
|
||||||
|
{
|
||||||
|
rnd.NextBytes(buffer);
|
||||||
|
await stream.WriteAsync(buffer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var fileInfoSource = new FileInfo(tempSourceFile);
|
||||||
|
Assert.That(fileInfoSource.Length, Is.EqualTo(32_000_000_000));
|
||||||
|
|
||||||
|
await CryptoProcessor.EncryptStream(File.OpenRead(tempSourceFile), File.OpenWrite(tempDestFile), password);
|
||||||
|
await CryptoProcessor.DecryptStream(File.OpenRead(tempDestFile), File.OpenWrite(tempFinalFile), password);
|
||||||
|
|
||||||
|
Assert.That(File.Exists(tempDestFile), Is.True);
|
||||||
|
Assert.That(File.Exists(tempFinalFile), Is.True);
|
||||||
|
|
||||||
|
var fileInfoEncrypted = new FileInfo(tempDestFile);
|
||||||
|
var fileInfoFinal = new FileInfo(tempFinalFile);
|
||||||
|
|
||||||
|
Assert.That(fileInfoEncrypted.Length, Is.GreaterThan(32_000_000_000));
|
||||||
|
Assert.That(fileInfoFinal.Length, Is.EqualTo(fileInfoSource.Length));
|
||||||
|
|
||||||
|
var identical = true;
|
||||||
|
await using (var sourceStream = File.OpenRead(tempSourceFile))
|
||||||
|
{
|
||||||
|
await using var finalStream = File.OpenRead(tempFinalFile);
|
||||||
|
|
||||||
|
var bufferSource = new byte[512_000];
|
||||||
|
var bufferFinal = new byte[512_000];
|
||||||
|
var iterations = 32_000_000_000 / bufferSource.Length;
|
||||||
|
for (var n = 0; n < iterations; n++)
|
||||||
|
{
|
||||||
|
await sourceStream.ReadAsync(bufferSource, 0, bufferSource.Length);
|
||||||
|
await finalStream.ReadAsync(bufferFinal, 0, bufferFinal.Length);
|
||||||
|
|
||||||
|
if (!bufferSource.SequenceEqual(bufferFinal))
|
||||||
|
{
|
||||||
|
identical = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Assert.That(identical, Is.True);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
File.Delete(tempSourceFile);
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
File.Delete(tempDestFile);
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
File.Delete(tempFinalFile);
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -88,6 +88,78 @@ namespace Encrypter
|
|||||||
return Encoding.ASCII.GetString(encryptedAndEncodedData.GetBuffer()[..(int)encryptedAndEncodedData.Length]);
|
return Encoding.ASCII.GetString(encryptedAndEncodedData.GetBuffer()[..(int)encryptedAndEncodedData.Length]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Encrypts a given input stream and writes the encrypted data to the provided output stream. A buffer stream
|
||||||
|
/// gets used in front of the output stream. This method expects, that both streams are read-to-use e.g. the
|
||||||
|
/// input stream is at the desired position and the output stream is writable, etc. This method disposes the
|
||||||
|
/// internal crypto streams. Thus, the input and output streams might get disposed as well. Please note, that
|
||||||
|
/// this method writes binary data without e.g. base64 encoding.
|
||||||
|
///
|
||||||
|
/// When the task finished, the entire encryption of the input stream is done.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="inputStream">The desired input stream. The encryption starts at the current position.</param>
|
||||||
|
/// <param name="outputStream">The desired output stream. The encrypted data gets written to the current position.</param>
|
||||||
|
/// <param name="password">The encryption password.</param>
|
||||||
|
/// <param name="iterations">The desired number of iterations to create the key. Should not be adjusted. The default is secure for the current time.</param>
|
||||||
|
public static async Task EncryptStream(Stream inputStream, Stream outputStream, 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.");
|
||||||
|
|
||||||
|
if (inputStream == null)
|
||||||
|
throw new CryptographicException("The input stream cannot be null.");
|
||||||
|
|
||||||
|
if (outputStream == null)
|
||||||
|
throw new CryptographicException("The output stream 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();
|
||||||
|
|
||||||
|
// A buffer stream for the output:
|
||||||
|
await using var bufferOutputStream = new BufferedStream(outputStream, 65_536);
|
||||||
|
|
||||||
|
// Write the salt into the base64 stream:
|
||||||
|
await bufferOutputStream.WriteAsync(saltBytes);
|
||||||
|
|
||||||
|
// Create the encryption stream:
|
||||||
|
await using var cryptoStream = new CryptoStream(bufferOutputStream, encryption, CryptoStreamMode.Write);
|
||||||
|
|
||||||
|
// Write the payload into the encryption stream:
|
||||||
|
await inputStream.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;
|
||||||
|
|
||||||
|
// Waits for the buffer stream to finish:
|
||||||
|
await bufferOutputStream.FlushAsync();
|
||||||
|
}
|
||||||
|
|
||||||
/// <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).
|
||||||
@ -160,6 +232,76 @@ namespace Encrypter
|
|||||||
return Encoding.UTF8.GetString(decryptedData.GetBuffer()[..(int)decryptedData.Length]);
|
return Encoding.UTF8.GetString(decryptedData.GetBuffer()[..(int)decryptedData.Length]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Decrypts a given input stream and writes the decrypted data to the provided output stream. A buffer stream
|
||||||
|
/// gets used in front of the output stream. This method expects, that both streams are read-to-use e.g. the
|
||||||
|
/// input stream is at the desired position and the output stream is writable, etc. This method disposes the
|
||||||
|
/// internal crypto streams. Thus, the input and output streams might get disposed as well. Please note, that
|
||||||
|
/// this method writes binary data without e.g. base64 encoding.
|
||||||
|
///
|
||||||
|
/// When the task finished, the entire decryption of the input stream is done.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="inputStream">The desired input stream. The decryption starts at the current position.</param>
|
||||||
|
/// <param name="outputStream">The desired output stream. The decrypted data gets written to the current position.</param>
|
||||||
|
/// <param name="password">The encryption password.</param>
|
||||||
|
/// <param name="iterations">The desired number of iterations to create the key. Should not be adjusted. The default is secure for the current time.</param>
|
||||||
|
public static async Task DecryptStream(Stream inputStream, Stream outputStream, 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.");
|
||||||
|
|
||||||
|
if (inputStream == null)
|
||||||
|
throw new CryptographicException("The input stream cannot be null.");
|
||||||
|
|
||||||
|
if (outputStream == null)
|
||||||
|
throw new CryptographicException("The output stream cannot be null.");
|
||||||
|
|
||||||
|
// 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 inputStream.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();
|
||||||
|
|
||||||
|
// The crypto stream:
|
||||||
|
await using var cryptoStream = new CryptoStream(inputStream, decryption, CryptoStreamMode.Read);
|
||||||
|
|
||||||
|
// Create a buffer stream in front of the output stream:
|
||||||
|
await using var bufferOutputStream = new BufferedStream(outputStream);
|
||||||
|
|
||||||
|
// 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(bufferOutputStream);
|
||||||
|
|
||||||
|
// Clears all sensitive information:
|
||||||
|
aes.Clear();
|
||||||
|
Array.Clear(key, 0, key.Length);
|
||||||
|
Array.Clear(iv, 0, iv.Length);
|
||||||
|
password = string.Empty;
|
||||||
|
|
||||||
|
// Waits for the buffer stream to finish:
|
||||||
|
await bufferOutputStream.FlushAsync();
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Upgrades the encryption regarding the used iterations for the key.
|
/// Upgrades the encryption regarding the used iterations for the key.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -22,6 +22,21 @@
|
|||||||
<param name="iterations">The number of iterations to derive the key. Should not be adjusted. The default is secure for the current time.</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.EncryptStream(System.IO.Stream,System.IO.Stream,System.String,System.Int32)">
|
||||||
|
<summary>
|
||||||
|
Encrypts a given input stream and writes the encrypted data to the provided output stream. A buffer stream
|
||||||
|
gets used in front of the output stream. This method expects, that both streams are read-to-use e.g. the
|
||||||
|
input stream is at the desired position and the output stream is writable, etc. This method disposes the
|
||||||
|
internal crypto streams. Thus, the input and output streams might get disposed as well. Please note, that
|
||||||
|
this method writes binary data without e.g. base64 encoding.
|
||||||
|
|
||||||
|
When the task finished, the entire encryption of the input stream is done.
|
||||||
|
</summary>
|
||||||
|
<param name="inputStream">The desired input stream. The encryption starts at the current position.</param>
|
||||||
|
<param name="outputStream">The desired output stream. The encrypted data gets written to the current position.</param>
|
||||||
|
<param name="password">The encryption password.</param>
|
||||||
|
<param name="iterations">The desired number of iterations to create the key. Should not be adjusted. The default is secure for the current time.</param>
|
||||||
|
</member>
|
||||||
<member name="M:Encrypter.CryptoProcessor.DecryptString(System.String,System.String,System.Int32)">
|
<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,
|
||||||
@ -35,6 +50,21 @@
|
|||||||
<param name="iterations">The number of iterations to derive the key. Should not be adjusted. The default is secure for the current time.</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.DecryptStream(System.IO.Stream,System.IO.Stream,System.String,System.Int32)">
|
||||||
|
<summary>
|
||||||
|
Decrypts a given input stream and writes the decrypted data to the provided output stream. A buffer stream
|
||||||
|
gets used in front of the output stream. This method expects, that both streams are read-to-use e.g. the
|
||||||
|
input stream is at the desired position and the output stream is writable, etc. This method disposes the
|
||||||
|
internal crypto streams. Thus, the input and output streams might get disposed as well. Please note, that
|
||||||
|
this method writes binary data without e.g. base64 encoding.
|
||||||
|
|
||||||
|
When the task finished, the entire decryption of the input stream is done.
|
||||||
|
</summary>
|
||||||
|
<param name="inputStream">The desired input stream. The decryption starts at the current position.</param>
|
||||||
|
<param name="outputStream">The desired output stream. The decrypted data gets written to the current position.</param>
|
||||||
|
<param name="password">The encryption password.</param>
|
||||||
|
<param name="iterations">The desired number of iterations to create the key. Should not be adjusted. The default is secure for the current time.</param>
|
||||||
|
</member>
|
||||||
<member name="M:Encrypter.CryptoProcessor.UpgradeIterations(System.String,System.String,System.Int32,System.Int32)">
|
<member name="M:Encrypter.CryptoProcessor.UpgradeIterations(System.String,System.String,System.Int32,System.Int32)">
|
||||||
<summary>
|
<summary>
|
||||||
Upgrades the encryption regarding the used iterations for the key.
|
Upgrades the encryption regarding the used iterations for the key.
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
@ -22,6 +23,23 @@ namespace Encrypter
|
|||||||
return await CryptoProcessor.EncryptString(data, password);
|
return await CryptoProcessor.EncryptString(data, password);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Encrypts a given input stream and writes the encrypted data to the provided output stream. A buffer stream
|
||||||
|
/// gets used in front of the output stream. This method expects, that both streams are read-to-use e.g. the
|
||||||
|
/// input stream is at the desired position and the output stream is writable, etc. This method disposes the
|
||||||
|
/// internal crypto streams. Thus, the input and output streams might get disposed as well. Please note, that
|
||||||
|
/// this method writes binary data without e.g. base64 encoding.
|
||||||
|
///
|
||||||
|
/// When the task finished, the entire encryption of the input stream is done.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="inputStream">The desired input stream. The encryption starts at the current position.</param>
|
||||||
|
/// <param name="outputStream">The desired output stream. The encrypted data gets written to the current position.</param>
|
||||||
|
/// <param name="password">The encryption password.</param>
|
||||||
|
public static async Task Encrypt(this Stream inputStream, Stream outputStream, string password)
|
||||||
|
{
|
||||||
|
await CryptoProcessor.EncryptStream(inputStream, outputStream, password);
|
||||||
|
}
|
||||||
|
|
||||||
/// <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).
|
||||||
@ -36,5 +54,22 @@ namespace Encrypter
|
|||||||
{
|
{
|
||||||
return await CryptoProcessor.DecryptString(data, password);
|
return await CryptoProcessor.DecryptString(data, password);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Decrypts a given input stream and writes the decrypted data to the provided output stream. A buffer stream
|
||||||
|
/// gets used in front of the output stream. This method expects, that both streams are read-to-use e.g. the
|
||||||
|
/// input stream is at the desired position and the output stream is writable, etc. This method disposes the
|
||||||
|
/// internal crypto streams. Thus, the input and output streams might get disposed as well. Please note, that
|
||||||
|
/// this method writes binary data without e.g. base64 encoding.
|
||||||
|
///
|
||||||
|
/// When the task finished, the entire decryption of the input stream is done.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="inputStream">The desired input stream. The decryption starts at the current position.</param>
|
||||||
|
/// <param name="outputStream">The desired output stream. The decrypted data gets written to the current position.</param>
|
||||||
|
/// <param name="password">The encryption password.</param>
|
||||||
|
public static async Task Decrypt(this Stream inputStream, Stream outputStream, string password)
|
||||||
|
{
|
||||||
|
await CryptoProcessor.DecryptStream(inputStream, outputStream, password);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user