Hashing una cadena con Sha256

141

Intento hacer un hash de una cadena usando SHA256, estoy usando el siguiente código:

using System;
using System.Security.Cryptography;
using System.Text;
 public class Hash
    {
    public static string getHashSha256(string text)
    {
        byte[] bytes = Encoding.Unicode.GetBytes(text);
        SHA256Managed hashstring = new SHA256Managed();
        byte[] hash = hashstring.ComputeHash(bytes);
        string hashString = string.Empty;
        foreach (byte x in hash)
        {
            hashString += String.Format("{0:x2}", x);
        }
        return hashString;
    }
}

Sin embargo, este código da resultados significativamente diferentes en comparación con mis amigos php, así como generadores en línea (como este generador )

¿Alguien sabe cuál es el error? Diferentes bases?

Nattfrosten
fuente
17
Fuera del tema, pero tenga en cuenta que crear un StringBuilder y usar AppendFormat en lugar de String.Format en su bucle foreach evitará que su código cree innecesariamente muchos objetos de cadena.
Marcel Lamothe

Respuestas:

154

Encoding.Unicodees el nombre engañoso de Microsoft para UTF-16 (una codificación de doble ancho, utilizada en el mundo de Windows por razones históricas pero no utilizada por nadie más). http://msdn.microsoft.com/en-us/library/system.text.encoding.unicode.aspx

Si inspecciona su bytesmatriz, verá que cada segundo byte es 0x00(debido a la codificación de doble ancho).

Deberías estar usando en su Encoding.UTF8.GetByteslugar.

Pero también, verá resultados diferentes dependiendo de si considera o no que el '\0'byte de terminación es parte de los datos que está troquelando. El hash de los dos bytes "Hi"dará un resultado diferente al de los tres bytes "Hi". Tendrás que decidir qué quieres hacer. (Presumiblemente quiere hacer lo que sea que esté haciendo el código PHP de su amigo).

Para el texto ASCII, Encoding.UTF8definitivamente será adecuado. Si busca una compatibilidad perfecta con el código de su amigo, incluso en entradas no ASCII, será mejor que pruebe algunos casos de prueba con caracteres no ASCII como éy y vea si sus resultados aún coinciden. Si no, tendrás que descubrir qué codificación está usando realmente tu amigo; podría ser una de las "páginas de códigos" de 8 bits que solían ser populares antes de la invención de Unicode. (Una vez más, creo que Windows es la razón principal por la que alguien todavía debe preocuparse por las "páginas de códigos").

Quuxplusone
fuente
3
@Elmue, puede que le complazca saber que "ordenar por bytes codificados en UTF8" y "ordenar por puntos de código Unicode" son equivalentes. (Al igual que "ordenar por shorts codificados con UTF16 ", pero no "ordenar por bytes codificados por UTF16" a menos que esté en un sistema big endian, que no es Windows). Sin embargo, "ordenar" en Unicode es realmente un tema complicado que debería guardarse para otro día.
Quuxplusone
2
@Elmue no confíes tanto en tus respuestas incorrectas. Pruébalo; te sorprenderás Si la sorpresa es agradable o desagradable depende totalmente de usted. :)
Quuxplusone
2
@Elmue, “ ¿Qué pasa si quieres hacer una comparación sin distinción entre mayúsculas y minúsculas? "También necesita convertir bytes en UTF-16 si desea hacer este tipo de cosas. El hecho de que tenga una longitud fija no ayuda en nada.
Arturo Torres Sánchez
2
La afirmación "no utilizada por nadie más" es bastante interesante, ya que Java maneja internamente cadenas como UTF-16 también ...
Sami Kuhmonen
44
@Elmue "Sus comentarios son incorrectos: UTF16 es Unicode". Está usted equivocado. "Unicode" es un estándar que asigna números (puntos de código) a los glifos. Excepto los pares sustitutos, no establece cómo representar esos números como bytes. UTF16 especifica puntos de código <--> bytes. Unicode especifica glifos <--> puntos de código.
antiduh
103

También tuve este problema con otro estilo de implementación, pero olvidé dónde lo obtuve, ya que fue hace 2 años.

static string sha256(string randomString)
{
    var crypt = new SHA256Managed();
    string hash = String.Empty;
    byte[] crypto = crypt.ComputeHash(Encoding.ASCII.GetBytes(randomString));
    foreach (byte theByte in crypto)
    {
        hash += theByte.ToString("x2");
    }
    return hash;
}

Cuando ingreso algo como abcdefghi2013por alguna razón, da resultados diferentes y produce errores en mi módulo de inicio de sesión. Luego intenté modificar el código de la misma manera que lo sugerido por Quuxplusone y cambié la codificación ASCIIpara UTF8que finalmente funcionara.

static string sha256(string randomString)
{
    var crypt = new System.Security.Cryptography.SHA256Managed();
    var hash = new System.Text.StringBuilder();
    byte[] crypto = crypt.ComputeHash(Encoding.UTF8.GetBytes(randomString));
    foreach (byte theByte in crypto)
    {
        hash.Append(theByte.ToString("x2"));
    }
    return hash.ToString();
}

Gracias de nuevo Quuxplusone por la maravillosa y detallada respuesta. :)

Nico Dumdum
fuente
Tu solución funcionó para mí. Pero tengo un caso diferente. es con sha512 y la línea de código que resolvió mi problema es hash += bit.ToString("x2");que tengo una pregunta aquí: estaba usando Convert.ToBase64String(byte[] encryptedBytes)para convertir de bytes a cadena. eso me estaba dando un resultado diferente. Entonces, ¿cuál es la diferencia entre estos dos métodos de conversión de bytes a cadena ...?
Keval Langalia
¿Es posible usar alguna personalización aquí (como mi propio vector de inicialización) o está agregando / anteponiendo la opción de cadena aleatoria solamente?
FrenkyB
No estoy muy seguro de lo que quieres decir. Esta es una función de hash muy simple y siempre puedes agregarla / personalizarla como quieras. Al anexar / anteponer una cadena aleatoria, ¿te refieres a la salazón? Bueno, esa es una buena manera de personalizarlo para mayor seguridad.
Nico Dumdum
No se recomienda usar solo el hashing SHA sin un factor de trabajo para almacenar contraseñas. En otras palabras, el proceso de hash de la contraseña debe ser significativamente lento, para evitar que los hackers adivinen rápidamente. Use Bcrypt o Scrypt para una mejor seguridad.
Ton Snoei
@TonSnoei Sí, eso es cierto. Sin embargo, este es un código antiguo de alguna antigua aplicación de sistema interno en la universidad que ya nadie usa y realmente no lo recomendaría yo mismo. Además, este hilo trata específicamente sobre la codificación SHA256 y no directamente sobre las contraseñas. Sin embargo, no me importaría editarlo para eliminar referencias a contraseñas si eso te gusta.
Nico Dumdum
6
public static string ComputeSHA256Hash(string text)
{
    using (var sha256 = new SHA256Managed())
    {
        return BitConverter.ToString(sha256.ComputeHash(Encoding.UTF8.GetBytes(text))).Replace("-", "");
    }                
}

La razón por la que obtiene resultados diferentes es porque no usa la misma codificación de cadena. El enlace que coloca para el sitio web en línea que calcula SHA256 usa la codificación UTF8, mientras que en su ejemplo usó la codificación Unicode. Son dos codificaciones diferentes, por lo que no obtienes el mismo resultado. Con el ejemplo anterior, obtiene el mismo hash SHA256 del sitio web vinculado. Debe usar la misma codificación también en PHP.

El mínimo absoluto que todo desarrollador de software debe saber absolutamente, positivamente sobre los conjuntos de caracteres y Unicode (¡sin excusas!)

https://www.joelonsoftware.com/2003/10/08/the-absolute-minimum-every-software-developer-absolutely-positively-must-know-about-unicode-and-character-sets-no-excuses/

Auto
fuente
4

En la versión de PHP, puede enviar 'verdadero' en el último parámetro, pero el valor predeterminado es 'falso'. El siguiente algoritmo es equivalente a la función hash predeterminada de PHP al pasar 'sha256' como primer parámetro:

public static string GetSha256FromString(string strData)
    {
        var message = Encoding.ASCII.GetBytes(strData);
        SHA256Managed hashString = new SHA256Managed();
        string hex = "";

        var hashValue = hashString.ComputeHash(message);
        foreach (byte x in hashValue)
        {
            hex += String.Format("{0:x2}", x);
        }
        return hex;
    }
Rachel
fuente
44
No estaría usando ASCIIy hacer en su byte[] arrBytes = System.Text.Encoding.UTF8.GetBytes(strData)lugar.
c00000fd
3
public string EncryptPassword(string password, string saltorusername)
        {
            using (var sha256 = SHA256.Create())
            {
                var saltedPassword = string.Format("{0}{1}", salt, password);
                byte[] saltedPasswordAsBytes = Encoding.UTF8.GetBytes(saltedPassword);
                return Convert.ToBase64String(sha256.ComputeHash(saltedPasswordAsBytes));
            }
        }
ARCO
fuente
1
me gusta el hecho de que agregaste un poco de sal ^^
Fabian
1

La forma más corta y rápida de la historia. ¡Solo 1 línea!

public static string StringSha256Hash(string text) =>
    string.IsNullOrEmpty(text) ? string.Empty : BitConverter.ToString(new System.Security.Cryptography.SHA256Managed().ComputeHash(System.Text.Encoding.UTF8.GetBytes(text))).Replace("-", string.Empty);
Erçin Dedeoğlu
fuente