Introducción
Seguimos con los tutoriales de Go. Ahora veremos cómo encriptar una contraseña y luego comprobar si esa contraseña coincide.
Sigue leyendo para que te des una mejor idea.
¿Cómo funciona?
En todos nuestros sistemas en donde implementemos contraseñas, debemos guardarlas de tal forma que sean, como yo lo llamo, “de un sólo camino”. Es decir, que una vez encriptadas ya nada las pueda desencriptar.
¿y cómo las compruebo después, si no las puedo desencriptar?
Muy fácil, supongamos que tenemos un algoritmo muy complejo que las encripta. Entonces, al registrar la contraseña, algo como “123” se convierte en “aaa”.
Dicho valor ya no podrá ser desencriptado, así que cuando el usuario quiera iniciar sesión, no podremos comprobar si su contraseña al desencriptar se convierte en “aaa”.
Pero hay algo que sí podemos hacer, encriptar la contraseña que nos está mandando, y comprobarla con la contraseña encriptada que ya tenemos guardada.
Así que si en nuestra base de datos tenemos “aaa” y el usuario quiere entrar con la contraseña “123” la encriptamos y al encriptarla se convertirá en “aaa” el cual es el mismo valor que tenemos.
Si en cambio el usuario intenta entrar con la contraseña “456” que al encriptarla se convierte en “bbb” entonces no coincidirá.
Obviamente esto fue un ejemplo, en la vida real las contraseñas se encriptan de diferente manera, pero la teoría es casi la misma.
Instalar paquete
No viene incluido por defecto, así que lo instalamos:
go get golang.org/x/crypto/bcrypt
Con esto ya podemos usar a bcrypt.
Hashear contraseña en Go
Es muy fácil generar un hash de contraseña segura. Para ello llamamos al método GenerateFromPassword de bcrypt.
Como primer argumento le pasamos un arreglo de bytes con la contraseña en texto plano, y como segundo le pasamos el costo.
Sobre el costo
El costo es muy importante a la hora de hashear nuestras contraseñas. Uno muy alto (el límite es 31) es casi imposible de romper, pero tomará mucho tiempo hashear una contraseña.
Por eso Go propone un costo mínimo, uno por defecto y uno máximo. El mínimo es 4, el que es por defecto es 10 y el máximo 31.
Recomiendo usar el 10, como se verá en el código. Pero si estamos dispuestos a que se consuman más recursos, podemos establecerlo más alto.
Cabe mencionar que también será pesado comprobar un hash cuando el usuario quiera loguearse, así que hay que establecer bien el costo.
Y finalmente, si pasas un costo menor que el mínimo (por ejemplo, 3 o 2) se pondrá el costo por defecto (10).
Código de ejemplo
El siguiente código hashea la contraseña “123” con un costo de 10.
package main
import (
"fmt"
"golang.org/x/crypto/bcrypt"
)
func main() {
contraseñaPlana := "123"
contraseñaPlanaComoByte := []byte(contraseñaPlana)
hash, err := bcrypt.GenerateFromPassword(contraseñaPlanaComoByte, bcrypt.DefaultCost) //DefaultCost es 10
if err != nil {
fmt.Println(err)
}
hashComoCadena := string(hash)
fmt.Printf("El hash generado a partir de %s es %s\n", contraseñaPlana, hashComoCadena)
}
Como se aprecia, debemos convertir la cadena a un arreglo de bytes. Y luego, como GenerateFromPassword regresa igualmente un arreglo de bytes lo convertimos a cadena.
Si lo ejecuto, esto pasa:
Si tú lo ejecutas te dará un resultado diferente, pero tranquilo que es normal y es una excelente práctica, ya que así se previenen los ataques de diccionario.
El hash como cadena ya podemos guardarlo en una base de datos o en donde queramos usarlo. Ahora veamos cómo comparar si la contraseña coincide.
Comprobar si hash y contraseña en texto plano coinciden
Ahora veamos cómo comprobar si una contraseña en texto plano y nuestro hash coinciden. Para ello pongamos el ejemplo de que nuestro hash es $2a$10$yXR.Jd79OMcHfNK8zZU5N.nQ7ZL5N1d65sGIbPr.vIf6Q3o7e540i (como en la imagen de arriba) y lo vamos a comprobar primero con “555” (contraseña incorrecta) y luego con “123” (correcta).
Go provee el método CompareHashAndPassword que recibe 2 argumentos de tipo arreglo de bytes: el hash y la contraseña en texto plano.
Importante: esta función no devuelve un booleano, sino que devuelve un error (o nil en caso de que no haya error). Si devuelve nil, entonces las contraseñas conciden. Si devuelve error, entonces las contraseñas no coinciden.
Código de ejemplo
Primero probemos con este, en donde la contraseña es incorrecta:
package main
import (
"fmt"
"golang.org/x/crypto/bcrypt"
)
func main() {
hash := "$2a$10$yXR.Jd79OMcHfNK8zZU5N.nQ7ZL5N1d65sGIbPr.vIf6Q3o7e540i"
hashComoByte := []byte(hash)
contraseña := "555"
contraseñaComoByte := []byte(contraseña)
error := bcrypt.CompareHashAndPassword(hashComoByte, contraseñaComoByte)
if error == nil {
fmt.Println("Las contraseñas coinciden :)")
} else {
fmt.Println("Las contraseñas no coinciden. El error es: ", error)
}
}
Si nos fijamos, dijimos que la contraseña es 555 pero realmente es 123. Al ejecutar el programa:
Es importante conocer el error, para ello lo estamos imprimiendo. Y como podemos ver, dice que la contraseña hasheada no es el hash de la contraseña en texto plano que proporcionamos.
Ahora cambiaré la contraseña a 123:
package main
import (
"fmt"
"golang.org/x/crypto/bcrypt"
)
func main() {
hash := "$2a$10$yXR.Jd79OMcHfNK8zZU5N.nQ7ZL5N1d65sGIbPr.vIf6Q3o7e540i"
hashComoByte := []byte(hash)
contraseña := "123"
contraseñaComoByte := []byte(contraseña)
error := bcrypt.CompareHashAndPassword(hashComoByte, contraseñaComoByte)
if error == nil {
fmt.Println("Las contraseñas coinciden :)")
} else {
fmt.Println("Las contraseñas no coinciden. El error es: ", error)
}
}
Y si ejecuto el código:
Así es como finalizamos este post.
Conclusión
Vimos las formas básicas de hashear y comprobar contraseñas. Recuerda que esto es un ejemplo fácil para no confundir al lector, pero puedes tomar atajos o crear tus propias funciones dependiendo de tus necesidades.
Finalmente aquí dejo la documentación oficial: bcrypt – GoDoc