encriptando o cifrando informacion?

http://sergiot2.com/blogimages/2008/12Dic/Criptografia.jpg

La necesidad de encriptar (ocultar a simple vista) información no es necesidad propia en los sistemas de información, su inicio y apogeo fue marcado por las diversas guerras que hubo en la historia, se presume desde las campañas militares de los romanos hasta las últimas guerras mundiales.

¿Qué ha cambiado, en el presente? Pues que ahora contamos con computadores más potentes capaces de poder desencriptar (vamos a usar estos neologismos en la entrada, si prefiere, reemplazar por cifrar/descifrar) la información “oculta”, utilizando fuerza bruta por ejemplo. Y desde hace décadas se tratado de mejorar y llevar al proceso de estandarización los algoritmos de encriptación.

Veamos, un ejemplo sencillo usando el método de encriptación CCQSMO cualquier cosa que se me ocurra:

La palabra Geeks.ms:

  1. Obtener el código Ascii: 7110110110711546109115
  2. Le sumamos 10 (clave) a cada código Ascii: 8111111111712556119125
  3. Volvemos a convertir a cadena, y tenemos nuestra cadena encriptada: Qoou}8w}

Obviamente hay algoritmos de encriptación, y hay “algoritmos”. Vamos a repasar algunos de estos métodos y sus respectivas codificaciones, pero antes vamos diferenciar algoritmos de encriptación y métodos de cifrado Hash.

¿Se han preguntado porqué cuando se registran en algunos foros, al hacer un “recovery password”, no me devuelve el password que he perdido, el sistema devuelve un nuevo password. ¿Por qué? Es que no esta usando un algoritmo de encriptación para ocultar el password, esta usando una función Hash, y es en un sólo sentido, en otras palabras, no se puede recuperar la cadena original a partir del password. ¿Entonces, cómo hace el match?, pues lo que hace no es desencriptar el password cifrado, para compararlo con el input del usuario, lo que hace es volver a cifrar/encriptar el input, y si el input cifrado coincide con el password cifrado que esta en la base de datos, entonces valida exitosamente tus credenciales. Mas detalles: Hash vs Encryption y Hashing vs. Encryption.

Hecho esta aclaraciones vamos a listar los métodos de criptografía, a los cuales podemos encontrar en dos grandes grupos:

1.a) Criptografía simétrica (en):

Estos algoritmos, usan una misma clave para encriptar/desencriptar el mensaje. En .Net tenemos el namespace: System.Security.Cryptography, para las operación de encriptación:

 

 1.b) Criptografía asimétrica (en):

Usa dos claves para el envió de mensajes, una clave pública y una clave privada. El destinatario del mensaje es el que posee estas dos claves, y envía la clave pública al remitente, y sólo con la clave privada (que tiene sólo el destinatario) puede desencriptar el mensaje.

2) Funciones de encriptación hash:

Como comenté al inicio, es encriptación (o cifrado) en un sólo sentido, no se puede recuperar el texto original a parte del texto encriptado. Además de tener la particularidad de generar un texto de un tamaño específico, siendo único cada contenido generado.

 

Ahora que ya vimos, los algoritmos de encriptación y funciones hash. Vayamos con algunos ejemplos, y su aplicabilidad dentro del entorno .Net

a) Encriptando secciones del web.config, con estos artículos queda clarísimo: Cifrado de información en los archivos de configuración de ASP.NET y Encrypting Configuration Information in ASP.NET 2.0 Applications. Podemos usar dos providers: RSA o DPAPI, básicamente DPAPI es más segura por que esta atada a la máquina donde se realiza la encriptación (leer los comentarios de José M.A.). Pero el proveedor DPAPI (DpapiProtectedConfigurationProvider), qué algoritmo usa? Utiliza una API del sistema operativo llamada Windows Data Protection, y es expuesta a través de: Crypt32.dll (Win32).

b) ¿Que algoritmo de encriptación usa el servicio de membership de ASP.NET 2.0+? Si estamos usando el servicio de Membership de ASP.NET 2.0+, dentro del web.config, en el elemento membership, nosotros podemos definir el formato del password a almacenar en la base de datos, y puede ser de tres tipos: Clear, Encrypted, and Hashed. Clear, almacena el password en texto plano (por si me olvidó, o me gusta modificar el password directamente en la base de datos con un Open Table), Hashed para usar una función hash (en un sólo sentido), y Encrypted, para poder encriptar/desencriptar un password. ¿Qué implicancia tiene, usar Hashed o Encrypted?, pues que si estamos usando el control PasswordRecovery, con el formato Hashed no vamos a poder recuperar el password, lo que hará el control es generar un nuevo password, recuerden que Hash es un sólo sentido. ¿Qué algoritmos usa, y donde lo configuro? La configuración se hace en el elemento machineKey, y puedo usar las funciones hash MD5 y SHA1, y los algoritmos de encriptación simétrica 3DES y AES.

P.D.: Esto es el fruto de dos amanecidas hasta las 2:00 a.m., una rápida revisión a los algoritmos de encriptación, y sus respectivas implementaciones en .NET, que lo disfruten!

Saludos,

[Ado.Net] Clase de conexion generica para cualquier motor de base datos, usando .Net Providers

Problema: En internet hay mucha información sobre como trabajar con Ado.Net y SQL, pero cuando tenemos que usar un nuevo proveedor  de base de datos desde .Net, comenzamos a buscar ejemplos específicos sin darnos cuenta, que también podemos usar los ejemplos de Ado.Net y SQL (usar la estructura).

Solución: Exponer una estructura básica de Ado.NET para las diversas operaciones que podemos hacer una fuente de datos, consultar, insertar, actualizar, y eliminar, y que esta estructura puede ser usada con cualquier proveedor disponible en .Net.

En el artículo usaremos las clases: XyzConnection, XyzCommand, XyzDataReader, y un XyzParameter, para presentar las clases base que después pueden ser reemplazadas (en la mayoría de casos) por las siguientes clases de acuerdo a los proveedores:

  1. SQL Server: SqlConnection, SqlCommand, SqlDataReader, y SqlParameter.
  2. SQL Server CE: SqlCeConnection, SqlCeCommand, SqlCeDataReader, y SqlCeParameter.
  3. Oracle (ODP.Net): OracleConnection, OracleCommand, OracleDataReader, y OracleParameter. Hay un proveedor de MS: Microsoft’s .NET for Oracle Provider, pero el Oracle Data Provider for .NET, es el oficial de la gentita de Oracle.
  4. DB2: DB2Connection, DB2Command, DB2DataReader, y DB2Parameter.
  5. MySQL: MySqlConnection, MySqlCommand, MySqlDataReader, y MySqlParameter. Ejemplos: Connector/NET Examples and Usage Guide.
  6. PostgreSQL: NpgsqlConnection, NpgsqlCommand, NpgsqlDataReader, y NpgsqlParameter. Ejemplos: Npgsql 2.0 User Manual.
  7. VistaDB: VistaDBConnection, VistaDBCommand, VistaDBDataReader, y VistaDBParameter.
  8. OleDb: OleDbConnection, OleDbCommand, OleDbDataReader, y OleDbParameter.
  9. Odbc: OdbcConnection, OdbcCommand, OdbcDataReader, y OdbcParameter.
  10. Y así…., se entiende la idea?. Ya no pongo más proveedores, por que no acabo la entrada. No se pierda la saga: OleDb vs Odbc.

En el caso que este disponible un proveedor puntual para una fuente de datos, podemos usar OleDb, y si no esta disponible en está, podemos usar Odbc:

  1. Access. Ejemplo de conexión con OleDb. Ejemplo en código: Using ADO .NET – Access and OleDB Part 2. Más ejemplos.
  2. Excel: Ejemplo de conexión con OleDb. Ejemplos en código: Reading Excel (.xls) Files with ADO.NET, y Reading and Writing Excel Spreadsheets Using ADO.NET C# DbProviderFactory. Más ejemplos.
  3. Text Files (CSV, tab, custom): Ejemplo de conexión con OleDb. Ejemplo en código: Using OleDb to Import Text Files (tab, CSV, custom). Más ejemplos.
  4. Se entiende la idea?

 

Código Ejemplo

Y después de tantos links, vayamos con el código propuesto para hacer operaciones (CRUD) contra una fuente de datos.

En nuestro modelo, para enviar o recuperar información no vamos a usar DataSet ni DataTables, vamos a usar objetos y listas (generics) de objetos:

  1. Las listas de objetos son tipadas. Con dataset: Convert.ToInt32(ds.Tables[0].Rows[0][“SupplierID”]), con un List<Supplier>: lstSuppliers[0].SupplierID, se nota la diferencia no?. El dataset también se puede tipar, pero no viene por defecto.
  2. El rendimiento que se puede lograr con una lista puede ser mejor que con DataSets. Revisar: Anti Prácticas .NET: Lectura de Datos con ADO.NET II.
  3. Si están trabajando con ASP.Net, es soportado por el control ObjetDataSource.
  4. Le tengo cariño a los List<Object>.

P.D.: Ojo con esto no quiero desechar a los Dataset y Datatable, quizás para Winforms se le encuentre más beneficios (traer toda la data y filtrar data en memoria), pero en entornos Web la palabra rendimiento es más necesitada, por eso los filtros deben venir desde la base de datos y no en memoria.

Como vamos a usar Listas (generics) de objetos para pasar entre nuestros métodos/capas, vamos a tener que definir nuestras entidades (capa de Entidades). La definición de nuestro entidad Category (Category.cs):

   1: using System;
   2:  
   3: namespace Northwind.BO
   4: {
   5:  public  class Cateogory
   6:   {
   7:  
   8:    private Int32 categoryIDField;   
   9:    public Int32 CategoryID
  10:    {
  11:      get { return categoryIDField; }
  12:      set { categoryIDField = value; }
  13:    }
  14:  
  15:    private String categoryNameField;
  16:    public String CategoryName
  17:    {
  18:      get { return categoryNameField; }
  19:      set { categoryNameField = value; }
  20:    }
  21:  
  22:    private String descriptionField;
  23:    public String Description
  24:    {
  25:      get { return descriptionField; }
  26:      set { descriptionField = value; }
  27:    }
  28:  
  29:   }
  30: }

Esta clase (entidad) normalmente tiene un atributo por cada columna de la tabla a mapear. En este caso hemos mapeado la tabla Category, de la famosísima Northwind. Nota: Si es una aplicación Web ASP.NET, y va usar el control ObjectDataSource, no se olvide de usar el atributo: DataObjectField. Leer más.

Ahora vamos a implementar la clase de acceso a datos (capa de acceso a datos) XyzCategory. Esta sólo es una clase base, y podemos hacer el uso de patrones, interfaces, o custom, para hacer más genérica esta estructura, y que sea el contrato para cualquier operación con la base de datos. Por hacer más simples los métodos, quedo pendiente el manejo de excepciones, pero cuando intente borrar un registro con dependencias notará su necesidade. No se olvide de revisar el uso de Using (con lección de Unai incluida).

   1: using System;
   2: using System.Collections.Generic;
   3: using System.Configuration;
   4: using System.Data.XyzClient;
   5: using Northwind.BO;
   6:  
   7: namespace Northwind.DA
   8: {
   9:   public class XyzCategory
  10:   {
  11:     private String connectionString;
  12:  
  13:     public XyzCategory(String connectionName)
  14:     {
  15:       connectionString =
  16:         ConfigurationManager.ConnectionStrings[connectionName].ConnectionString;
  17:     }
  18:  
  19:     #region getAll , getOne
  20:  
  21:     public List<Cateogory> getAll()
  22:     {   
  23:     }
  24:  
  25:     public Cateogory getOneByID(Int32 catID)
  26:     {      
  27:     }
  28:  
  29:     #endregion
  30:  
  31:     #region Insert, Update, Delete
  32:  
  33:     public Int32 Insert(Cateogory objCat)
  34:     {    
  35:     }
  36:  
  37:     public Boolean Update(Cateogory objCat)
  38:     {    
  39:     }
  40:  
  41:     public Boolean Delete(Int32 categoryID)
  42:     {    
  43:     }
  44:  
  45:     #endregion
  46:   }
  47: }

Vayamos con los métodos específicos, el método getAll vamos a traer la lista de registros. Si están en una aplicación Web la recomendación es traer la información paginada para no traer todos los resultados, y si estamos usando un ObjectDataSource tendríamos que agregar un método Count (será tema de otro post). Nota: para los string que contienen los queries vamos a usar: cadenas verbatim.

   1: public List<Cateogory> getAll()
   2: {
   3:   List<Cateogory> lstSup = new List<Cateogory>();    
   4:  
   5:   //crear conexion
   6:   using (XyzConnection xxxCn = new XyzConnection())
   7:   {
   8:  
   9:     String cmdText = @"SELECT categoryID, categoryName, Description  
  10:                        FROM Categories";
  11:     //crear comando
  12:     using (XyzCommand cmd = new XyzCommand(cmdText, xxxCn))
  13:     {
  14:       //abrir conexion
  15:       cmd.Connection.Open();
  16:  
  17:       //recorrer resultados
  18:       using (XyzDataReader rd = cmd.ExecuteReader())
  19:       {
  20:  
  21:         Cateogory objCat = null;
  22:  
  23:         //recuperar el orden de columnas
  24:         Int32 categoryIDIndex = rd.GetOrdinal("categoryID");
  25:         Int32 categoryNameIndex = rd.GetOrdinal("categoryName");
  26:         Int32 descriptionIndex = rd.GetOrdinal("Description");
  27:  
  28:         Int32 colCount = rd.FieldCount;
  29:         Object[] values = new Object[colCount];
  30:  
  31:         while (rd.Read())
  32:         {
  33:           objCat = new Cateogory();
  34:  
  35:           //obtener todos los valores
  36:           rd.GetValues(values);
  37:  
  38:           objCat.CategoryID = Convert.ToInt32(values[categoryIDIndex]);
  39:           objCat.CategoryName = values[categoryNameIndex].ToString();
  40:           //en el caso de que la columna sea null
  41:           //Null to Int = (exception)
  42:           if ( !(values[descriptionIndex] is DBNull))
  43:           {
  44:             objCat.Description = values[descriptionIndex].ToString();
  45:           }
  46:          
  47:           //agregar objecto a la lista
  48:           lstSup.Add(objCat);
  49:         }
  50:       }
  51:     }
  52:   }
  53:  
  54:   return lstSup;
  55: }

Ahora el método para devolver un registro, si va usar SQL dinámico, es recomendable usar consultas parametrizadas para enviar los ataques SQL-Injection:

   1: public Cateogory getOneByID(Int32 catID)
   2: {
   3:   Cateogory objCat = null;
   4:  
   5:   if (catID > 0)
   6:   {
   7:  
   8:     //connection
   9:     using (XyzConnection xxxCn = new XyzConnection())
  10:     {
  11:  
  12:       String cmdText = @"SELECT categoryID, categoryName, Description
  13:                          FROM Categories 
  14:                          WHERE CategoryID=@categoryID";
  15:       //comando
  16:       using (XyzCommand cmd = new XyzCommand(cmdText, xxxCn))
  17:       {           
  18:         cmd.Connection.Open();
  19:  
  20:         //consulta parametrizada 
  21:         //evita ataques SQL-Injection
  22:         XyzParameter param = new XyzParameter("categoryID", catID);
  23:         cmd.Parameters.Add(param);
  24:  
  25:         using (XyzDataReader rd = cmd.ExecuteReader())
  26:         {
  27:  
  28:           Int32 categoryIDIndex = rd.GetOrdinal("categoryID");
  29:           Int32 categoryNameIndex = rd.GetOrdinal("categoryName");
  30:           Int32 descriptionIndex = rd.GetOrdinal("Description");
  31:  
  32:           Int32 colCount = rd.FieldCount;
  33:           Object[] values = new Object[colCount];
  34:  
  35:           while (rd.Read())
  36:           {
  37:             objCat = new Cateogory();
  38:  
  39:             rd.GetValues(values);
  40:             objCat.CategoryID = Convert.ToInt32(values[categoryIDIndex]);
  41:             objCat.CategoryName = values[categoryNameIndex].ToString();
  42:             if (!(values[descriptionIndex] is DBNull))
  43:             {
  44:               objCat.Description = values[descriptionIndex].ToString();
  45:             }
  46:           }
  47:         }
  48:       }          
  49:     }                
  50:   }
  51:  
  52:   return objCat;
  53: }

Método insertar, notar como pasamos todos los parámetros del registro a través de la entidad. No se pierda la saga @@Identity vs SCOPE_IDENTITY (Only SQL).

   1: public Int32 Insert(Cateogory objCat)
   2: {
   3:   Int32 catID = 0;
   4:  
   5:   using (XyzConnection xxxCn = new XyzConnection())
   6:   {
   7:  
   8:     String cmdText = @"INSERT INTO Categories (categoryName, Description)
   9:                        VALUES (@categoryName, @description) 
  10:                        SELECT SCOPE_IDENTITY() ";
  11:     //comando
  12:     using (XyzCommand cmd = new XyzCommand(cmdText, xxxCn))
  13:     {
  14:       cmd.Connection.Open();
  15:  
  16:       //parametros
  17:       XyzParameter param = 
  18:           new XyzParameter( "categoryID", objCat.CategoryName);
  19:       cmd.Parameters.Add(param);
  20:       param = new XyzParameter("Description", objCat.Description);
  21:       cmd.Parameters.Add(param);
  22:  
  23:       catID = Convert.ToInt32(cmd.ExecuteScalar());
  24:       
  25:     }       
  26:   }     
  27:   return catID;
  28: }

Método Actualizar, si van tener varios tipos de actualización como actualizar registro y actualizar estado, podría crear dos métodos Update con distinto nombre, y cada uno con el query específico y usando sólo las propiedades que necesite:

   1: public Boolean Update(Cateogory objCat)
   2: {
   3:   Boolean exito = false;
   4:  
   5:   using (XyzConnection xxxCn = new XyzConnection())
   6:   {
   7:  
   8:     String cmdText = @"UPDATE  Categories 
   9:                        SET categoryName = @categoryName, 
  10:                            Description =  @description
  11:                        WHERE categoryID = @categoryID ";       
  12:     using (XyzCommand cmd = new XyzCommand(cmdText, xxxCn))
  13:     {
  14:       cmd.Connection.Open();
  15:       
  16:       XyzParameter param =
  17:           new XyzParameter("Description", objCat.Description);
  18:       cmd.Parameters.Add(param);
  19:       param = new XyzParameter("categoryName", objCat.CategoryName);
  20:       cmd.Parameters.Add(param);
  21:       param = new XyzParameter("categoryID", objCat.CategoryID);
  22:       cmd.Parameters.Add(param);
  23:  
  24:       exito = Convert.ToBoolean(cmd.ExecuteNonQuery());
  25:  
  26:     }      
  27:   }     
  28:  
  29:   return exito;
  30: }

Y con el último método, eliminar, para cerrar la entrada. Nota: no se olvide de revisar el tema de manejo de excepciones.

   1: public Boolean Delete(Int32 categoryID)
   2: {
   3:   Boolean exito = false;
   4:  
   5:   using (XyzConnection xxxCn = new XyzConnection())
   6:   {
   7:  
   8:     String cmdText = @"DELETE  Categories                            
   9:                        WHERE categoryID = @categoryID ";      
  10:     using (XyzCommand cmd = new XyzCommand(cmdText, xxxCn))
  11:     {
  12:       cmd.Connection.Open();
  13:      
  14:       XyzParameter param =
  15:           new XyzParameter("categoryID", categoryID);
  16:       cmd.Parameters.Add(param);    
  17:  
  18:       exito = Convert.ToBoolean(cmd.ExecuteNonQuery());
  19:  
  20:     }        
  21:   }    
  22:  
  23:   return exito;
  24: }

P.D.: Preguntas (con respecto a la estructura), sugerencias, bugs, en los comentarios por favor :D. A medida del tiempo, y bajo demanda trataré de enviar ejemplos específicos para un determinado proveedor .Net.

P.D.2.: Si tiene preguntas técnicas con respecto algún código en alguno de estos providers, les recomendaría visitar algunos de los foros de estos productos (.Net Providers), quizás en algún hilo del foro ya se encuentre la solución.

Saludos,