Colaboraciones en el Guille

El paradigma SAV (2ª Parte)

Continuamos con nuestro 'control remoto' para encriptar.

Fecha: 15/Oct/2005 (13-10-05)
Autor: Raul Carrillo Garrido aka metsuke (rcarrillo@metsuke.com)

Este artículo es la continuación de El paradigma SAV ('Simply A Video'). Si no lo has leído te recomiendo que le eches un vistazo para poder seguir fácilmente las explicaciones siguientes.


Antes de entrar en materia... un pequeño juguete al mas puro 'Simply Style'.

En el artículo anterior, dejamos nuestro control remoto justo después de completar la desencriptación simétrica, y estaba previsto continuar directamente con la encriptación asimétrica y algoritmos hash, pero voy a hacer una pequeña parada para mostrar lo rápido que se puede construir nueva funcionalidad basándonos en nuestro control remoto, sin renunciar por ello a la robustez y simplicidad de mantenimiento.

Vamos a construir un par de funciones que nos permitan encriptar y desencriptar de forma simétrica pero empleando los cuatro proveedores en secuencia. Lo cierto es que no se si esto aumenta o disminuye la seguridad de la cadena encriptada, pero nos servirá como ejercicio:

 Imports EncryptionClassLibrary 

Namespace Cryptography
Public Class Crypto

Private pvSymmetricKey As String = "1r+!1-esKªPZyL"

#Region "Enums" [...]

#Region "Symmetric" [...]

#Region "SymmetricAll"

Public Function CryptSymmetricAll( _
ByVal pString As String _
, Optional ByVal pFormat As CryptedStringFormat = CryptedStringFormat.Hex _
) As String
Try
pString = CryptSymmetric(pString, pFormat, SymmetricEncryptionMethod.DES)
pString = CryptSymmetric(pString, pFormat, SymmetricEncryptionMethod.RC2)
pString = CryptSymmetric(pString, pFormat, SymmetricEncryptionMethod.TripleDES)
pString = CryptSymmetric(pString, pFormat, SymmetricEncryptionMethod.Rijndael)
Return pString
Catch ex As Exception
Throw
End Try
End Function

Public Function DecryptSymmetricAll( _
ByVal pString As String _
, Optional ByVal pFormat As CryptedStringFormat = CryptedStringFormat.Hex _
) As String
Try
pString = DecryptSymmetric(pString, pFormat, SymmetricEncryptionMethod.Rijndael)
pString = DecryptSymmetric(pString, pFormat, SymmetricEncryptionMethod.TripleDES)
pString = DecryptSymmetric(pString, pFormat, SymmetricEncryptionMethod.RC2)
pString = DecryptSymmetric(pString, pFormat, SymmetricEncryptionMethod.DES)
Return pString
Catch ex As Exception
Throw
End Try
End Function
#End Region

#Region "Properties" [...]

End Class
End Namespace

Como podemos observar, nada diferente a lo que ya habíamos realizado en el resto de la clase, lo único a destacar es lo fácilmente que hemos programado la nueva funcionalidad utilizando las funciones previamente encapsuladas, y que nuevamente hacemos uso de parámetros opcionales en lugar de las sobrecargas.

Parámetros Opcionales vs. Sobrecarga

Tal y como comenté en El paradigma SAV ('Simply A Video'), sigo defendiendo el uso de parámetros opcionales siempre que se pueda conseguir la misma funcionalidad que con las sobrecargas, pero últimamente he leído algunos argumentos de Francesco Balena a favor de lo contrario que han sembrado en mí dudas más que razonables con respecto a la 'validez objetiva' de ese planteamiento.

Por ello tengo previsto realizar un artículo monográfico sobre esta cuestión en el que me gustaría salir de dudas realizando una comparativa lo más objetiva posible entre los dos sistemas tomando como medidas:

  1. Tamaño de ejecutable generado.
  2. Consumo de memoria de la aplicación al ser ejecutada.
  3. Medidas de rendimiento real en cuanto a tiempo utilizando NTime.
  4. Facilidad o no del uso de las clases generadas en otros lenguajes de la plataforma .NET.

Si a alguien le parece interesante que tenga en cuenta algún otro parámetro, ruego me lo indique por email a rcarrillo@metsuke.com indicando en el subject 'Comentario Articulo SAV' para tenerlo en cuenta. Gracias por adelantado.

Y ahora a por la Encriptación Asimétrica.

Tomando como base las recomendaciones del autor de la clase Simple Encryption, generamos las rutinas para encriptar y desencriptar. Lo primero que necesitamos son dos variables privadas que almacenen la clave pública y la clave privada, así como la inicialización de las mismas a través de los constructores.

Observando el código que reproduzco a continuación, podemos observar dos constructores, uno sin parámetros y otro que permite proporcionar una clave para encriptación simétrica. Ambos constructores llaman a un procedimiento que se encarga de generar claves las publica y privada 'por defecto' para la instancia (cumpliendo el requerimiento SAV de que funcione con el mínimo necesario de información por parte del usuario pero permitiendo configurar el mayor numero de elementos posible):

 Imports EncryptionClassLibrary 

Namespace Cryptography
Public Class Crypto

Private pvSymmetricKey As String = "1r+!1-esKªPZyL"
Private pvAsymmetricPublicKey As New Encryption.Asymmetric.PublicKey
Private pvAsymmetricPrivateKey As New Encryption.Asymmetric.PrivateKey

#Region "Constructor"
Public Sub New()
Try
InitializeAssymmetricKeys()
Catch ex As Exception
Throw
End Try
End Sub

Public Sub New(ByVal pSymmetricKey As String)
Try
Me.SymmetricKeyWord = pSymmetricKey
InitializeAssymmetricKeys()
Catch ex As Exception
Throw
End Try
End Sub

Private Sub InitializeAssymmetricKeys()
Try
Dim asym As New Encryption.Asymmetric
asym.GenerateNewKeyset(pvAsymmetricPublicKey, pvAsymmetricPrivateKey)
Catch ex As Exception
Throw
End Try
End Sub

#End Region

#Region "Enums" [...]

#Region "Asymmetric"
Public Function CryptAsymmetric( _
ByVal pString As String _
, Optional ByVal pFormat As CryptedStringFormat = CryptedStringFormat.Base64 _
) As String [...]

Public Function DecryptAsymmetric( _
ByVal pString As String _
, Optional ByVal pFormat As CryptedStringFormat = CryptedStringFormat.Base64 _
) As String [...]

#End Region

#Region "Symmetric" [...]

#Region "SymmetricAll" [...]

#Region "Properties"
Public Property SymmetricKeyWord() As String [...]

Public Property AsymmetricPublicKey() As String [...]

Public Property AsymmetricPrivateKey() As String [...]
#End Region

End Class
End Namespace

Podemos observar que hemos generado, además de lo descrito anteriormente, dos nuevas funciones para encriptar y desencriptar, así como sendas propiedades que nos permitirán proporcionar y recoger las claves pública y privada, en caso de que deseemos utilizar claves diferentes.

CryptAsymmetric y DecryptAsymmetric

Veamos las funciones para encriptar y desencriptar:

 ... 
#Region "Asymmetric"
Public Function CryptAsymmetric( _
ByVal pString As String _
, Optional ByVal pFormat As CryptedStringFormat = CryptedStringFormat.Base64 _
) As String
Try

If pString.Length > 117 Then
Throw New System.ArgumentException( "Limite de tamaño 117 caracteres para este tipo de encriptacion")
Else

Dim asym As New Encryption.Asymmetric

Dim EncryptedData As Encryption.Data
EncryptedData = asym.Encrypt(New Encryption.Data(pString), pvAsymmetricPublicKey)

Select Case pFormat
Case CryptedStringFormat.Base64 : Return EncryptedData.Base64
Case CryptedStringFormat.Hex : Return EncryptedData.Hex
End Select
End If
Catch ex As ArgumentException 'Por tamaño de la cadena a encriptar.
Throw
Catch ex As Exception
Throw
End Try
End Function

Public Function DecryptAsymmetric( _
ByVal pString As String _
, Optional ByVal pFormat As CryptedStringFormat = CryptedStringFormat.Base64 _
) As String
Try
Dim Asym As New Encryption.Asymmetric
Dim decryptedData As New Encryption.Data

Select Case pFormat
Case CryptedStringFormat.Base64 : decryptedData.Base64 = pString
Case CryptedStringFormat.Hex : decryptedData.Hex = pString
End Select

decryptedData = Asym.Decrypt(decryptedData, pvAsymmetricPrivateKey)
Return decryptedData.Text

Catch ex As Exception
Throw
End Try
End Function
#End Region
...

Como podemos observar, el formato de las funciones no muestra ningún detalle técnico destacable. El único elemento nuevo es la comprobación del tamaño de la cadena a encriptar, y es que a partir de la longitud indicada, se produce una excepción (el método asimétrico está pensado para cadenas relativamente cortas, ya que es un método 'caro' en términos de recursos y velocidad), así que para simplificar la depuración se comprueba la longitud y en caso de que supere el límite permitido, se lanza una excepción explicando en detalle la causa del problema, de modo que el programador no tenga que volverse loco buscando un porqué.

Los Properties

En relación a la este tipo de encriptación solo queda mostrar las dos propiedades que nos permiten asignar y obtener las claves pública y privada. .NET nos proporciona la posibilidad de obtener y asignar dichas claves en un formato XML fácilmente manejable, hecho que aprovecharemos para simplificar el proceso al máximo:

 #Region "Properties" 
Public Property SymmetricKeyWord() As String [...]

Public Property AsymmetricPublicKey() As String
Get
Try
Return pvAsymmetricPublicKey.ToXml.ToString
Catch ex As Exception
Throw
End Try
End Get
Set(ByVal Value As String)
Try
pvAsymmetricPublicKey.LoadFromXml(Value)
Catch ex As Exception
Throw
End Try
End Set
End Property

Public Property AsymmetricPrivateKey() As String
Get
Try
Return pvAsymmetricPrivateKey.ToXml.ToString
Catch ex As Exception
Throw
End Try
End Get
Set(ByVal Value As String)
Try
pvAsymmetricPrivateKey.LoadFromXml(Value)
Catch ex As Exception
Throw
End Try
End Set
End Property
#End Region

Como podemos ver, salvo el detalle de la asignación y obtención de las claves en formato XML, no hay nada que destacar en esta porción del código de nuestro control remoto.

Algoritmos Hash

Solo nos queda completar la primera versión de nuestra clase incorporando la función que implementa los algoritmos hash de Simple Encryption:

 Imports EncryptionClassLibrary 

Namespace Cryptography
Public Class Crypto

Private pvSymmetricKey As String = "1r+!1-esKªPZyL"
Private pvAsymmetricPublicKey As New Encryption.Asymmetric.PublicKey
Private pvAsymmetricPrivateKey As New Encryption.Asymmetric.PrivateKey

#Region "Constructor" [...]

#Region "Enums"
Public Enum CryptedStringFormat
Hex = 1
Base64 = 2
End Enum

Public Enum SymmetricEncryptionMethod
DES = 1
RC2 = 2
TripleDES = 4
Rijndael = 8
End Enum

Public Enum HashEncryptionMethod
SHA1 = 1
SHA256 = 2
SHA384 = 4
SHA512 = 8
MD5 = 16
CRC32 = 32
End Enum
#End Region

#Region "Asymmetric" [...]

#Region "Hash"

Public Function CalculateHash( _
ByVal pString As String _
, Optional ByVal pFormat As CryptedStringFormat = CryptedStringFormat.Hex _
, Optional ByVal pHashAlgorithm As HashEncryptionMethod = HashEncryptionMethod.SHA512 _
) As String
Try

Dim HashObject As Encryption.Hash
Dim HashBaseData As New Encryption.Data(pString)

Select Case pHashAlgorithm
Case HashEncryptionMethod.CRC32
HashObject = New Encryption.Hash(Encryption.Hash.Provider.CRC32)
Case HashEncryptionMethod.MD5
HashObject = New Encryption.Hash(Encryption.Hash.Provider.MD5)
Case HashEncryptionMethod.SHA1
HashObject = New Encryption.Hash(Encryption.Hash.Provider.SHA1)
Case HashEncryptionMethod.SHA256
HashObject = New Encryption.Hash(Encryption.Hash.Provider.SHA256)
Case HashEncryptionMethod.SHA384
HashObject = New Encryption.Hash(Encryption.Hash.Provider.SHA384)
Case HashEncryptionMethod.SHA512
HashObject = New Encryption.Hash(Encryption.Hash.Provider.SHA512)
End Select

Select Case pFormat
Case CryptedStringFormat.Base64
Return HashObject.Calculate(HashBaseData).Base64
Case CryptedStringFormat.Hex
Return HashObject.Calculate(HashBaseData).Hex
End Select

Catch ex As Exception
Throw
End Try

End Function

#End Region

#Region "Symmetric" [...]

#Region "SymmetricAll" [...]

#Region "Properties" [...]

End Class
End Namespace

Como podemos ver nada fuera de lo común: un enum para establecer los distintos tipos de algoritmos hash disponibles, parámetros opcionales (esta vez se ha tomado como formato de devolución por defecto Hex) y poco más.

Legibilidad del Código Fuente

A lo largo de estos dos artículos quizá habréis notado cierta tendencia a la 'simetría' en la colocación en el código. Esto no es algo aleatorio, considero tanto o más importante que la simplicidad del código en sí, el que este , en la medida de lo posible, sea legible mediante la reducción de la 'información consciente' a evaluar para que nuestro cerebro interprete adecuadamente el código fuente que observa.

Al hablar de legibilidad no pretendo imponer unos formatos de codificación extremadamente estrictos, ya que la excesiva dureza en dichos estándares lleva bien a una reducción drástica de productividad o a ignorarlos completamente, lo cual no ayuda nada al proyecto. En su lugar abogo por un 'compromiso individual de legibilidad' según el cual y siempre siguiendo un conjunto de normas mínimo (a pactar dentro del equipo de desarrollo), la legibilidad se convierta un parámetro más a tomar en consideración al planificar cualquier bloque de código.

De este modo y de forma natural el código queda suficientemente legible sin afectar negativamente a la productividad. Es un equilibrio delicado y cuya 'formula' hay que ajustar a cada proyecto en función del nivel de compromiso profesional del grupo de desarrolladores. En mi caso particular hay dos cuestiones que suelo utilizar siempre que puedo:

Colocar el código de forma que las 'diferencias' queden destacadas.

Formatos del tipo 'Select Case' que podemos ver en código anterior ayuda a que nuestro cerebro tome automáticamente la estructura como una 'lista de elementos a seleccionar' y que se centre en la parte final de cada línea para saber que hace el código basándose en las diferencias, ya que el resto del código es 'obvio'.

Incluso en un if..else hay veces que es mejor usar select case simplemente por cuestiones de legibilidad. Observemos el código siguiente:

 If (metodoEncriptacion = "Method 1") Then 
provEncriptacion = "Proveedor 1"
Else 'Method 2
provEncriptacion = "Proveedor 2"
End If

El código en sí es válido, la única razón para cambiarlo bien podría ser la legibilidad:

 Select Case metodoEncriptacion 
Case "Method 1": provEncriptacion = "Proveedor 1"
Case Else: provEncriptacion = "Proveedor 2" 'Method 2
End Select

O incluso mejor:

 Select Case metodoEncriptacion 
Case "Method 1": provEncriptacion = "Proveedor 1"
Case "Method 2": provEncriptacion = "Proveedor 2"
Case Else: provEncriptacion = "Proveedor Default"
End Select

Personalmente me gusta mucho más la tercera opción, principalmente porque mi cerebro asume la lógica del código y la relación entre método y proveedor sin necesidad de pensar, pero cada cual debe juzgar cual es el método de 'legibilidad' más acorde con sus gustos y necesidades.

Espaciado del código

Es común encontrarse con código fuente en el que de forma sistemática se tiende a no dejar nunca líneas en blanco entre bloques. Personalmente prefiero jugar con esas líneas en blanco para dotar al código de una 'organización por bloques' de modo que cada bloque esté relacionado con una idea. Por ejemplo en el código que implementa el algoritmo hash podemos ver un primer bloque con declaraciones e inicializaciones, un segundo bloque 'que ejecuta' la acción, y un último bloque 'de devolución'. Todo esto lo vemos sin necesidad de un solo comentario.

Creo que la mejor forma de mostrarlo es ver el mismo código sin los espacios, y que cada uno compare la legibilidad con el bloque anterior en el que se incluían las líneas en blanco:

 Imports EncryptionClassLibrary 
Namespace Cryptography
Public Class Crypto
Private pvSymmetricKey As String = "1r+!1-esKªPZyL"
Private pvAsymmetricPublicKey As New Encryption.Asymmetric.PublicKey
Private pvAsymmetricPrivateKey As New Encryption.Asymmetric.PrivateKey
#Region "Constructor" [...]
#Region "Enums"
Public Enum CryptedStringFormat
Hex = 1
Base64 = 2
End Enum
Public Enum SymmetricEncryptionMethod
DES = 1
RC2 = 2
TripleDES = 4
Rijndael = 8
End Enum
Public Enum HashEncryptionMethod
SHA1 = 1
SHA256 = 2
SHA384 = 4
SHA512 = 8
MD5 = 16
CRC32 = 32
End Enum
#End Region
#Region "Asymmetric" [...]
#Region "Hash"
Public Function CalculateHash( _
ByVal pString As String _
, Optional ByVal pFormat As CryptedStringFormat = CryptedStringFormat.Hex _
, Optional ByVal pHashAlgorithm As HashEncryptionMethod = HashEncryptionMethod.SHA512 _
) As String
Try
Dim HashObject As Encryption.Hash
Dim HashBaseData As New Encryption.Data(pString)
Select Case pHashAlgorithm
Case HashEncryptionMethod.CRC32
HashObject = New Encryption.Hash(Encryption.Hash.Provider.CRC32)
Case HashEncryptionMethod.MD5
HashObject = New Encryption.Hash(Encryption.Hash.Provider.MD5)
Case HashEncryptionMethod.SHA1
HashObject = New Encryption.Hash(Encryption.Hash.Provider.SHA1)
Case HashEncryptionMethod.SHA256
HashObject = New Encryption.Hash(Encryption.Hash.Provider.SHA256)
Case HashEncryptionMethod.SHA384
HashObject = New Encryption.Hash(Encryption.Hash.Provider.SHA384)
Case HashEncryptionMethod.SHA512
HashObject = New Encryption.Hash(Encryption.Hash.Provider.SHA512)
End Select
Select Case pFormat
Case CryptedStringFormat.Base64
Return HashObject.Calculate(HashBaseData).Base64
Case CryptedStringFormat.Hex
Return HashObject.Calculate(HashBaseData).Hex
End Select
Catch ex As Exception
Throw
End Try
End Function
#End Region
#Region "Symmetric" [...]
#Region "SymmetricAll" [...]
#Region "Properties" [...]
End Class
End Namespace

Entiendo que la imagen habla por sí misma, aunque insisto en que cada uno debe aplicar las medidas con respecto a sus propias convicciones, esto es solo un ejemplo de un método posible, no es el único ni probablemente el mejor.

End Try

A través de estos artículos he tratado de mostrar un método de trabajo y filosofía de codificación basada en la simplificación, de modo que no nos pongamos trabas nosotros mismos al desarrollar. Bastante complicado es ya como para tropezarnos voluntariamente por pisarnos los cordones de los zapatos.

En futuros artículos , además de la comparativa entre parámetros opcionales y sobrecarga de métodos (para salir de dudas), procuraré abordar el proyecto savLib, cuyo objetivo es simplificar distintas funcionalidades de forma que podamos recurrir a ellas en cualquier momento con la misma facilidad que usaremos con este control remoto la encriptación en .NET.

Espero que hayan disfrutado leyendo este artículo tanto como yo escribiéndolo. No olviden que pueden dirigir cualquier aportación, comentario y/o crítica (siempre constructiva, apliquemos la metodología SAV también a esto) a rcarrillo@metsuke.com.


Espacios de nombres usados en el código de este artículo:

System.Security.Cryptography (usado por la librería sobre la que se basa el articulo)


Fichero con el código de ejemplo: metsuke_savIntroduccion2.zip - 4 KB


Creative Commons License


ir al índice principal del Guille