MachineKeySectionUtils.cs 9.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309
  1. //
  2. // System.Web.Util.MachineKeySectionUtils
  3. //
  4. // Authors:
  5. // Chris Toshok ([email protected])
  6. // Sebastien Pouliot <[email protected]>
  7. //
  8. // (c) Copyright 2005, 2010 Novell, Inc (http://www.novell.com)
  9. //
  10. //
  11. // Permission is hereby granted, free of charge, to any person obtaining
  12. // a copy of this software and associated documentation files (the
  13. // "Software"), to deal in the Software without restriction, including
  14. // without limitation the rights to use, copy, modify, merge, publish,
  15. // distribute, sublicense, and/or sell copies of the Software, and to
  16. // permit persons to whom the Software is furnished to do so, subject to
  17. // the following conditions:
  18. //
  19. // The above copyright notice and this permission notice shall be
  20. // included in all copies or substantial portions of the Software.
  21. //
  22. // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
  23. // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
  24. // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
  25. // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
  26. // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
  27. // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
  28. // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
  29. //
  30. using System.ComponentModel;
  31. using System.Configuration;
  32. using System.Configuration.Provider;
  33. using System.Security.Cryptography;
  34. using System.Text;
  35. using System.Web.Configuration;
  36. namespace System.Web.Util {
  37. static class MachineKeySectionUtils {
  38. static byte ToHexValue (char c, bool high)
  39. {
  40. byte v;
  41. if (c >= '0' && c <= '9')
  42. v = (byte) (c - '0');
  43. else if (c >= 'a' && c <= 'f')
  44. v = (byte) (c - 'a' + 10);
  45. else if (c >= 'A' && c <= 'F')
  46. v = (byte) (c - 'A' + 10);
  47. else
  48. throw new ArgumentException ("Invalid hex character");
  49. if (high)
  50. v <<= 4;
  51. return v;
  52. }
  53. internal static byte [] GetBytes (string key, int len)
  54. {
  55. byte [] result = new byte [len / 2];
  56. for (int i = 0; i < len; i += 2)
  57. result [i / 2] = (byte) (ToHexValue (key [i], true) + ToHexValue (key [i + 1], false));
  58. return result;
  59. }
  60. static public string GetHexString (byte [] bytes)
  61. {
  62. StringBuilder sb = new StringBuilder (bytes.Length * 2);
  63. int letterPart = 55;
  64. const int numberPart = 48;
  65. for (int i = 0; i < bytes.Length; i++) {
  66. int tmp = (int) bytes [i];
  67. int second = tmp & 15;
  68. int first = (tmp >> 4) & 15;
  69. sb.Append ((char) (first > 9 ? letterPart + first : numberPart + first));
  70. sb.Append ((char) (second > 9 ? letterPart + second : numberPart + second));
  71. }
  72. return sb.ToString ();
  73. }
  74. // decryption="Auto" [Auto | DES | 3DES | AES | alg:algorithm_name]
  75. // http://msdn.microsoft.com/en-us/library/w8h3skw9.aspx
  76. public static SymmetricAlgorithm GetDecryptionAlgorithm (string name)
  77. {
  78. SymmetricAlgorithm sa = null;
  79. switch (name) {
  80. case "AES":
  81. case "Auto":
  82. sa = Rijndael.Create ();
  83. break;
  84. case "DES":
  85. sa = DES.Create ();
  86. break;
  87. case "3DES":
  88. sa = TripleDES.Create ();
  89. break;
  90. default:
  91. if (name.StartsWith ("alg:")) {
  92. sa = SymmetricAlgorithm.Create (name.Substring (4));
  93. break;
  94. }
  95. throw new ConfigurationErrorsException ();
  96. }
  97. return sa;
  98. }
  99. // validation="HMACSHA256" [SHA1 | MD5 | 3DES | AES | HMACSHA256 | HMACSHA384 | HMACSHA512 | alg:algorithm_name]
  100. // [1] http://msdn.microsoft.com/en-us/library/system.web.configuration.machinekeyvalidation.aspx
  101. // [2] http://msdn.microsoft.com/en-us/library/w8h3skw9.aspx
  102. public static KeyedHashAlgorithm GetValidationAlgorithm (MachineKeySection section)
  103. {
  104. KeyedHashAlgorithm kha = null;
  105. switch (section.Validation) {
  106. case MachineKeyValidation.MD5:
  107. kha = new HMACMD5 ();
  108. break;
  109. case MachineKeyValidation.AES: // see link [1] or [2]
  110. case MachineKeyValidation.TripleDES: // see link [2]
  111. case MachineKeyValidation.SHA1:
  112. kha = new HMACSHA1 ();
  113. break;
  114. case MachineKeyValidation.HMACSHA256:
  115. kha = new HMACSHA256 ();
  116. break;
  117. case MachineKeyValidation.HMACSHA384:
  118. kha = new HMACSHA384 ();
  119. break;
  120. case MachineKeyValidation.HMACSHA512:
  121. kha = new HMACSHA512 ();
  122. break;
  123. case MachineKeyValidation.Custom:
  124. // remove the "alg:" from the start of the string
  125. string algo = section.ValidationAlgorithm;
  126. if (algo.StartsWith ("alg:"))
  127. kha = KeyedHashAlgorithm.Create (algo.Substring (4));
  128. break;
  129. }
  130. return kha;
  131. }
  132. // helpers to ease unit testing of the cryptographic code
  133. #if TEST
  134. static byte [] decryption_key;
  135. static byte [] validation_key;
  136. static SymmetricAlgorithm GetDecryptionAlgorithm (MachineKeySection section)
  137. {
  138. return GetDecryptionAlgorithm (section.Decryption);
  139. }
  140. static byte [] GetDecryptionKey (MachineKeySection section)
  141. {
  142. if (decryption_key == null)
  143. decryption_key = GetDecryptionAlgorithm (section).Key;
  144. return decryption_key;
  145. }
  146. static byte [] GetValidationKey (MachineKeySection section)
  147. {
  148. if (validation_key == null)
  149. validation_key = GetValidationAlgorithm (section).Key;
  150. return validation_key;
  151. }
  152. #else
  153. static SymmetricAlgorithm GetDecryptionAlgorithm (MachineKeySection section)
  154. {
  155. return section.GetDecryptionAlgorithm ();
  156. }
  157. static byte[] GetDecryptionKey (MachineKeySection section)
  158. {
  159. return section.GetDecryptionKey ();
  160. }
  161. public static byte [] GetValidationKey (MachineKeySection section)
  162. {
  163. return section.GetValidationKey ();
  164. }
  165. #endif
  166. static public byte [] Decrypt (MachineKeySection section, byte [] encodedData)
  167. {
  168. return Decrypt (section, encodedData, 0, encodedData.Length);
  169. }
  170. static byte [] Decrypt (MachineKeySection section, byte [] encodedData, int offset, int length)
  171. {
  172. using (SymmetricAlgorithm sa = GetDecryptionAlgorithm (section)) {
  173. sa.Key = GetDecryptionKey (section);
  174. return Decrypt (sa, encodedData, offset, length);
  175. }
  176. }
  177. static public byte [] Decrypt (SymmetricAlgorithm alg, byte [] encodedData, int offset, int length)
  178. {
  179. // alg.IV is randomly set (default behavior) and perfect for our needs
  180. // iv is the first part of the encodedPassword
  181. byte [] iv = new byte [alg.IV.Length];
  182. Array.Copy (encodedData, 0, iv, 0, iv.Length);
  183. using (ICryptoTransform decryptor = alg.CreateDecryptor (alg.Key, iv)) {
  184. try {
  185. return decryptor.TransformFinalBlock (encodedData, iv.Length + offset, length - iv.Length);
  186. }
  187. catch (CryptographicException) {
  188. return null;
  189. }
  190. }
  191. }
  192. static public byte [] Encrypt (MachineKeySection section, byte [] data)
  193. {
  194. using (SymmetricAlgorithm sa = GetDecryptionAlgorithm (section)) {
  195. sa.Key = GetDecryptionKey (section);
  196. return Encrypt (sa, data);
  197. }
  198. }
  199. static public byte [] Encrypt (SymmetricAlgorithm alg, byte [] data)
  200. {
  201. // alg.IV is randomly set (default behavior) and perfect for our needs
  202. byte [] iv = alg.IV;
  203. using (ICryptoTransform encryptor = alg.CreateEncryptor (alg.Key, iv)) {
  204. byte [] encrypted = encryptor.TransformFinalBlock (data, 0, data.Length);
  205. byte [] output = new byte [iv.Length + encrypted.Length];
  206. // note: the IV can be public, however it should not be based on the password
  207. Array.Copy (iv, 0, output, 0, iv.Length);
  208. Array.Copy (encrypted, 0, output, iv.Length, encrypted.Length);
  209. return output;
  210. }
  211. }
  212. // in [data]
  213. // return [data][signature]
  214. public static byte [] Sign (MachineKeySection section, byte [] data)
  215. {
  216. return Sign (section, data, 0, data.Length);
  217. }
  218. static byte [] Sign (MachineKeySection section, byte [] data, int offset, int length)
  219. {
  220. using (KeyedHashAlgorithm kha = GetValidationAlgorithm (section)) {
  221. kha.Key = GetValidationKey (section);
  222. byte [] signature = kha.ComputeHash (data, offset, length);
  223. byte [] block = new byte [length + signature.Length];
  224. Array.Copy (data, block, length);
  225. Array.Copy (signature, 0, block, length, signature.Length);
  226. return block;
  227. }
  228. }
  229. public static byte [] Verify (MachineKeySection section, byte [] data)
  230. {
  231. byte [] unsigned_data = null;
  232. bool valid = true;
  233. using (KeyedHashAlgorithm kha = GetValidationAlgorithm (section)) {
  234. kha.Key = GetValidationKey (section);
  235. int signlen = kha.HashSize >> 3; // bits to bytes
  236. byte [] signature = Sign (section, data, 0, data.Length - signlen);
  237. for (int i = 0; i < signature.Length; i++) {
  238. if (signature [i] != data [data.Length - signature.Length + i])
  239. valid = false; // do not return (timing attack)
  240. }
  241. unsigned_data = new byte [data.Length - signlen];
  242. Array.Copy (data, 0, unsigned_data, 0, unsigned_data.Length);
  243. }
  244. return valid ? unsigned_data : null;
  245. }
  246. // do NOT sign then encrypt
  247. public static byte [] EncryptSign (MachineKeySection section, byte [] data)
  248. {
  249. byte [] encdata = Encrypt (section, data);
  250. return Sign (section, encdata);
  251. }
  252. // note: take no shortcut (timing attack) while verifying or decrypting
  253. public static byte [] VerifyDecrypt (MachineKeySection section, byte [] block)
  254. {
  255. bool valid = true;
  256. int signlen;
  257. using (KeyedHashAlgorithm kha = GetValidationAlgorithm (section)) {
  258. kha.Key = GetValidationKey (section);
  259. signlen = kha.HashSize >> 3; // bits to bytes
  260. byte [] signature = Sign (section, block, 0, block.Length - signlen);
  261. for (int i = 0; i < signature.Length; i++) {
  262. if (signature [i] != block [block.Length - signature.Length + i])
  263. valid = false; // do not return (timing attack)
  264. }
  265. }
  266. // whatever the signature continue with decryption
  267. try {
  268. byte [] decdata = Decrypt (section, block, 0, block.Length - signlen);
  269. return valid ? decdata : null;
  270. }
  271. catch {
  272. return null;
  273. }
  274. }
  275. }
  276. }