Crypto-Notepad/Crypto Notepad/AES.cs
Alexander fa5fefbd3f Fixed performance issue on large values of "Password iterations"
When user try to open files with large values of "Password iterations", the interface does not freeze, but it becomes unclickable. When saving files, there will be the same effect.
2019-11-06 11:04:35 +02:00

258 lines
9.3 KiB
C#

using System;
using System.IO;
using System.Security.Cryptography;
using System.Text;
using System.Windows.Forms;
using System.Collections.Generic;
using System.Threading.Tasks;
namespace Crypto_Notepad
{
/// <summary>
/// This class extracts information needed for decryption from a .cnp file
/// </summary>
class AESMetadata
{
/// <summary>
/// Offset to actual AES data; size of metadata
/// </summary>
public int OffsetToData { get; private set; }
public byte[] InitialVector { get; private set; }
public byte[] Salt { get; private set; }
public AESMetadata()
{
InitialVector = new byte[16];
Salt = null;
}
public void DeleteMetadataFromBuffer(ref byte[] rawData)
{
byte[] buffer = new byte[rawData.Length - OffsetToData];
Buffer.BlockCopy(rawData, OffsetToData, buffer, 0, rawData.Length - OffsetToData);
rawData = buffer;
}
private bool ReadData(byte[] rawData, int offset, ref byte[] dataOut)
{
// Buffer to store bytes
List<byte> buffer = new List<byte>();
const byte nullTerminator = 0;
bool foundData = false;
// Push data to buffer
for (int i = offset; i < rawData.Length; i++)
{
if (rawData[i] == nullTerminator) { foundData = true; break; }
else { buffer.Add(rawData[i]); }
}
if (foundData == true)
{
dataOut = buffer.ToArray();
return true;
}
return false;
}
public static void WriteMetadata(MemoryStream stream, byte[] IV, byte[] salt)
{
byte[] nullByte = { 0 };
stream.Write(IV, 0, IV.Length);
stream.Write(nullByte, 0, 1);
stream.Write(salt, 0, salt.Length);
stream.Write(nullByte, 0, 1);
}
public bool GetMetadata(byte[] rawData)
{
int offset = 0;
byte[] buffer = null;
if (!ReadData(rawData, 0, ref buffer)) { return false; }
InitialVector = buffer;
offset += buffer.Length + 1;
if (!ReadData(rawData, offset, ref buffer)) { return false; }
Salt = buffer;
offset += buffer.Length + 1;
OffsetToData = offset;
return true;
}
}
class AES
{
private static bool ByteArrayIsNonZero(byte[] byte_array)
{
foreach (byte i in byte_array)
{
if (i == 0)
{
return false;
}
}
return true;
}
// The purpose of this function is to ensure that the bytes generated by it are nonzero 100% of the time
// as there was a strange bug in which RNGCryptoServiceProvider.GetNonZeroBytes() would return a null byte sometimes
private static byte[] GenerateSecureNonZeroByteArray(int length)
{
byte[] result = new byte[length];
using (RNGCryptoServiceProvider rng = new RNGCryptoServiceProvider())
{
do
{
rng.GetNonZeroBytes(result);
} while (!ByteArrayIsNonZero(result));
}
return result;
}
public static async Task<string> Encrypt(string plainText, string password,
string salt = null, string hashAlgorithm = "SHA1",
int passwordIterations = 2, int keySize = 256)
{
return await Task.Run(() =>
{
if (string.IsNullOrEmpty(plainText))
return null;
if (string.IsNullOrEmpty(password))
return null;
byte[] plainTextBytes;
byte[] saltValueBytes;
// In case user wants a random salt or salt is null/empty for some other reason
if (string.IsNullOrEmpty(salt))
{
saltValueBytes = new byte[64]; // Nice and long
saltValueBytes = GenerateSecureNonZeroByteArray(saltValueBytes.Length);
}
else
{
saltValueBytes = Encoding.ASCII.GetBytes(salt);
}
plainTextBytes = Encoding.UTF8.GetBytes(plainText);
PasswordDeriveBytes derivedPassword = new PasswordDeriveBytes
(password, saltValueBytes, hashAlgorithm, passwordIterations);
// Null password; adds *some* memory dump protection
password = null;
byte[] keyBytes = derivedPassword.GetBytes(keySize / 8);
RijndaelManaged symmetricKey = new RijndaelManaged();
symmetricKey.Mode = CipherMode.CBC;
// Generate IV
symmetricKey.IV = GenerateSecureNonZeroByteArray(symmetricKey.IV.Length);
byte[] cipherTextBytes = null;
using (MemoryStream memStream = new MemoryStream())
{
AESMetadata.WriteMetadata(memStream, symmetricKey.IV, saltValueBytes);
using (ICryptoTransform encryptor = symmetricKey.CreateEncryptor
(keyBytes, symmetricKey.IV))
{
using (CryptoStream cryptoStream = new CryptoStream
(memStream, encryptor, CryptoStreamMode.Write))
{
cryptoStream.Write(plainTextBytes, 0, plainTextBytes.Length);
cryptoStream.FlushFinalBlock();
cipherTextBytes = memStream.ToArray();
memStream.Close();
cryptoStream.Close();
}
}
}
symmetricKey.Dispose();
derivedPassword.Dispose();
return Convert.ToBase64String(cipherTextBytes);
});
}
public static async Task<string> Decrypt(string cipherText, string password, string salt = "Kosher",
string hashAlgorithm = "SHA1",
int passwordIterations = 2,
int keySize = 256)
{
return await Task.Run(() =>
{
if (string.IsNullOrEmpty(cipherText))
return null;
if (string.IsNullOrEmpty(password))
return null;
byte[] initialVectorBytes;
byte[] saltValueBytes;
byte[] cipherTextBytes = Convert.FromBase64String(cipherText);
// Extract metadata from file
AESMetadata metadata = new AESMetadata();
if (!metadata.GetMetadata(cipherTextBytes))
{
// Metadata parsing error
DialogResult result = MessageBox.Show("Unable to parse file metadata.\nAttempt to open anyway?\n(May result in a \'Incorrect Key\' error if the salt is wrong.)",
"Missing or Corrupted Metadata", MessageBoxButtons.YesNoCancel, MessageBoxIcon.Asterisk);
if (result == DialogResult.Yes)
{
// Default initialization vector from builds v1.1.2 and older
const string default_IV = "16CHARSLONG12345";
initialVectorBytes = Encoding.ASCII.GetBytes(default_IV);
saltValueBytes = Encoding.ASCII.GetBytes(salt);
}
else { return null; }
}
else
{
saltValueBytes = metadata.Salt;
initialVectorBytes = metadata.InitialVector;
metadata.DeleteMetadataFromBuffer(ref cipherTextBytes);
}
PasswordDeriveBytes derivedPassword = new PasswordDeriveBytes
(password, saltValueBytes, hashAlgorithm, passwordIterations);
byte[] keyBytes = derivedPassword.GetBytes(keySize / 8);
RijndaelManaged symmetricKey = new RijndaelManaged();
symmetricKey.Mode = CipherMode.CBC;
byte[] plainTextBytes = new byte[cipherTextBytes.Length];
int byteCount = 0;
using (MemoryStream memStream = new MemoryStream(cipherTextBytes))
{
using (ICryptoTransform decryptor = symmetricKey.CreateDecryptor
(keyBytes, initialVectorBytes))
{
using (CryptoStream cryptoStream
= new CryptoStream(memStream, decryptor, CryptoStreamMode.Read))
{
byteCount = cryptoStream.Read(plainTextBytes, 0, plainTextBytes.Length);
memStream.Close();
cryptoStream.Close();
}
}
symmetricKey.Dispose();
}
derivedPassword.Dispose();
return Encoding.UTF8.GetString(plainTextBytes, 0, byteCount);
});
}
}
}