2016-01-06 14:47:38 +00:00
using System ;
using System.IO ;
using System.Security.Cryptography ;
using System.Text ;
2018-11-30 11:03:21 +00:00
using System.Windows.Forms ;
2018-12-03 08:12:19 +00:00
using System.Collections.Generic ;
2019-11-06 09:04:35 +00:00
using System.Threading.Tasks ;
2016-01-06 14:47:38 +00:00
2016-01-09 20:46:25 +00:00
namespace Crypto_Notepad
2016-01-06 14:47:38 +00:00
{
2018-11-22 06:19:39 +00:00
/// <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>
2018-12-03 08:12:19 +00:00
public int OffsetToData { get ; private set ; }
public byte [ ] InitialVector { get ; private set ; }
public byte [ ] Salt { get ; private set ; }
2018-11-22 06:19:39 +00:00
public AESMetadata ( )
{
2019-09-28 19:57:03 +00:00
InitialVector = new byte [ 16 ] ;
Salt = null ;
2018-11-22 06:19:39 +00:00
}
public void DeleteMetadataFromBuffer ( ref byte [ ] rawData )
{
2019-09-28 19:57:03 +00:00
byte [ ] buffer = new byte [ rawData . Length - OffsetToData ] ;
Buffer . BlockCopy ( rawData , OffsetToData , buffer , 0 , rawData . Length - OffsetToData ) ;
2018-11-22 06:19:39 +00:00
rawData = buffer ;
}
2018-12-03 08:12:19 +00:00
private bool ReadData ( byte [ ] rawData , int offset , ref byte [ ] dataOut )
2018-11-22 06:19:39 +00:00
{
2018-12-03 08:12:19 +00:00
// Buffer to store bytes
List < byte > buffer = new List < byte > ( ) ;
const byte nullTerminator = 0 ;
bool foundData = false ;
// Push data to buffer
2019-11-06 09:04:35 +00:00
for ( int i = offset ; i < rawData . Length ; i + + )
{
2018-12-03 08:12:19 +00:00
if ( rawData [ i ] = = nullTerminator ) { foundData = true ; break ; }
else { buffer . Add ( rawData [ i ] ) ; }
2018-11-22 06:19:39 +00:00
}
2018-11-30 11:03:21 +00:00
2018-12-03 08:12:19 +00:00
if ( foundData = = true )
2018-11-22 06:19:39 +00:00
{
2018-12-03 08:12:19 +00:00
dataOut = buffer . ToArray ( ) ;
return true ;
2018-11-22 06:19:39 +00:00
}
2018-12-03 08:12:19 +00:00
return false ;
}
2018-12-06 01:30:35 +00:00
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 ) ;
}
2018-12-03 08:12:19 +00:00
public bool GetMetadata ( byte [ ] rawData )
{
int offset = 0 ;
byte [ ] buffer = null ;
2019-09-28 19:57:03 +00:00
if ( ! ReadData ( rawData , 0 , ref buffer ) ) { return false ; }
InitialVector = buffer ;
2018-12-03 08:12:19 +00:00
offset + = buffer . Length + 1 ;
2019-09-28 19:57:03 +00:00
if ( ! ReadData ( rawData , offset , ref buffer ) ) { return false ; }
Salt = buffer ;
2018-12-03 08:12:19 +00:00
offset + = buffer . Length + 1 ;
2018-11-22 06:19:39 +00:00
2019-09-28 19:57:03 +00:00
OffsetToData = offset ;
2018-11-30 11:03:21 +00:00
return true ;
2018-11-22 06:19:39 +00:00
}
}
2016-01-06 14:47:38 +00:00
class AES
{
2018-12-19 09:33:31 +00:00
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 ;
}
2019-11-06 09:04:35 +00:00
public static async Task < string > Encrypt ( string plainText , string password ,
2018-11-30 11:03:21 +00:00
string salt = null , string hashAlgorithm = "SHA1" ,
2018-11-22 06:19:39 +00:00
int passwordIterations = 2 , int keySize = 256 )
2016-01-06 14:47:38 +00:00
{
2019-11-06 09:04:35 +00:00
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 ) ;
} ) ;
2016-01-06 14:47:38 +00:00
}
2019-11-06 09:04:35 +00:00
public static async Task < string > Decrypt ( string cipherText , string password , string salt = "Kosher" ,
2018-11-22 06:19:39 +00:00
string hashAlgorithm = "SHA1" ,
int passwordIterations = 2 ,
2016-01-06 14:47:38 +00:00
int keySize = 256 )
{
2019-11-06 09:04:35 +00:00
return await Task . Run ( ( ) = >
2018-11-30 11:03:21 +00:00
{
2019-11-06 09:04:35 +00:00
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 ) )
2018-11-30 11:03:21 +00:00
{
2019-11-06 09:04:35 +00:00
// 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.)" ,
2019-11-27 09:56:53 +00:00
"Missing or Corrupted Metadata" , MessageBoxButtons . YesNoCancel , MessageBoxIcon . Asterisk , MessageBoxDefaultButton . Button1 , MessageBoxOptions . DefaultDesktopOnly ) ;
2019-11-06 09:04:35 +00:00
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 ) ;
}
2019-11-27 09:56:53 +00:00
else
{
PublicVar . metadataCorrupt = true ;
return null ;
}
2019-11-06 09:04:35 +00:00
}
else
{
saltValueBytes = metadata . Salt ;
initialVectorBytes = metadata . InitialVector ;
metadata . DeleteMetadataFromBuffer ( ref cipherTextBytes ) ;
2018-11-30 11:03:21 +00:00
}
2018-11-22 06:19:39 +00:00
2019-11-06 09:04:35 +00:00
PasswordDeriveBytes derivedPassword = new PasswordDeriveBytes
( password , saltValueBytes , hashAlgorithm , passwordIterations ) ;
byte [ ] keyBytes = derivedPassword . GetBytes ( keySize / 8 ) ;
2016-01-06 14:47:38 +00:00
2019-11-06 09:04:35 +00:00
RijndaelManaged symmetricKey = new RijndaelManaged ( ) ;
symmetricKey . Mode = CipherMode . CBC ;
2016-01-06 14:47:38 +00:00
2019-11-06 09:04:35 +00:00
byte [ ] plainTextBytes = new byte [ cipherTextBytes . Length ] ;
int byteCount = 0 ;
2016-01-06 14:47:38 +00:00
2019-11-06 09:04:35 +00:00
using ( MemoryStream memStream = new MemoryStream ( cipherTextBytes ) )
2016-01-06 14:47:38 +00:00
{
2019-11-06 09:04:35 +00:00
using ( ICryptoTransform decryptor = symmetricKey . CreateDecryptor
( keyBytes , initialVectorBytes ) )
2016-01-06 14:47:38 +00:00
{
2019-11-06 09:04:35 +00:00
using ( CryptoStream cryptoStream
= new CryptoStream ( memStream , decryptor , CryptoStreamMode . Read ) )
{
byteCount = cryptoStream . Read ( plainTextBytes , 0 , plainTextBytes . Length ) ;
memStream . Close ( ) ;
cryptoStream . Close ( ) ;
}
2016-01-06 14:47:38 +00:00
}
2019-11-06 09:04:35 +00:00
symmetricKey . Dispose ( ) ;
2016-01-06 14:47:38 +00:00
}
2019-11-06 09:04:35 +00:00
derivedPassword . Dispose ( ) ;
2018-12-28 10:58:07 +00:00
2019-11-06 09:04:35 +00:00
return Encoding . UTF8 . GetString ( plainTextBytes , 0 , byteCount ) ;
} ) ;
2016-01-06 14:47:38 +00:00
}
}
}