diff --git a/Program.cs b/Program.cs
index 664bdb1..d728a7c 100644
--- a/Program.cs
+++ b/Program.cs
@@ -504,3 +504,190 @@ public class Manifest
public ulong SteamID { get; set; }
}
}
+
+///
+/// This class provides the controls that will encrypt and decrypt the *.maFile files
+///
+/// Passwords entered will be passed into 100k rounds of PBKDF2 (RFC2898) with a cryptographically random salt.
+/// The generated key will then be passed into AES-256 (RijndalManaged) which will encrypt the data
+/// in cypher block chaining (CBC) mode, and then write both the PBKDF2 salt and encrypted data onto the disk.
+///
+public static class FileEncryptor
+{
+ private const int PBKDF2_ITERATIONS = 50000; //Set to 50k to make program not unbearably slow. May increase in future.
+ private const int SALT_LENGTH = 8;
+ private const int KEY_SIZE_BYTES = 32;
+ private const int IV_LENGTH = 16;
+
+ ///
+ /// Returns an 8-byte cryptographically random salt in base64 encoding
+ ///
+ ///
+ public static string GetRandomSalt()
+ {
+ byte[] salt = new byte[SALT_LENGTH];
+ using (RNGCryptoServiceProvider rng = new RNGCryptoServiceProvider())
+ {
+ rng.GetBytes(salt);
+ }
+ return Convert.ToBase64String(salt);
+ }
+
+ ///
+ /// Returns a 16-byte cryptographically random initialization vector (IV) in base64 encoding
+ ///
+ ///
+ public static string GetInitializationVector()
+ {
+ byte[] IV = new byte[IV_LENGTH];
+ using (RNGCryptoServiceProvider rng = new RNGCryptoServiceProvider())
+ {
+ rng.GetBytes(IV);
+ }
+ return Convert.ToBase64String(IV);
+ }
+
+
+ ///
+ /// Generates an encryption key derived using a password, a random salt, and specified number of rounds of PBKDF2
+ ///
+ /// TODO: pass in password via SecureString?
+ ///
+ ///
+ ///
+ ///
+ private static byte[] GetEncryptionKey(string password, string salt)
+ {
+ if (string.IsNullOrEmpty(password))
+ {
+ throw new ArgumentException("Password is empty");
+ }
+ if (string.IsNullOrEmpty(salt))
+ {
+ throw new ArgumentException("Salt is empty");
+ }
+ using (Rfc2898DeriveBytes pbkdf2 = new Rfc2898DeriveBytes(password, Convert.FromBase64String(salt), PBKDF2_ITERATIONS))
+ {
+ return pbkdf2.GetBytes(KEY_SIZE_BYTES);
+ }
+ }
+
+ ///
+ /// Tries to decrypt and return data given an encrypted base64 encoded string. Must use the same
+ /// password, salt, IV, and ciphertext that was used during the original encryption of the data.
+ ///
+ ///
+ ///
+ /// Initialization Vector
+ ///
+ ///
+ public static string DecryptData(string password, string passwordSalt, string IV, string encryptedData)
+ {
+ if (string.IsNullOrEmpty(password))
+ {
+ throw new ArgumentException("Password is empty");
+ }
+ if (string.IsNullOrEmpty(passwordSalt))
+ {
+ throw new ArgumentException("Salt is empty");
+ }
+ if (string.IsNullOrEmpty(IV))
+ {
+ throw new ArgumentException("Initialization Vector is empty");
+ }
+ if (string.IsNullOrEmpty(encryptedData))
+ {
+ throw new ArgumentException("Encrypted data is empty");
+ }
+
+ byte[] cipherText = Convert.FromBase64String(encryptedData);
+ byte[] key = GetEncryptionKey(password, passwordSalt);
+ string plaintext = null;
+
+ using (RijndaelManaged aes256 = new RijndaelManaged())
+ {
+ aes256.IV = Convert.FromBase64String(IV);
+ aes256.Key = key;
+ aes256.Padding = PaddingMode.PKCS7;
+ aes256.Mode = CipherMode.CBC;
+
+ //create decryptor to perform the stream transform
+ ICryptoTransform decryptor = aes256.CreateDecryptor(aes256.Key, aes256.IV);
+
+ //wrap in a try since a bad password yields a bad key, which would throw an exception on decrypt
+ try
+ {
+ using (MemoryStream msDecrypt = new MemoryStream(cipherText))
+ {
+ using (CryptoStream csDecrypt = new CryptoStream(msDecrypt, decryptor, CryptoStreamMode.Read))
+ {
+ using (StreamReader srDecrypt = new StreamReader(csDecrypt))
+ {
+ plaintext = srDecrypt.ReadToEnd();
+ }
+ }
+ }
+ }
+ catch (CryptographicException)
+ {
+ plaintext = null;
+ }
+ }
+ return plaintext;
+ }
+
+ ///
+ /// Encrypts a string given a password, salt, and initialization vector, then returns result in base64 encoded string.
+ ///
+ /// To retrieve this data, you must decrypt with the same password, salt, IV, and cyphertext that was used during encryption
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ public static string EncryptData(string password, string passwordSalt, string IV, string plaintext)
+ {
+ if (string.IsNullOrEmpty(password))
+ {
+ throw new ArgumentException("Password is empty");
+ }
+ if (string.IsNullOrEmpty(passwordSalt))
+ {
+ throw new ArgumentException("Salt is empty");
+ }
+ if (string.IsNullOrEmpty(IV))
+ {
+ throw new ArgumentException("Initialization Vector is empty");
+ }
+ if (string.IsNullOrEmpty(plaintext))
+ {
+ throw new ArgumentException("Plaintext data is empty");
+ }
+ byte[] key = GetEncryptionKey(password, passwordSalt);
+ byte[] ciphertext;
+
+ using (RijndaelManaged aes256 = new RijndaelManaged())
+ {
+ aes256.Key = key;
+ aes256.IV = Convert.FromBase64String(IV);
+ aes256.Padding = PaddingMode.PKCS7;
+ aes256.Mode = CipherMode.CBC;
+
+ ICryptoTransform encryptor = aes256.CreateEncryptor(aes256.Key, aes256.IV);
+
+ using (MemoryStream msEncrypt = new MemoryStream())
+ {
+ using (CryptoStream csEncrypt = new CryptoStream(msEncrypt, encryptor, CryptoStreamMode.Write))
+ {
+ using (StreamWriter swEncypt = new StreamWriter(csEncrypt))
+ {
+ swEncypt.Write(plaintext);
+ }
+ ciphertext = msEncrypt.ToArray();
+ }
+ }
+ }
+ return Convert.ToBase64String(ciphertext);
+ }
+}