FileEncryptor.cs 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System.Text;
  5. using System.Threading.Tasks;
  6. using System.Security.Cryptography;
  7. using System.IO;
  8. namespace Steam_Desktop_Authenticator
  9. {
  10. /// <summary>
  11. /// This class provides the controls that will encrypt and decrypt the *.maFile files
  12. ///
  13. /// Passwords entered will be passed into 100k rounds of PBKDF2 (RFC2898) with a cryptographically random salt.
  14. /// The generated key will then be passed into AES-256 (RijndalManaged) which will encrypt the data
  15. /// in cypher block chaining (CBC) mode, and then write both the PBKDF2 salt and encrypted data onto the disk.
  16. /// </summary>
  17. public static class FileEncryptor
  18. {
  19. private const int PBKDF2_ITERATIONS = 50000; //Set to 50k to make program not unbearably slow. May increase in future.
  20. private const int SALT_LENGTH = 8;
  21. private const int KEY_SIZE_BYTES = 32;
  22. private const int IV_LENGTH = 16;
  23. /// <summary>
  24. /// Returns an 8-byte cryptographically random salt in base64 encoding
  25. /// </summary>
  26. /// <returns></returns>
  27. public static string GetRandomSalt()
  28. {
  29. byte[] salt = new byte[SALT_LENGTH];
  30. using (RNGCryptoServiceProvider rng = new RNGCryptoServiceProvider())
  31. {
  32. rng.GetBytes(salt);
  33. }
  34. return Convert.ToBase64String(salt);
  35. }
  36. /// <summary>
  37. /// Returns a 16-byte cryptographically random initialization vector (IV) in base64 encoding
  38. /// </summary>
  39. /// <returns></returns>
  40. public static string GetInitializationVector()
  41. {
  42. byte[] IV = new byte[IV_LENGTH];
  43. using (RNGCryptoServiceProvider rng = new RNGCryptoServiceProvider())
  44. {
  45. rng.GetBytes(IV);
  46. }
  47. return Convert.ToBase64String(IV);
  48. }
  49. /// <summary>
  50. /// Generates an encryption key derived using a password, a random salt, and specified number of rounds of PBKDF2
  51. ///
  52. /// TODO: pass in password via SecureString?
  53. /// </summary>
  54. /// <param name="password"></param>
  55. /// <param name="salt"></param>
  56. /// <returns></returns>
  57. private static byte[] GetEncryptionKey(string password, string salt)
  58. {
  59. if (string.IsNullOrEmpty(password))
  60. {
  61. throw new ArgumentException("Password is empty");
  62. }
  63. if (string.IsNullOrEmpty(salt))
  64. {
  65. throw new ArgumentException("Salt is empty");
  66. }
  67. using (Rfc2898DeriveBytes pbkdf2 = new Rfc2898DeriveBytes(password, Convert.FromBase64String(salt), PBKDF2_ITERATIONS))
  68. {
  69. return pbkdf2.GetBytes(KEY_SIZE_BYTES);
  70. }
  71. }
  72. /// <summary>
  73. /// Tries to decrypt and return data given an encrypted base64 encoded string. Must use the same
  74. /// password, salt, IV, and ciphertext that was used during the original encryption of the data.
  75. /// </summary>
  76. /// <param name="password"></param>
  77. /// <param name="passwordSalt"></param>
  78. /// <param name="IV">Initialization Vector</param>
  79. /// <param name="encryptedData"></param>
  80. /// <returns></returns>
  81. public static string DecryptData(string password, string passwordSalt, string IV, string encryptedData)
  82. {
  83. if (string.IsNullOrEmpty(password))
  84. {
  85. throw new ArgumentException("Password is empty");
  86. }
  87. if (string.IsNullOrEmpty(passwordSalt))
  88. {
  89. throw new ArgumentException("Salt is empty");
  90. }
  91. if (string.IsNullOrEmpty(IV))
  92. {
  93. throw new ArgumentException("Initialization Vector is empty");
  94. }
  95. if (string.IsNullOrEmpty(encryptedData))
  96. {
  97. throw new ArgumentException("Encrypted data is empty");
  98. }
  99. byte[] cipherText = Convert.FromBase64String(encryptedData);
  100. byte[] key = GetEncryptionKey(password, passwordSalt);
  101. string plaintext = null;
  102. using (RijndaelManaged aes256 = new RijndaelManaged())
  103. {
  104. aes256.IV = Convert.FromBase64String(IV);
  105. aes256.Key = key;
  106. aes256.Padding = PaddingMode.PKCS7;
  107. aes256.Mode = CipherMode.CBC;
  108. //create decryptor to perform the stream transform
  109. ICryptoTransform decryptor = aes256.CreateDecryptor(aes256.Key, aes256.IV);
  110. //wrap in a try since a bad password yields a bad key, which would throw an exception on decrypt
  111. try
  112. {
  113. using (MemoryStream msDecrypt = new MemoryStream(cipherText))
  114. {
  115. using (CryptoStream csDecrypt = new CryptoStream(msDecrypt, decryptor, CryptoStreamMode.Read))
  116. {
  117. using (StreamReader srDecrypt = new StreamReader(csDecrypt))
  118. {
  119. plaintext = srDecrypt.ReadToEnd();
  120. }
  121. }
  122. }
  123. }
  124. catch (CryptographicException)
  125. {
  126. plaintext = null;
  127. }
  128. }
  129. return plaintext;
  130. }
  131. /// <summary>
  132. /// Encrypts a string given a password, salt, and initialization vector, then returns result in base64 encoded string.
  133. ///
  134. /// To retrieve this data, you must decrypt with the same password, salt, IV, and cyphertext that was used during encryption
  135. /// </summary>
  136. /// <param name="password"></param>
  137. /// <param name="passwordSalt"></param>
  138. /// <param name="IV"></param>
  139. /// <param name="plaintext"></param>
  140. /// <returns></returns>
  141. public static string EncryptData(string password, string passwordSalt, string IV, string plaintext)
  142. {
  143. if (string.IsNullOrEmpty(password))
  144. {
  145. throw new ArgumentException("Password is empty");
  146. }
  147. if (string.IsNullOrEmpty(passwordSalt))
  148. {
  149. throw new ArgumentException("Salt is empty");
  150. }
  151. if (string.IsNullOrEmpty(IV))
  152. {
  153. throw new ArgumentException("Initialization Vector is empty");
  154. }
  155. if (string.IsNullOrEmpty(plaintext))
  156. {
  157. throw new ArgumentException("Plaintext data is empty");
  158. }
  159. byte[] key = GetEncryptionKey(password, passwordSalt);
  160. byte[] ciphertext;
  161. using (RijndaelManaged aes256 = new RijndaelManaged())
  162. {
  163. aes256.Key = key;
  164. aes256.IV = Convert.FromBase64String(IV);
  165. aes256.Padding = PaddingMode.PKCS7;
  166. aes256.Mode = CipherMode.CBC;
  167. ICryptoTransform encryptor = aes256.CreateEncryptor(aes256.Key, aes256.IV);
  168. using (MemoryStream msEncrypt = new MemoryStream())
  169. {
  170. using (CryptoStream csEncrypt = new CryptoStream(msEncrypt, encryptor, CryptoStreamMode.Write))
  171. {
  172. using (StreamWriter swEncypt = new StreamWriter(csEncrypt))
  173. {
  174. swEncypt.Write(plaintext);
  175. }
  176. ciphertext = msEncrypt.ToArray();
  177. }
  178. }
  179. }
  180. return Convert.ToBase64String(ciphertext);
  181. }
  182. }
  183. }