Hashing uma corda com Sha256

141

Eu tento hash uma seqüência usando SHA256, estou usando o seguinte 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;
    }
}

No entanto, esse código fornece resultados significativamente diferentes em comparação com o php dos meus amigos, além de geradores on-line (como este gerador )

Alguém sabe qual é o erro? Bases diferentes?

Nattfrosten
fonte
17
Fora do tópico, mas lembre-se de que criar um StringBuilder e usar AppendFormat em vez de String.Format no loop foreach impedirá que seu código crie desnecessariamente muitos objetos de string.
Marcel Lamothe #

Respostas:

154

Encoding.Unicodeé o nome enganoso da Microsoft para UTF-16 (uma codificação de largura dupla, usada no mundo do Windows por razões históricas, mas não usada por mais ninguém). http://msdn.microsoft.com/en-us/library/system.text.encoding.unicode.aspx

Se você inspecionar sua bytesmatriz, verá que cada segundo byte é 0x00(por causa da codificação de largura dupla).

Você deveria estar usando Encoding.UTF8.GetBytes.

Além disso, você verá resultados diferentes, dependendo de considerar ou não o '\0'byte final como parte dos dados que você está hash. O hash dos dois bytes "Hi"fornecerá um resultado diferente do hash dos três bytes "Hi". Você terá que decidir o que deseja fazer. (Presumivelmente, você deseja fazer o que o código PHP de seu amigo estiver fazendo.)

Para texto ASCII, Encoding.UTF8será definitivamente adequado. Se você está apontando para perfeita compatibilidade com o código do seu amigo, mesmo em entradas não-ASCII, é melhor tentar alguns casos de teste com caracteres não-ASCII, como ée para ver se os resultados ainda igualar-se. Caso contrário, você terá que descobrir qual codificação seu amigo está realmente usando; pode ser uma das "páginas de código" de 8 bits que eram populares antes da invenção do Unicode. (Mais uma vez, acho que o Windows é o principal motivo pelo qual alguém ainda precisa se preocupar com "páginas de código".)

Quuxplusone
fonte
3
@Elmue, você pode ficar satisfeito ao saber que "classificar por bytes codificados em UTF8" e "classificar por pontos de código Unicode" são equivalentes! (Como é "classificar por shorts codificados em UTF16 ", mas não "classificar por bytes codificados em UTF16", a menos que você esteja em um sistema big endian, o que o Windows não é.) No entanto, "classificar" no Unicode é realmente um tópico complicado que deve ser salvo para outro dia.
Quuxplusone
2
@ Elmue não seja tão confiante em suas respostas erradas. Experimente; você ficará surpreso. Se a surpresa é agradável ou desagradável, depende inteiramente de você. :)
Quuxplusone
2
@Elmue, “ E se você quiser fazer uma comparação sem distinção entre maiúsculas e minúsculas? Você também precisa converter bytes em UTF-16, se quiser fazer esse tipo de coisa. O fato de ter um comprimento fixo não ajuda em nada.
Arturo Torres Sánchez
2
O "não utilizado por mais ninguém" é a alegação bastante interessante, uma vez que Java lida internamente cadeias como UTF-16 também ...
Sami Kuhmonen
4
@Elmue "Seus comentários estão incorretos: UTF16 é Unicode." Você está errado. "Unicode" é um padrão que atribui números (pontos de código) a glifos. Exceto pares substitutos, ele não indica como representar esses números como bytes. UTF16 especifica pontos de código <--> bytes. Unicode especifica glifos <--> pontos de código.
Antiduh
103

Eu também tive esse problema com outro estilo de implementação, mas esqueci onde o consegui desde há 2 anos.

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;
}

Quando introduzo algo como, abcdefghi2013por algum motivo, ele fornece resultados diferentes e resulta em erros no meu módulo de login. Tentei modificar o código da mesma maneira sugerida pelo Quuxplusone e alterei a codificação de ASCIIparaUTF8 então finalmente funcionou!

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();
}

Mais uma vez obrigado Quuxplusone pela resposta maravilhosa e detalhada! :)

Nico Dumdum
fonte
sua solução funcionou para mim. mas eu tenho um caso diferente. é com sha512 e a linha de código que resolveu o meu problema é que hash += bit.ToString("x2");eu tenho uma pergunta aqui: eu estava usando Convert.ToBase64String(byte[] encryptedBytes)para converter novamente de bytes em string. isso estava me dando um resultado diferente. Então, qual é a diferença entre esses dois métodos de conversão de bytes em string?
Keval Langalia # 11/15
É possível usar alguma personalização aqui (como meu próprio vetor de inicialização) ou é a opção apenas de anexar / adicionar caracteres aleatórios?
FrenkyB
Não tenho muita certeza do que você quer dizer. Esta é apenas uma função de hash muito simples e você sempre pode adicionar / personalizar como quiser. Anexando / anexando uma sequência aleatória, você quer dizer salgar? Bem, essa é uma boa maneira de personalizá-lo para maior segurança.
Nico Dumdum
Não é recomendável usar apenas o hash SHA sem um fator de trabalho para armazenar senhas. Em outras palavras, o processo de hash da senha precisa ser significativamente lento, para impedir que os hackers adivinhem rapidamente. Use Bcrypt ou Scrypt para melhor segurança.
Ton Snoei
@TonSnoei Sim, isso é verdade. No entanto, esse é um código antigo de algum aplicativo antigo de sistema interno da faculdade que ninguém usa mais e eu realmente não recomendaria isso sozinho. Além disso, esse segmento trata especificamente da codificação SHA256 e não diretamente de senhas. Embora, eu não me importo de editá-lo para remover referências a senhas, se isso lhe agradar.
Nico Dumdum
6
public static string ComputeSHA256Hash(string text)
{
    using (var sha256 = new SHA256Managed())
    {
        return BitConverter.ToString(sha256.ComputeHash(Encoding.UTF8.GetBytes(text))).Replace("-", "");
    }                
}

A razão pela qual você obtém resultados diferentes é porque você não usa a mesma codificação de string. O link que você colocou para o site on-line que calcula o SHA256 usa a codificação UTF8, enquanto no seu exemplo você usou a codificação Unicode. São duas codificações diferentes, para que você não obtenha o mesmo resultado. Com o exemplo acima, você obtém o mesmo hash SHA256 do site vinculado. Você precisa usar a mesma codificação também no PHP.

O mínimo absoluto que todo desenvolvedor de software deve saber absolutamente, positivamente sobre Unicode e conjuntos de caracteres (sem desculpas!)

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
fonte
4

Na versão PHP, você pode enviar 'true' no último parâmetro, mas o padrão é 'false'. O algoritmo a seguir é equivalente à função hash do PHP padrão ao passar 'sha256' como o primeiro 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
fonte
4
Eu não estaria usando ASCIIe o faria byte[] arrBytes = System.Text.Encoding.UTF8.GetBytes(strData).
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
fonte
1
eu gosto do fato de você ter adicionado um pouco de sal ^^
Fabian
1

A maneira mais curta e rápida de todas. Apenas 1 linha!

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
fonte