¿Cómo se realiza la codificación doble SHA-256?

La especificación del Protocolo Bitcoin da un ejemplo de codificación doble-SHA-256.

hola
2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824 (primera ronda de sha-256)
9595c9df90075148eb06860365df33584b75bff782a510c6cd41988882a510c de 6 (253a)

Probé varias calculadoras SHA256 y la primera codificación coincide sin problemas, pero la segunda siempre resuelve

d7914fe546b684688bb95f4f888a92dfc680603a75f23eb823658031fff766d9

También probé MAYÚSCULAS e intercambié el byte endianness.

Solo semántica, pero para evitar un malentendido común: sha256 hace hash , no codifica. La codificación es algo completamente diferente. Por un lado, implica que se puede decodificar, mientras que el hashing es estrictamente una operación unidireccional (y destructiva).

Respuestas (7)

Estás procesando la representación hexadecimal del primer hash. Necesita hash el hash real, los datos binarios que representa el hexadecimal.

Prueba esto:

$ echo -n hello |openssl dgst -sha256 -binary |openssl dgst -sha256
Esto parece correcto, pero ¿cómo funcionó la primera ronda al codificar la cadena ASCII "HOLA" frente a la representación binaria de ASCII hola?
@makerofthings7 "HOLA" es binario. H es un byte con valor 72, E es 69, etc. También se puede imprimir un hash binario sin procesar, pero es probable que muchos de sus caracteres sean caracteres de control invisibles, por lo que generalmente se escribe en hexadecimal. También puedo escribir "HOLA" en hexadecimal: es 48454c4c4f.
@makerofthings7: No hay diferencia entre "la cadena ASCII 'HOLA' y "la representación binaria de ASCII 'HOLA'". Sin embargo, hay una diferencia entre la representación ASCII de un número en hexadecimal y ese número en binario.
theymos y @DavidSchwartz todo tiene mucho sentido ahora. Gracias
Hola, hay una opción optimizada (directa y más rápida) para realizar SHA256d? ¿ Algún opensslalgoritmo optimizado similar (confiable) para ello?

Desea trabajar con los resúmenes, no con las cadenas hexadecimales.

Aquí hay algo de Rubí:

require 'digest'
d = Digest::SHA2.new 256
d2 = Digest::SHA2.new 256
d << 'hello'
d.to_s
d2 << d.digest
d2.to_s

Esta será la salida de irb:

1.9.3p194 :001 > require 'digest'
 => true 
1.9.3p194 :003 >   d = Digest::SHA2.new 256
 => #<Digest::SHA2:256 e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855> 
1.9.3p194 :004 > d2 = Digest::SHA2.new 256
 => #<Digest::SHA2:256 e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855> 
1.9.3p194 :005 > d << 'hello'
 => #<Digest::SHA2:256 2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824> 
1.9.3p194 :006 > d.to_s
 => "2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824" 
1.9.3p194 :007 > d2 << d.digest
 => #<Digest::SHA2:256 9595c9df90075148eb06860365df33584b75bff782a510c6cd4883a419833d50> 
1.9.3p194 :008 > d2.to_s
 => "9595c9df90075148eb06860365df33584b75bff782a510c6cd4883a419833d50"

Aquí está lo mismo en Python:

import hashlib
d = hashlib.sha256(b"hello")
d2 = hashlib.sha256()
d.hexdigest()
d2.update(d.digest())
d2.hexdigest()

Y la salida desde dentro de un shell de Python:

>>> d = hashlib.sha256(b"hello")
>>> d2 = hashlib.sha256()
>>> d.hexdigest()
'2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824'
>>> d2.update(d.digest())
>>> d2.hexdigest()
'9595c9df90075148eb06860365df33584b75bff782a510c6cd4883a419833d50'
gracias por los ejemplos de código. Más tarde encontré el código de Python en la wiki, pero aún no podía entender qué lo hacía funcionar. +1

Para la segunda ronda de sha256, debe codificar la salida binaria sin procesar de la primera ronda, no la versión textual.

Un hash sha256 tiene 256 bits o 32 bytes. Por lo tanto, para la segunda ronda, debe codificar una pieza de datos de 32 bytes. Al codificar una cadena hexadecimal como entrada literal para la segunda ronda, sus datos son 64 bytes.

Pruebe una herramienta hash que pueda interpretar entradas hexadecimales. Por ejemplo , vea aquí , copie/pegue el hash en el campo de entrada y marque la casilla de verificación 'Hex'.

Para una referencia fácil, nombremos las diversas entradas y salidas de su pregunta de la siguiente manera:

pre-image 1:                     hello
                                   |
                                   v
hash operation 1:                SHA256
                                   |
                                   v
hash output 1 (aka pre-image 2): 2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824
                                   |
                                   v
hash operation 2:                SHA256
                                   |
                                   v
hash output 2:                   9595c9df90075148eb06860365df33584b75bff782a510c6cd4883a419833d50

La imagen previa 1 son datos binarios (ASCII). Sabemos esto porque el rango de caracteres hexadecimales es 0-9y a-f, y hay caracteres más allá fen la imagen previa 1. Por lo tanto, la operación hash 1 debe realizarse con una entrada binaria.

Por definición, una operación hash SHA256 siempre produce un número entero de 256 bits. Por lo general, representamos esto en formato hexadecimal, sin embargo, podría escribirse correctamente en formato decimal y nada cambiaría:

hex:     2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824
decimal: 20329878786436204988385760252021328656300425018755239228739303522659023427620

(Conversión decimal usando Wolfram Alpha)

Dado que sabemos que la imagen previa 2 es un número hexadecimal, la operación hash 2 debe realizarse sobre un número, en lugar de sobre datos binarios.

En Javascript, usando Stanford Javascript Crypto Library , los dos hashes se harían así:

var preImage1 = 'hello';
var hashOutput1 = sjcl.hash.sha256.hash(preImage1);
console.log('hash output 1: ' + sjcl.codec.hex.fromBits(hashOutput1));
var preImage2 = hashOutput1;
var hashOutput2 = sjcl.hash.sha256.hash(preImage2);
console.log('hash output 2: ' + sjcl.codec.hex.fromBits(hashOutput2));

El SJCL es lo suficientemente inteligente como para descubrir los formatos de imagen previa y realizar el hash correctamente en cada caso.

También puede realizar estas operaciones hash en vivo en mi blog, aquí: https://analysis.null.place/how-do-the-bitcoin-mining-algorithms-work/#form10

Tenga en cuenta cómo cambia la salida hash cuando hace clic en la casilla de verificación pre-image is hexadecimal (esto solo funciona cuando los caracteres hexadecimales están presentes en la entrada).

Llegó tarde a la fiesta, pero aquí hay una implementación de Node.js usando el cryptomódulo incorporado:

const crypto = require('crypto');
/**
 *
 * @param {Buffer} data
 * @returns {Buffer}
 */
function doubleSHA256(data) {
  return crypto.createHash('sha256').update(crypto.createHash('sha256').update(data).digest()).digest();
}

Espero que esto ayude.....

Tuve que hacer ligeras modificaciones a lo ParseHex()proporcionado por Andrew Chow ....

Los encabezados necesarios son:

#include <string>
#include <cstring>
#include <assert.h> // assert() is used

¡¡La implementación de Sha256 en uso es la proporcionada por Zedwood !!

La primera es la ParseHexfunción proporcionada por Andrew Chow


std::string ParseHex(std::string& s)
{
    assert(s.size() % 2 == 0);
    static const std::size_t symbol_count = 256;
    static const unsigned char hex_to_bin[symbol_count] = 
    {
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x00 - 0x07
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x08 - 0x0F
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x10 - 0x17
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x18 - 0x1F
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x20 - 0x27
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x28 - 0x2F
        0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, // 0x30 - 0x37
        0x08, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x38 - 0x3F
        0x00, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x00, // 0x40 - 0x47
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x48 - 0x4F
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x50 - 0x57
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x58 - 0x5F
        0x00, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x00, // 0x60 - 0x67
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x68 - 0x6F
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x70 - 0x77
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x78 - 0x7F
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x80 - 0x87
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x88 - 0x8F
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x90 - 0x97
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x98 - 0x9F
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0xA0 - 0xA7
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0xA8 - 0xAF
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0xB0 - 0xB7
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0xB8 - 0xBF
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0xC0 - 0xC7
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0xC8 - 0xCF
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0xD0 - 0xD7
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0xD8 - 0xDF
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0xE0 - 0xE7
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0xE8 - 0xEF
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0xF0 - 0xF7
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00  // 0xF8 - 0xFF
    };

    std::string out;
    auto itr = s.begin();
    while (itr != s.end())
    {
       unsigned char b = static_cast<unsigned char>(hex_to_bin[*(itr++)] << 4);
       b |= static_cast<unsigned char>(hex_to_bin[*(itr++)]     );
       out.push_back(b);
    }
    return out;
}

Luego, la siguiente es la sha256llamada de función modificada.


std::string sha256(std::string input)
{
    string kk;
    int size = input.length();
    
    unsigned char header_data[size], hash[32];// header_data is "160" in size because midstate optimization is not implemented
    char buf[2*32+1];
    buf[2*32] = 0;

    input = ParseHex(input); // ParseHex() is UNDEFINED !!!
    init(); // Initiialize
    update((unsigned char*)input.c_str(), input.length()); // Update
    final(hash); // Finalize

    // Uncomment code below if you want to directly double hash input data using a single "sha256(input)" function call
    // init();
    // update(hash, 32);
    // final(hash);

    for (int i = 0; i < 32; i++) 
        sprintf(buf+i*2, "%02x", hash[i]); // feed data from hash into buf and format data

    return std::string(buf); // Return buf as string instead of char
}

  • ESTAS FUNCIONES DEBEN SER PEGADAS DENTRO DEL ARCHIVO DE CABECERA SHA256
  • TENDRÁS QUE INVERTIR LA TERMINACIÓN DEL RESULTADO DEL HASH.
  • TAMBIÉN, "afirmar ()" LANZA UN ERROR PARA ENTRADAS DE LONGITUD DESIGUAL... ASÍ QUE "abc" NO FUNCIONARÁ.

Espero que esto te ayude si alguna vez necesitas esto....

¡¡Salud!!

Parece que ha codificado su algoritmo para asumir siempre que su mensaje de entrada siempre contiene datos en formato de visualización ASCII. Eso explicaría por qué su primer algoritmo produjo la tabla hash correcta. Para run2, puede tener sus datos de mensajes de entrada en cualquier formato que desee, siempre que tenga alguna forma de decirle a su algoritmo qué esperar. Voy a suponer que cuando escribió su algo, lo codificó para esperar siempre el formato de visualización ASCII en el archivo de mensaje de entrada. Entonces, si no cambió su primera tabla de bloques de salida al formato de visualización ASCII, eso explicaría por qué no obtuvo la tabla de bloques final esperada de su segunda ejecución. Tome una lista de "El conjunto de caracteres de PC de IBM" y verá a lo que me refiero. por ejemplo, si está proporcionando una 'a' y en realidad es una 'a' hexadecimal en lugar de un formato de visualización ASCII ' a', entonces TIENES que dejar que tu algoritmo sepa lo que estás haciendo; no puede adivinar que has cambiado de formato sin decírselo. De manera similar, es posible que haya puesto el hash hexadecimal o incluso la tabla hash binaria, desde la salida de ejecución 1 a la entrada de ejecución 2, lo cual está bien, PERO le ha hecho saber a su algoritmo que debe leerlo como cualquier formato que haya elegido, para entrar el archivo de entrada run2. Hay 2 formas de solucionar este IMO. (1) convierta su tabla de bloques hash de salida de run1 a caracteres de visualización ASCII y transfiéralos al archivo de entrada run2. No haría esto porque obtienes algunos caracteres de visualización ASCII extraños y maravillosos que pueden hacer cosas extrañas. (2) Vuelva a codificar su algo, para que pueda decirle qué datos recibirá, desde el archivo de entrada run2. Esa es una forma más flexible de hacer las cosas, y abre muchas más opciones en términos de usar cualquier base que te guste, incluso las tuyas personalizadas, pero YMMV. Ha pasado mucha agua debajo del puente desde que se publicó su problema, pero esto puede ayudar a otros.