Access: Encriptar contraseñas con SHA-256 utilizando biblioteca de clases .NET con C#

Libreria Encriptar SHA256

Después de un tiempo centrado en las pruebas de rendimiento de Access, hago un inciso (sobre todo porque me lo pide el cuerpo) y voy a retomar en este artículo la interoperabilidad COM.

Es posible que si habéis seguido los anteriores artículos sobre interoperabilidad COM, tengáis la sensación de que hemos abordado en profundidad la manera de hacer compatibles las bibliotecas, pero no hemos visto su funcionalidad en «fuego real». Por ello, voy a volver sobre un artículo del pasado en el que explicaba como Encriptar contraseñas con SHA-2 de 256 bits utilizando VBA y voy a hacerlo después utilizando un biblioteca de clases .NET.

En el mencionado artículo, utilizaba un módulo de VBA para encriptar las contraseñas. Vamos a recordar el código de dicho módulo:

Attribute VB_Name = "SHA256"
Option Compare Database

'********************************************************************************************************************************************
'********************************************************************************************************************************************
'********************************************************************************************************************************************
'Algoritmo para encriptar contraseñas con SHA2 - 256 bits

    Function SHA(ByVal sMessage)
        Dim i, result(32), temp(8) As Double, fraccubeprimes, hashValues
        Dim done512, index512, words(64) As Double, index32, mask(4)
        Dim s0, s1, t1, t2, maj, ch, strLen

        mask(0) = 4294967296#
        mask(1) = 16777216
        mask(2) = 65536
        mask(3) = 256

        hashValues = Array( _
            1779033703, 3144134277#, 1013904242, 2773480762#, _
            1359893119, 2600822924#, 528734635, 1541459225)

        fraccubeprimes = Array( _
            1116352408, 1899447441, 3049323471#, 3921009573#, 961987163, 1508970993, 2453635748#, 2870763221#, _
            3624381080#, 310598401, 607225278, 1426881987, 1925078388, 2162078206#, 2614888103#, 3248222580#, _
            3835390401#, 4022224774#, 264347078, 604807628, 770255983, 1249150122, 1555081692, 1996064986, _
            2554220882#, 2821834349#, 2952996808#, 3210313671#, 3336571891#, 3584528711#, 113926993, 338241895, _
            666307205, 773529912, 1294757372, 1396182291, 1695183700, 1986661051, 2177026350#, 2456956037#, _
            2730485921#, 2820302411#, 3259730800#, 3345764771#, 3516065817#, 3600352804#, 4094571909#, 275423344, _
            430227734, 506948616, 659060556, 883997877, 958139571, 1322822218, 1537002063, 1747873779, _
            1955562222, 2024104815, 2227730452#, 2361852424#, 2428436474#, 2756734187#, 3204031479#, 3329325298#)

        sMessage = Nz(sMessage, "")
        strLen = Len(sMessage) * 8
        sMessage = sMessage & Chr(128)
        done512 = False
        index512 = 0

        If (Len(sMessage) Mod 64) < 60 Then
            sMessage = sMessage & String(60 - (Len(sMessage) Mod 64), Chr(0))
        ElseIf (Len(sMessage) Mod 64) > 60 Then
            sMessage = sMessage & String(124 - (Len(sMessage) Mod 64), Chr(0))
        End If

        sMessage = sMessage & Chr(Int((strLen / mask(0) - Int(strLen / mask(0))) * 256))
        sMessage = sMessage & Chr(Int((strLen / mask(1) - Int(strLen / mask(1))) * 256))
        sMessage = sMessage & Chr(Int((strLen / mask(2) - Int(strLen / mask(2))) * 256))
        sMessage = sMessage & Chr(Int((strLen / mask(3) - Int(strLen / mask(3))) * 256))

        Do Until done512
            For i = 0 To 15
                words(i) = Asc(Mid(sMessage, index512 * 64 + i * 4 + 1, 1)) * mask(1) + Asc(Mid(sMessage, index512 * 64 + i * 4 + 2, 1)) * mask(2) + Asc(Mid(sMessage, index512 * 64 + i * 4 + 3, 1)) * mask(3) + Asc(Mid(sMessage, index512 * 64 + i * 4 + 4, 1))
            Next

            For i = 16 To 63
                s0 = largeXor(largeXor(rightRotate(words(i - 15), 7, 32), rightRotate(words(i - 15), 18, 32), 32), Int(words(i - 15) / 8), 32)
                s1 = largeXor(largeXor(rightRotate(words(i - 2), 17, 32), rightRotate(words(i - 2), 19, 32), 32), Int(words(i - 2) / 1024), 32)
                words(i) = Mod32Bit(words(i - 16) + s0 + words(i - 7) + s1)
            Next

            For i = 0 To 7
                temp(i) = hashValues(i)
            Next

            For i = 0 To 63
                s0 = largeXor(largeXor(rightRotate(temp(0), 2, 32), rightRotate(temp(0), 13, 32), 32), rightRotate(temp(0), 22, 32), 32)
                maj = largeXor(largeXor(largeAnd(temp(0), temp(1), 32), largeAnd(temp(0), temp(2), 32), 32), largeAnd(temp(1), temp(2), 32), 32)
                t2 = Mod32Bit(s0 + maj)
                s1 = largeXor(largeXor(rightRotate(temp(4), 6, 32), rightRotate(temp(4), 11, 32), 32), rightRotate(temp(4), 25, 32), 32)
                ch = largeXor(largeAnd(temp(4), temp(5), 32), largeAnd(largeNot(temp(4), 32), temp(6), 32), 32)
                t1 = Mod32Bit(temp(7) + s1 + ch + fraccubeprimes(i) + words(i))

                temp(7) = temp(6)
                temp(6) = temp(5)
                temp(5) = temp(4)
                temp(4) = Mod32Bit(temp(3) + t1)
                temp(3) = temp(2)
                temp(2) = temp(1)
                temp(1) = temp(0)
                temp(0) = Mod32Bit(t1 + t2)
            Next

            For i = 0 To 7
                hashValues(i) = Mod32Bit(hashValues(i) + temp(i))
            Next

            If (index512 + 1) * 64 >= Len(sMessage) Then done512 = True
            index512 = index512 + 1
        Loop

        For i = 0 To 31
            result(i) = Int((hashValues(i \ 4) / mask(i Mod 4) - Int(hashValues(i \ 4) / mask(i Mod 4))) * 256)
        Next

        SHA = result
    End Function

    Function Mod32Bit(value)
        Mod32Bit = Int((value / 4294967296# - Int(value / 4294967296#)) * 4294967296#)
    End Function

    Function rightRotate(value, amount, totalBits)
        'To leftRotate, make amount = totalBits - amount
        Dim i
        rightRotate = 0

        For i = 0 To (totalBits - 1)
            If i >= amount Then
                rightRotate = rightRotate + (Int((value / (2 ^ (i + 1)) - Int(value / (2 ^ (i + 1)))) * 2)) * 2 ^ (i - amount)
            Else
                rightRotate = rightRotate + (Int((value / (2 ^ (i + 1)) - Int(value / (2 ^ (i + 1)))) * 2)) * 2 ^ (totalBits - amount + i)
            End If
        Next
    End Function

    Function largeXor(value, xorValue, totalBits)
        Dim i, a, b
        largeXor = 0

        For i = 0 To (totalBits - 1)
            a = (Int((value / (2 ^ (i + 1)) - Int(value / (2 ^ (i + 1)))) * 2))
            b = (Int((xorValue / (2 ^ (i + 1)) - Int(xorValue / (2 ^ (i + 1)))) * 2))
            If a <> b Then
                largeXor = largeXor + 2 ^ i
            End If
        Next
    End Function

    Function largeNot(value, totalBits)
        Dim i, a
        largeNot = 0

        For i = 0 To (totalBits - 1)
            a = Int((value / (2 ^ (i + 1)) - Int(value / (2 ^ (i + 1)))) * 2)
            If a = 0 Then
                largeNot = largeNot + 2 ^ i
            End If
        Next
    End Function

    Function largeAnd(value, andValue, totalBits)
        Dim i, a, b
        largeAnd = 0

        For i = 0 To (totalBits - 1)
            a = Int((value / (2 ^ (i + 1)) - Int(value / (2 ^ (i + 1)))) * 2)
            b = (Int((andValue / (2 ^ (i + 1)) - Int(andValue / (2 ^ (i + 1)))) * 2))
            If a = 1 And b = 1 Then
                largeAnd = largeAnd + 2 ^ i
            End If
        Next
    End Function

'********************************************************************************************************************************************
'********************************************************************************************************************************************
'********************************************************************************************************************************************
'********************************************************************************************************************************************

Como veis, es lo suficientemente complejo como para que un error nos haga perder mucho tiempo, además de que (aunque funciona correctamente) el encriptado se hace utilizando el algoritmo completo y no utilizando librerías de encriptado ya creadas. Este es la gran diferencia de VBA con la plataforma .NET, hay librerías muy potentes ya creadas y preparadas para que las utilicemos.

Ahora vamos a crear una librería sencillita con Visual Studio que haga lo mismo y que se pueda utilizar en Access. Apara ello, podemos utilizar uno de los métodos de nuestros anteriores artículos sobre interoperabilidad COM, lo dejo a vuestra elección. Yo simplemente voy a crear la librería y después la voy a añadir en las referencias de Access. La función que nos permitirá encriptar las contraseñas es la siguiente:

public string SHA256Encripta(string input)
        {
            SHA256CryptoServiceProvider provider = new SHA256CryptoServiceProvider();

            byte[] inputBytes = Encoding.UTF8.GetBytes(input);
            byte[] hashedBytes = provider.ComputeHash(inputBytes);

            StringBuilder output = new StringBuilder();

            for (int i = 0; i < hashedBytes.Length; i++)
                output.Append(hashedBytes[i].ToString("x2").ToLower());

            return output.ToString();
        }

Y tendremos que añadir la siguiente referencia a nuestro proyecto de Visual Studio:

System.Security.Cryptography;

Así que la librería quedará parecida a lo siguiente (dependiendo del método que utilicemos para la compatibilidad COM):

Libreria Encriptar SHA256

Libreria Encriptar SHA256

Ahora solo tendremos que añadir nuestra nueva librería a las referencias de nuestro proyecto Access. Repasad los artículos sobre interoperabilidad COM si tenéis alguna duda sobre esto.

Una vez añadida voy a mostrar una función sencillita a la que le pasaremos una cadena de texto con la contraseña y nos devolverá el hash de esa misma cadena creado con el algoritmo SHA-2 de 256 bits. Para los que no hayáis leído mis artículos sobre cómo encriptar contraseñas, una pequeña explicación sobre funciones Hash:

Se puede calcular una función Hash (también llamada Digest) de cualquier cadena de texto mediante distintos algoritmos. Una función hash es una especie de «resumen» de la cadena que se le pasa al algoritmo y siempre será la misma para una cadena dada. Lo interesante de estas funciones es que es un proceso irreversible, es decir, a partir de una función hash no se puede llegar a la cadena original (siempre que no consigan romper el algoritmo). Esto las hace muy interesantes para almacenar contraseñas ya que aunque consigan capturarla, no les sirve para acceder al sistema.

Si es irreversible, ¿cómo hacemos para comprobarla? Muy sencillo, simplemente guardamos en nuestra base de datos el hash de la contraseña y en el proceso de login hacemos que el usuario meta su contraseña. Calculamos el hash de la cadena que ha introducido el usuario y la comparamos con la que tenemos guardada. Con esto logramos no tener nunca las contraseñas almacenadas y además evitamos que viajen por la red (protegiéndonos de posibles sniffers).

De momento el algoritmo SHA-2 no ha sido atacado con éxito como otros conocidos algoritmos muy utilizados en el pasado (como MD5 y SHA1 ). Además, vamos a utilizar 256 bits para el encriptado, es decir, el hash tendrá 32 bytes.

Después de esta pequeña explicación, vamos a ver la diferencia de código entre la versión anterior y esta. Vamos con la función:

Function probarNET(ByVal contraseña As String)


Dim Encriptador As New Seguridad.Encriptar
encriptarNET = Encriptador.SHA256Encripta(contraseña)


End Function

Y ya está, con esto tenemos una función que nos calcula el hash con el algoritmo SHA-2 de 256 bits. Vamos a comprobarlo utilizando el mismo ejemplo que en el artículo anterior. Utilizábamos SHA-2 de 256 bits con el resultado truncado a 64 bytes. La palabra pasada era «hola»:

-El hash era b221d9dbb083a7f33428d7c2a3c3198ae925614d70210e28716ccaa7cd4ddb79

Comprobamos ahora con nuestra nueva función pasando la cadena «hola»:

Pasar Hola al algoritmo

Pasar Hola al algoritmo

Ahora agregamos una inspección en la variable en donde guardamos el resultado y…

Resultado incorrecto

Resultado incorrecto

Vaya por dios! el resultado no es el mismo, el algoritmo no funciona correctamente. CUIDADO, le he pasado el valor «Hola» y no el valor «hola» del artículo anterior. Por supuesto, el hash es distinto. Vamos ahora a pasar el valor correcto:

Pasar bien el valor hola

Pasar bien el valor hola

Y probamos de nuevo:

Resultado de correcto del Hash

Resultado de correcto del Hash

Ahora si, el resultado es el correcto. Como veis, el código es mucho más sencillo y la posibilidad de cometer errores mucho menor.

Ahora si que podemos ver la verdadera funcionalidad de crear bibliotecas .NET para utilizarlas en Access. Con una buena cantidad de bibliotecas podremos llevar nuestros proyectos Access a niveles que hace unos años eran impensables y además lo haremos fácilmente y sin necesidad de desarrollar largos algoritmos.

Hay infinidad de bibliotecas .NET ya creadas que podremos utilizar simplemente añadiendo interoperabilidad a la biblioteca. La mayoría funciona sin problemas, aunque tenemos que tener en cuenta que hay cosas que varían un poco (como por ejemplo constructores con parámetros que no se pueden utilizar para interoperabilidad COM), pero eso ya lo iremos viendo.

The following two tabs change content below.
Llevo más de 10 años programando, sobre todo en Visual Basic y con bases de datos Access. Para mí, VBA y Access siguen siendo herramientas muy potentes. He desarrollado varios proyectos con PHP y MySql. Si sumo las webs que he tenido, probablemente pasaría de 100. Ahora prefiero dedicar todo mi esfuerzo a este blog (aunque sigo manteniendo unas cuantas...). Trabajo en la administración pública (si, soy funcionario), pero he trabajado en pequeñas empresas e incluso en una "grande" de las telecomunicaciones. Ultimamente estoy bastante metido en abrirme nuevos horizontes con C# y .NET. Renovarse o morir!

2 Respuestas a Access: Encriptar contraseñas con SHA-256 utilizando biblioteca de clases .NET con C#

  1. Soy aficionado a la programación, utilizo Access, VBA y NET .

    En mi tiempo libre desarrollo solo por diversión y conocimiento.

    Solo queria felicitarte por los aportes y el esfuerzo que desempeñas compartiendo tus conocimientos a disposición de los demas.

    Muchas Gracias, todos los temas que expones son muy interesantes y utiles.

Deja una respuesta