2010-09-10 17 views
42

Tengo algunos datos que quiero encriptar en una aplicación ASP.NET MVC para evitar que los usuarios lo alteren. Puedo usar las clases de criptografía para hacer el cifrado/descifrado real, no hay problema allí. El problema principal es averiguar dónde almacenar la clave de cifrado y gestionar los cambios en ella.Aprovechamiento de ASP.NET machineKey Para cifrar mis propios datos

Dado que ASP.NET ya mantiene una máquinaclave para varias cosas (cifrado ViewData, etc.), me preguntaba si había alguna función ASP.NET que me permitiera encriptar/desencriptar mis propios datos utilizando machineKey? De esta forma no tendría que idear mi propio sistema de gestión de claves.

Respuesta

9

Supongo que no directamente. No recuerdo de dónde saqué esto, probablemente una combinación de Reflector y algunos blogs.

public abstract class MyAwesomeClass 
{ 
    private static byte[] cryptKey; 

    private static MachineKeySection machineKeyConfig = 
     (MachineKeySection)ConfigurationManager 
      .GetSection("system.web/machineKey"); 

    // ... snip ... 

    static MyAwesomeClass() 
    { 
     string configKey; 
     byte[] key; 

     configKey = machineKeyConfig.DecryptionKey; 
     if (configKey.Contains("AutoGenerate")) 
     { 
      throw new ConfigurationErrorsException(
       Resources.MyAwesomeClass_ExplicitAlgorithmRequired); 
     } 

     key = HexStringToByteArray(configKey); 

     cryptKey = key; 
    } 

    // ... snip ... 

    protected static byte[] Encrypt(byte[] inputBuffer) 
    { 
     SymmetricAlgorithm algorithm; 
     byte[] outputBuffer; 

     if (inputBuffer == null) 
     { 
      throw new ArgumentNullException("inputBuffer"); 
     } 

     algorithm = GetCryptAlgorithm(); 

     using (var ms = new MemoryStream()) 
     { 
      algorithm.GenerateIV(); 
      ms.Write(algorithm.IV, 0, algorithm.IV.Length); 

      using (var cs = new CryptoStream(
       ms, 
       algorithm.CreateEncryptor(), 
       CryptoStreamMode.Write)) 
      { 
       cs.Write(inputBuffer, 0, inputBuffer.Length); 
       cs.FlushFinalBlock(); 
      } 

      outputBuffer = ms.ToArray(); 
     } 

     return outputBuffer; 
    } 

    protected static byte[] Decrypt(string input) 
    { 
     SymmetricAlgorithm algorithm; 
     byte[] inputBuffer, inputVectorBuffer, outputBuffer; 

     if (input == null) 
     { 
      throw new ArgumentNullException("input"); 
     } 

     algorithm = GetCryptAlgorithm(); 
     outputBuffer = null; 

     try 
     { 
      inputBuffer = Convert.FromBase64String(input); 

      inputVectorBuffer = new byte[algorithm.IV.Length]; 
      Array.Copy(
       inputBuffer, 
       inputVectorBuffer, 
       inputVectorBuffer.Length); 
      algorithm.IV = inputVectorBuffer; 

      using (var ms = new MemoryStream()) 
      { 
       using (var cs = new CryptoStream(
        ms, 
        algorithm.CreateDecryptor(), 
        CryptoStreamMode.Write)) 
       { 
        cs.Write(
         inputBuffer, 
         inputVectorBuffer.Length, 
         inputBuffer.Length - inputVectorBuffer.Length); 
        cs.FlushFinalBlock(); 
       } 

       outputBuffer = ms.ToArray(); 
      } 
     } 
     catch (FormatException e) 
     { 
      throw new CryptographicException(
       "The string could not be decoded.", e); 
     } 

     return outputBuffer; 
    } 

    // ... snip ... 

    private static SymmetricAlgorithm GetCryptAlgorithm() 
    { 
     SymmetricAlgorithm algorithm; 
     string algorithmName; 

     algorithmName = machineKeyConfig.Decryption; 
     if (algorithmName == "Auto") 
     { 
      throw new ConfigurationErrorsException(
       Resources.MyAwesomeClass_ExplicitAlgorithmRequired); 
     } 

     switch (algorithmName) 
     { 
      case "AES": 
       algorithm = new RijndaelManaged(); 
       break; 
      case "3DES": 
       algorithm = new TripleDESCryptoServiceProvider(); 
       break; 
      case "DES": 
       algorithm = new DESCryptoServiceProvider(); 
       break; 
      default: 
       throw new ConfigurationErrorsException(
        string.Format(
         CultureInfo.InvariantCulture, 
         Resources.MyAwesomeClass_UnrecognizedAlgorithmName, 
         algorithmName)); 
     } 

     algorithm.Key = cryptKey; 

     return algorithm; 
    } 

    private static byte[] HexStringToByteArray(string str) 
    { 
     byte[] buffer; 

     if (str == null) 
     { 
      throw new ArgumentNullException("str"); 
     } 

     if (str.Length % 2 == 1) 
     { 
      str = '0' + str; 
     } 

     buffer = new byte[str.Length/2]; 

     for (int i = 0; i < buffer.Length; ++i) 
     { 
      buffer[i] = byte.Parse(
       str.Substring(i * 2, 2), 
       NumberStyles.HexNumber, 
       CultureInfo.InvariantCulture); 
     } 

     return buffer; 
    } 
} 

Caveat emptor!

+2

+1 nombre de clase épico – vtortola

+0

En lugar de completar la publicación de las partes internas de cómo se hace esto. – JJS

0

Es posible que pueda reutilizar el método MembershipProvider.EncryptPassword, que a su vez utiliza algunos métodos de cifrado (desafortunadamente internos) de la clase MachineKeySection.

+0

Afortunadamente ya no es interno. Ver la respuesta de Jeff Moser. –

40

La nueva clase MachineKey en ASP.NET 4.0 hace exactamente lo que desea.

Por ejemplo:

public static class StringEncryptor { 
    public static string Encrypt(string plaintextValue) { 
     var plaintextBytes = Encoding.UTF8.GetBytes(plaintextValue); 
     return MachineKey.Encode(plaintextBytes, MachineKeyProtection.All); 
    } 

    public static string Decrypt(string encryptedValue) { 
     try { 
      var decryptedBytes = MachineKey.Decode(encryptedValue, MachineKeyProtection.All); 
      return Encoding.UTF8.GetString(decryptedBytes); 
     } 
     catch { 
      return null; 
     } 
    } 
} 

ACTUALIZACIÓN: Como se mencionó here, tener cuidado de cómo se utiliza este o usted podría permitir a alguien para forjar un token de autenticación de formularios.

+9

A partir de .NET 4.5, MachinKey.Encode/MachineKey.Decode ha quedado en desuso y MachineKey.Protect se debe utilizar en su lugar. Según la respuesta de Marco Bettiolo. – Anthony

3

Si está trabajando con 3.5 o anterior se puede evitar una gran cantidad de código y simplemente hacer esto:

public static string Encrypt(string cookieValue) 
{ 
    return FormsAuthentication.Encrypt(new FormsAuthenticationTicket(1, 
                    string.Empty, 
                    DateTime.Now, 
                    DateTime.Now.AddMinutes(20160), 
                    true, 
                    cookieValue)); 
} 

public static string Decrypt(string encryptedTicket) 
{ 
    return FormsAuthentication.Decrypt(encryptedTicket).UserData; 
} 

Uno de mis colegas me hablaron en ella y creo que es bastante razonable para hacer esto para cookies personalizadas, si no son para las necesidades generales de cifrado.

+0

FYI, esto supone que no exceda la longitud máxima de boleto (4096) – mgoodric

43

Con .NET Framwork 4.5 Se debe utilizar la nueva API:

public class StringProtector 
{ 

    private const string Purpose = "Authentication Token"; 

    public string Protect(string unprotectedText) 
    { 
     var unprotectedBytes = Encoding.UTF8.GetBytes(unprotectedText); 
     var protectedBytes = MachineKey.Protect(unprotectedBytes, Purpose); 
     var protectedText = Convert.ToBase64String(protectedBytes); 
     return protectedText; 
    } 

    public string Unprotect(string protectedText) 
    { 
     var protectedBytes = Convert.FromBase64String(protectedText); 
     var unprotectedBytes = MachineKey.Unprotect(protectedBytes, Purpose); 
     var unprotectedText = Encoding.UTF8.GetString(unprotectedBytes); 
     return unprotectedText; 
    } 

} 

Lo ideal sería que el "propósito" debe haber un valor válido una vez conocida para prevenir la forja.

+0

¡Gracias! Me gustaría agregar: http://msdn.microsoft.com/en-us/library/system.web.security.machinekey.protect(v=vs.110).aspx Compruebe "Comentarios" para ver una mejor manera para usar "Propósito". –

+2

Solo para confirmarlo ya que se hace referencia a la Clave de la máquina, supongo que en una configuración de carga equilibrada sin sesiones adhesivas, deberá asegurarse de que la clave de la máquina sea la misma. –

+2

Sí, la clave de la máquina debe ser la misma en todas las máquinas en un equilibrador de carga –

Cuestiones relacionadas