Added stream support
This commit is contained in:
parent
fa4ce31491
commit
b803611bd0
@ -1,5 +1,8 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
@ -200,5 +203,141 @@ namespace Encrypter_Tests
|
||||
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]);
|
||||
}
|
||||
|
||||
/// <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>
|
||||
/// 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).
|
||||
@ -160,6 +232,76 @@ namespace Encrypter
|
||||
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>
|
||||
/// Upgrades the encryption regarding the used iterations for the key.
|
||||
/// </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>
|
||||
<returns>The base64 encoded and encrypted string. The string is ASCII encoding.</returns>
|
||||
</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)">
|
||||
<summary>
|
||||
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>
|
||||
<returns>The decrypted UTF8 encoded string.</returns>
|
||||
</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)">
|
||||
<summary>
|
||||
Upgrades the encryption regarding the used iterations for the key.
|
||||
|
@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
@ -22,6 +23,23 @@ namespace Encrypter
|
||||
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>
|
||||
/// 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).
|
||||
@ -36,5 +54,22 @@ namespace Encrypter
|
||||
{
|
||||
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