2 // System.Web.Util.MachineKeySectionUtils
5 // Chris Toshok (toshok@ximian.com)
6 // Sebastien Pouliot <sebastien@ximian.com>
8 // (c) Copyright 2005, 2010 Novell, Inc (http://www.novell.com)
12 // Permission is hereby granted, free of charge, to any person obtaining
13 // a copy of this software and associated documentation files (the
14 // "Software"), to deal in the Software without restriction, including
15 // without limitation the rights to use, copy, modify, merge, publish,
16 // distribute, sublicense, and/or sell copies of the Software, and to
17 // permit persons to whom the Software is furnished to do so, subject to
18 // the following conditions:
20 // The above copyright notice and this permission notice shall be
21 // included in all copies or substantial portions of the Software.
23 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
24 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
25 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
26 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
27 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
28 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
29 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
32 using System.ComponentModel;
33 using System.Configuration;
34 using System.Configuration.Provider;
35 using System.Security.Cryptography;
37 using System.Web.Configuration;
40 namespace System.Web.Util {
42 static class MachineKeySectionUtils {
43 static byte ToHexValue (char c, bool high)
46 if (c >= '0' && c <= '9')
48 else if (c >= 'a' && c <= 'f')
49 v = (byte) (c - 'a' + 10);
50 else if (c >= 'A' && c <= 'F')
51 v = (byte) (c - 'A' + 10);
53 throw new ArgumentException ("Invalid hex character");
61 internal static byte [] GetBytes (string key, int len)
63 byte [] result = new byte [len / 2];
64 for (int i = 0; i < len; i += 2)
65 result [i / 2] = (byte) (ToHexValue (key [i], true) + ToHexValue (key [i + 1], false));
70 static public string GetHexString (byte [] bytes)
72 StringBuilder sb = new StringBuilder (bytes.Length * 2);
74 const int numberPart = 48;
75 for (int i = 0; i < bytes.Length; i++) {
76 int tmp = (int) bytes [i];
77 int second = tmp & 15;
78 int first = (tmp >> 4) & 15;
79 sb.Append ((char) (first > 9 ? letterPart + first : numberPart + first));
80 sb.Append ((char) (second > 9 ? letterPart + second : numberPart + second));
82 return sb.ToString ();
86 // decryption="Auto" [Auto | DES | 3DES | AES | alg:algorithm_name]
87 // http://msdn.microsoft.com/en-us/library/w8h3skw9.aspx
88 public static SymmetricAlgorithm GetDecryptionAlgorithm (string name)
90 SymmetricAlgorithm sa = null;
94 sa = Rijndael.Create ();
100 sa = TripleDES.Create ();
104 if (name.StartsWith ("alg:")) {
105 sa = SymmetricAlgorithm.Create (name.Substring (4));
109 throw new ConfigurationErrorsException ();
114 // validation="HMACSHA256" [SHA1 | MD5 | 3DES | AES | HMACSHA256 | HMACSHA384 | HMACSHA512 | alg:algorithm_name]
115 // [1] http://msdn.microsoft.com/en-us/library/system.web.configuration.machinekeyvalidation.aspx
116 // [2] http://msdn.microsoft.com/en-us/library/w8h3skw9.aspx
117 public static KeyedHashAlgorithm GetValidationAlgorithm (MachineKeySection section)
119 KeyedHashAlgorithm kha = null;
120 switch (section.Validation) {
121 case MachineKeyValidation.MD5:
122 kha = new HMACMD5 ();
124 case MachineKeyValidation.AES: // see link [1] or [2]
125 case MachineKeyValidation.TripleDES: // see link [2]
126 case MachineKeyValidation.SHA1:
127 kha = new HMACSHA1 ();
130 case MachineKeyValidation.HMACSHA256:
131 kha = new HMACSHA256 ();
133 case MachineKeyValidation.HMACSHA384:
134 kha = new HMACSHA384 ();
136 case MachineKeyValidation.HMACSHA512:
137 kha = new HMACSHA512 ();
139 case MachineKeyValidation.Custom:
140 // remove the "alg:" from the start of the string
141 string algo = section.ValidationAlgorithm;
142 if (algo.StartsWith ("alg:"))
143 kha = KeyedHashAlgorithm.Create (algo.Substring (4));
150 // helpers to ease unit testing of the cryptographic code
152 static byte [] decryption_key;
153 static byte [] validation_key;
155 static SymmetricAlgorithm GetDecryptionAlgorithm (MachineKeySection section)
157 return GetDecryptionAlgorithm (section.Decryption);
160 static byte [] GetDecryptionKey (MachineKeySection section)
162 if (decryption_key == null)
163 decryption_key = GetDecryptionAlgorithm (section).Key;
164 return decryption_key;
167 static byte [] GetValidationKey (MachineKeySection section)
169 if (validation_key == null)
170 validation_key = GetValidationAlgorithm (section).Key;
171 return validation_key;
174 static SymmetricAlgorithm GetDecryptionAlgorithm (MachineKeySection section)
176 return section.GetDecryptionAlgorithm ();
179 static byte[] GetDecryptionKey (MachineKeySection section)
181 return section.GetDecryptionKey ();
184 public static byte [] GetValidationKey (MachineKeySection section)
186 return section.GetValidationKey ();
190 static public byte [] Decrypt (MachineKeySection section, byte [] encodedData)
192 return Decrypt (section, encodedData, 0, encodedData.Length);
195 static byte [] Decrypt (MachineKeySection section, byte [] encodedData, int offset, int length)
197 using (SymmetricAlgorithm sa = GetDecryptionAlgorithm (section)) {
198 sa.Key = GetDecryptionKey (section);
199 return Decrypt (sa, encodedData, offset, length);
203 static public byte [] Decrypt (SymmetricAlgorithm alg, byte [] encodedData, int offset, int length)
205 // alg.IV is randomly set (default behavior) and perfect for our needs
206 // iv is the first part of the encodedPassword
207 byte [] iv = new byte [alg.IV.Length];
208 Array.Copy (encodedData, 0, iv, 0, iv.Length);
209 using (ICryptoTransform decryptor = alg.CreateDecryptor (alg.Key, iv)) {
211 return decryptor.TransformFinalBlock (encodedData, iv.Length + offset, length - iv.Length);
213 catch (CryptographicException) {
219 static public byte [] Encrypt (MachineKeySection section, byte [] data)
221 using (SymmetricAlgorithm sa = GetDecryptionAlgorithm (section)) {
222 sa.Key = GetDecryptionKey (section);
223 return Encrypt (sa, data);
227 static public byte [] Encrypt (SymmetricAlgorithm alg, byte [] data)
229 // alg.IV is randomly set (default behavior) and perfect for our needs
231 using (ICryptoTransform encryptor = alg.CreateEncryptor (alg.Key, iv)) {
232 byte [] encrypted = encryptor.TransformFinalBlock (data, 0, data.Length);
233 byte [] output = new byte [iv.Length + encrypted.Length];
234 // note: the IV can be public, however it should not be based on the password
235 Array.Copy (iv, 0, output, 0, iv.Length);
236 Array.Copy (encrypted, 0, output, iv.Length, encrypted.Length);
242 // return [data][signature]
243 public static byte [] Sign (MachineKeySection section, byte [] data)
245 return Sign (section, data, 0, data.Length);
248 static byte [] Sign (MachineKeySection section, byte [] data, int offset, int length)
250 using (KeyedHashAlgorithm kha = GetValidationAlgorithm (section)) {
251 kha.Key = GetValidationKey (section);
252 byte [] signature = kha.ComputeHash (data, offset, length);
253 byte [] block = new byte [length + signature.Length];
254 Array.Copy (data, block, length);
255 Array.Copy (signature, 0, block, length, signature.Length);
260 public static byte [] Verify (MachineKeySection section, byte [] data)
262 byte [] unsigned_data = null;
264 using (KeyedHashAlgorithm kha = GetValidationAlgorithm (section)) {
265 kha.Key = GetValidationKey (section);
266 int signlen = kha.HashSize >> 3; // bits to bytes
267 byte [] signature = Sign (section, data, 0, data.Length - signlen);
268 for (int i = 0; i < signature.Length; i++) {
269 if (signature [i] != data [data.Length - signature.Length + i])
270 valid = false; // do not return (timing attack)
272 unsigned_data = new byte [data.Length - signlen];
273 Array.Copy (data, 0, unsigned_data, 0, unsigned_data.Length);
275 return valid ? unsigned_data : null;
278 // do NOT sign then encrypt
280 public static byte [] EncryptSign (MachineKeySection section, byte [] data)
282 byte [] encdata = Encrypt (section, data);
283 return Sign (section, encdata);
286 // note: take no shortcut (timing attack) while verifying or decrypting
287 public static byte [] VerifyDecrypt (MachineKeySection section, byte [] block)
292 using (KeyedHashAlgorithm kha = GetValidationAlgorithm (section)) {
293 kha.Key = GetValidationKey (section);
294 signlen = kha.HashSize >> 3; // bits to bytes
295 byte [] signature = Sign (section, block, 0, block.Length - signlen);
296 for (int i = 0; i < signature.Length; i++) {
297 if (signature [i] != block [block.Length - signature.Length + i])
298 valid = false; // do not return (timing attack)
302 // whatever the signature continue with decryption
304 byte [] decdata = Decrypt (section, block, 0, block.Length - signlen);
305 return valid ? decdata : null;