Convertir a SHA256 en C++

Jay Shaw 12 octubre 2023
  1. el algoritmo hash SHA256
  2. Programa C++ para convertir texto sin formato en SHA256
  3. Conclusión
Convertir a SHA256 en C++

La conversión SHA256 es una técnica de hashing criptográfico que ha estado en uso desde los años 90 pero ganó prominencia después del inicio de Bitcoin y Blockchain.

Utiliza una técnica de hashing unidireccional irreversible, lo que significa que la respuesta final nunca se puede revertir a su mensaje original, lo que la hace muy segura.

El mensaje final se representa en un formato de 256 bits, por lo que ostenta el 256 en su nombre.

el algoritmo hash SHA256

El algoritmo hash utilizado para la conversión SHA256 consta de cuatro partes: relleno, búferes iniciales, creación de programación de mensajes y compresión. Comprendamos cómo implementar cada uno de ellos en C++.

Puntas de relleno en SHA256

El primer paso del algoritmo hash para la conversión SHA256 es rellenar el mensaje original. El relleno debe hacerse de manera que el mensaje tenga 448 bits, que son 64 bits menos que 512 bits.

Si hay que rellenar un mensaje que contenga las letras abc, los primeros 24 bits se rellenarán con valores binarios de abc, seguidos de 424 bits de ceros, totalizando 448 bits. La longitud se almacenará en los últimos 64 bits.

Longitud del relleno en SHA256

El mensaje original se rellena para que tenga 512 bits mucho antes de usarlo para el hashing. El mensaje debe rellenarse para que los primeros 448 bits almacenen el mensaje, mientras que los 64 bits de datos restantes son relleno de longitud.

Estos bits no utilizados se utilizan para realizar un seguimiento de la longitud del mensaje. Estos 64 bits se rellenan en su mayoría con ceros, excepto el primer dígito, que debe ser 1, y los últimos dígitos restantes, que almacenan la longitud del mensaje.

Búferes para Hashing en SHA256

El algoritmo de conversión SHA256 necesita inicializar búferes o valores hash para la compresión. Estos búferes se denominan registros de estado.

En el hashing SHA256, el proceso siempre comienza con el mismo conjunto de registros de estado, y luego estos se agregan repetidamente con nuevos valores tomados del resumen del mensaje.

Hay un total de 8 registros estatales. Para comprenderlo mejor, perciba estos registros de estado como ranuras que llevan valores aleatorios predefinidos.

Los valores aleatorios se encuentran tomando el módulo de la raíz cuadrada de los primeros 8 números primos y luego multiplicándolo por 2 elevado a la potencia de 32.

El algoritmo SHA256 crea los 8 registros de estado utilizando este método.

En C++, estos registros de estado se inicializan asignando valores hexadecimales de esos 8 números primos directamente en una matriz.

La inicialización se muestra a continuación:

void SHA256::state_register() {
  s_r[0] = 0x6a09e667;
  s_r[1] = 0xbb67ae85;
  s_r[2] = 0x3c6ef372;
  s_r[3] = 0xa54ff53a;
  s_r[4] = 0x510e527f;
  s_r[5] = 0x9b05688c;
  s_r[6] = 0x1f83d9ab;
  s_r[7] = 0x5be0cd19;
  m_len = 0;
  m_tot_len = 0;
}

También se requiere otro conjunto de claves, que almacena 64 valores hexadecimales en una matriz, que van desde hash_keys[0] hasta hash_keys[63].

const unsigned int SHA256::hash_keys[64] = {
    0xef6685ff, 0x38fd3da,  0x94402b15, 0xc67cb7b7, 0x780e38cd, 0xe7440103,
    0x5d415e6e, 0xbb7c2922, 0xf1df8153, 0x5f47e03f, 0x8c658cf7, 0x95ca718,
    0x678d5436, 0xda792dc4, 0x4aa3778b, 0x449e3719, 0x23913e93, 0xfdb2380c,
    0x2e82c771, 0x5bb60bcd, 0x13f53664, 0x174004ae, 0xc338e749, 0x199adec,
    0x28a3dcfe, 0x36fc4894, 0xe1a019cc, 0x59b7fe92, 0x5b007153, 0x1bb32e0d,
    0x2cba796a, 0x3a159148, 0x266d057b, 0xbc9c1d52, 0x17601e7,  0x39b3ccc7,
    0x10367db5, 0xa3558c1b, 0xbf98037f, 0x6fbffc84, 0xef54e44,  0x961a993a,
    0x33e5297b, 0xd2dce255, 0x7fe9864c, 0xfdd93543, 0xc62f137,  0x14eea06b,
    0x2f106df2, 0xf7956237, 0xd053bbca, 0x7a449ecf, 0x8af91f64, 0x9f34a155,
    0x663002e3, 0x7acf8b9c, 0xb0c90a35, 0xa71bba61, 0xc2d6c5a3, 0x9af20609,
    0x8cfc5464, 0x29d95bcf, 0x7c5478b,  0xde9f4ec3};

Programación de mensajes en SHA256

Un programa de mensajes consta de 16 palabras y se crea a partir de cada bloque de mensajes. Cada uno de los cuales tiene 32 bits de largo, este programa se ensambla en 16 bloques distintos de números binarios.

El bloque de mensajes, sin embargo, debe tener una longitud de 64 palabras. Esto se hace compilando palabras ya presentes dentro del programa de mensajes para crear nuevos bloques.

Es necesario preparar una programación de mensajes antes de comprimir el mensaje. El bloque de mensajes de 512 bits se divide en 16 palabras de 32 bits.

La convención es crear una nueva palabra con las palabras existentes usando operaciones bit a bit como las operaciones XOR y AND. El bloque 17 se crea usando la fórmula:

$$ W(16) = ROTL(\sigma1(W(t-2)) + W(t-7) + \sigma0(W(t-15)) + W(t-16) $$

Dónde,

$$ \sigma0(x) = ROTR(7)x + ROTR(18)x + SHR(3)x $$
$$ \sigma1(x) = ROTR(17)x + ROTR(19)x + SHR(10)x $$

Inicialización de las funciones de rotación sigma 0 y sigma 1:

#define SHAF_3(x) (R_ROTATE(x, 7) ^ R_ROTATE(x, 18) ^ R_SHFT(x, 3))
#define SHAF_4(x) (R_ROTATE(x, 17) ^ R_ROTATE(x, 19) ^ R_SHFT(x, 10))

Creación de la palabra W(16):

int m;
int n;
for (m = 0; m < (int)block_nb; m++) {
  sub_block = message + (m << 6);
  for (n = 0; n < 16; n++) {
    SHAF_PACK32(&sub_block[n << 2], &w[n]);
  }
  for (n = 16; n < 64; n++) {
    w[n] = SHAF_4(w[n - 2]) + w[n - 7] + SHAF_3(w[n - 15]) + w[n - 16];
  }
  for (n = 0; n < 8; n++) {
    buffer[n] = s_r[n];
  }

Estas funciones de rotación usan los datos existentes dentro del bloque para fusionarlos y expandir el programa de mensajes con muchos bits nuevos.

Compresión en SHA256

Este es el núcleo de la función hash. Cada bit se intercambia y se superpone uno encima del otro para crear el resumen del mensaje final.

Esta función utiliza los registros de estado que creamos anteriormente y los combina con el programa de mensajes y el bloque de mensajes original para generar un resumen.

Cada función hash de conversión SHA256 comienza con los mismos registros de estado antes de modificarse. La función crea dos palabras temporales usando:

$$ T1 = \sigma1(e) + Ch(e,f,g) + h + k(0) + W(0) $$
$$ T2 = \sigma0(a) + Maj(a,b,c) $$

Aquí, Ch(e,f,g) representa la elección de la función, y Maj(a,b,c) representa la función principal. Estas dos funciones se explican a continuación en detalle.

Una vez que se crean estas palabras temporales, la función agrega las dos palabras temporales, desplaza hacia abajo cada palabra en el registro de estado en un lugar y llena la palabra agregada dentro del primer registro de estado, y T1 se agrega al registro e.

Se puede observar en el siguiente código que buffer[] son registros de estado. El valor dentro del registro 8 (buffer[7]) se intercambia con el valor dentro del registro 7 (buffer[6]).

El proceso de intercambio continúa hasta que todos los registros obtienen un nuevo valor con buffer[0] o el primer registro recibe la suma de T1 y T2.

Este proceso de compresión continúa para las 64 palabras, dejando finalmente un registro de estado actualizado.

for (n = 0; n < 64; n++) {
  t1 = buffer[7] + SHAF_2(buffer[4]) +
       CHOICE_OF(buffer[4], buffer[5], buffer[6]) + hash_keys[n] + w[n];
  t2 = SHAF_1(buffer[0]) + MAJORITY_OF(buffer[0], buffer[1], buffer[2]);
  buffer[7] = buffer[6];
  buffer[6] = buffer[5];
  buffer[5] = buffer[4];
  buffer[4] = buffer[3] + t1;
  buffer[3] = buffer[2];
  buffer[2] = buffer[1];
  buffer[1] = buffer[0];
  buffer[0] = t1 + t2;
}
for (n = 0; n < 8; n++) {
  s_r[n] += buffer[n];

Aquí, CHOICE_OF() y MAJORITY_OF son funciones principales y de elección. Estos se definen como:

#define CHOICE_OF(x, y, z) ((x & y) ^ (~x & z))
#define MAJORITY_OF(x, y, z) ((x & y) ^ (x & z) ^ (y & z))

Dónde:

  • x & y = x e y
  • ~x = negación de x
  • ^ = XOR

La última parte de la compresión para este bloque de mensajes es tomar los valores hash iniciales con los que comenzamos y sumarlos con el resultado de la compresión. Los valores hash finales se convierten a hexadecimales y se concatenan para crear el resumen del mensaje final.

Programa C++ para convertir texto sin formato en SHA256

Este programa tiene dos partes: un archivo de encabezado SHA256 que contiene todas las funciones esenciales definidas y un archivo de programa principal de C++.

Archivo de encabezado para el algoritmo SHA256 en C++

#ifndef HASHFUNCTIONS_H
#define HASHFUNCTIONS_H
#include <string>

class hash_functions {
 protected:
  typedef unsigned char register_8;
  typedef unsigned int register_32;
  typedef unsigned long long register_64;

  const static register_32 hash_keys[];
  static const unsigned int BLOCK_SIZE_of_256 = (512 / 8);

 public:
  void stateregister();  // init
  void adjust_digest(const unsigned char *text, unsigned int text_len);
  void digest_final(unsigned char *digest);
  static const unsigned int PADD_SIZE = (256 / 8);

 protected:
  void compress(const unsigned char *message, unsigned int block_nb);
  unsigned int s_r_totlen;
  unsigned int s_r_len;
  unsigned char s_r_block[2 * BLOCK_SIZE_of_256];
  register_32 s_r[8];
};

std::string sha256(std::string input);

#define R_SHFT(x, n) (x >> n)  // Right shift function
#define R_ROTATE(x, n) \
  ((x >> n) | (x << ((sizeof(x) << 3) - n)))  // Right rotate function
#define L_ROTATE(x, n) \
  ((x << n) | (x >> ((sizeof(x) << 3) - n)))     // Left rotate function
#define CHOICE_OF(x, y, z) ((x & y) ^ (~x & z))  // function to find choice of
#define MAJORITY_OF(x, y, z) \
  ((x & y) ^ (x & z) ^ (y & z))  // function to find majority of
#define SHAF_1(x)                     \
  (R_ROTATE(x, 2) ^ R_ROTATE(x, 13) ^ \
   R_ROTATE(x, 22))  // sigma rotation function
#define SHAF_2(x)                     \
  (R_ROTATE(x, 6) ^ R_ROTATE(x, 11) ^ \
   R_ROTATE(x, 25))  // sigma rotation function
#define SHAF_3(x) \
  (R_ROTATE(x, 7) ^ R_ROTATE(x, 18) ^ R_SHFT(x, 3))  // sigma0 rotation
#define SHAF_4(x) \
  (R_ROTATE(x, 17) ^ R_ROTATE(x, 19) ^ R_SHFT(x, 10))  // sigma1 rotation
#define SHAF_UNPACK32(x, str)               \
  {                                         \
    *((str) + 3) = (register_8)((x));       \
    *((str) + 2) = (register_8)((x) >> 8);  \
    *((str) + 1) = (register_8)((x) >> 16); \
    *((str) + 0) = (register_8)((x) >> 24); \
  }
#define SHAF_PACK32(str, x)                      \
  {                                              \
    *(x) = ((register_32) * ((str) + 3)) |       \
           ((register_32) * ((str) + 2) << 8) |  \
           ((register_32) * ((str) + 1) << 16) | \
           ((register_32) * ((str) + 0) << 24);  \
  }
#endif

Archivo principal para ejecutar SHA256 en C++

#include <cstring>
#include <fstream>
#include <iostream>

#include "hash_functions.h"

const unsigned int hash_functions::hash_keys[64] = {
    0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1,
    0x923f82a4, 0xab1c5ed5, 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3,
    0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174, 0xe49b69c1, 0xefbe4786,
    0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
    0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147,
    0x06ca6351, 0x14292967, 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13,
    0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85, 0xa2bfe8a1, 0xa81a664b,
    0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
    0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a,
    0x5b9cca4f, 0x682e6ff3, 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208,
    0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2};

void hash_functions::compress(const unsigned char *message,
                              unsigned int block_nb) {
  register_32 w[64];
  register_32 buffer[8];
  register_32 t1, t2;
  const unsigned char *sub_block;
  int m;
  int n;
  for (m = 0; m < (int)block_nb; m++) {
    sub_block = message + (m << 6);
    for (n = 0; n < 16; n++) {
      SHAF_PACK32(&sub_block[n << 2], &w[n]);
    }
    for (n = 16; n < 64; n++) {
      w[n] = SHAF_4(w[n - 2]) + w[n - 7] + SHAF_3(w[n - 15]) + w[n - 16];
    }
    for (n = 0; n < 8; n++) {
      buffer[n] = s_r[n];
    }
    for (n = 0; n < 64; n++) {
      t1 = buffer[7] + SHAF_2(buffer[4]) +
           CHOICE_OF(buffer[4], buffer[5], buffer[6]) + hash_keys[n] + w[n];
      t2 = SHAF_1(buffer[0]) + MAJORITY_OF(buffer[0], buffer[1], buffer[2]);
      buffer[7] = buffer[6];
      buffer[6] = buffer[5];
      buffer[5] = buffer[4];
      buffer[4] = buffer[3] + t1;
      buffer[3] = buffer[2];
      buffer[2] = buffer[1];
      buffer[1] = buffer[0];
      buffer[0] = t1 + t2;
    }
    for (n = 0; n < 8; n++) {
      s_r[n] += buffer[n];
    }
  }
}

void hash_functions::stateregister() {
  s_r[0] = 0x6a09e667;
  s_r[1] = 0xbb67ae85;
  s_r[2] = 0x3c6ef372;
  s_r[3] = 0xa54ff53a;
  s_r[4] = 0x510e527f;
  s_r[5] = 0x9b05688c;
  s_r[6] = 0x1f83d9ab;
  s_r[7] = 0x5be0cd19;
  s_r_len = 0;
  s_r_totlen = 0;
}

void hash_functions::adjust_digest(const unsigned char *text,
                                   unsigned int text_len) {
  unsigned int block_nb;
  unsigned int new_len, rem_len, tmp_len;
  const unsigned char *shifted_message;
  tmp_len = BLOCK_SIZE_of_256 - s_r_len;
  rem_len = text_len < tmp_len ? text_len : tmp_len;
  memcpy(&s_r_block[s_r_len], text, rem_len);
  if (s_r_len + text_len < BLOCK_SIZE_of_256) {
    s_r_len += text_len;
    return;
  }
  new_len = text_len - rem_len;
  block_nb = new_len / BLOCK_SIZE_of_256;
  shifted_message = text + rem_len;
  compress(s_r_block, 1);
  compress(shifted_message, block_nb);
  rem_len = new_len % BLOCK_SIZE_of_256;
  memcpy(s_r_block, &shifted_message[block_nb << 6], rem_len);
  s_r_len = rem_len;
  s_r_totlen += (block_nb + 1) << 6;
}

void hash_functions::digest_final(unsigned char *digest) {
  unsigned int block_nb;
  unsigned int pm_len;
  unsigned int len_b;
  int i;
  block_nb = (1 + ((BLOCK_SIZE_of_256 - 9) < (s_r_len % BLOCK_SIZE_of_256)));
  len_b = (s_r_totlen + s_r_len) << 3;
  pm_len = block_nb << 6;
  memset(s_r_block + s_r_len, 0, pm_len - s_r_len);
  s_r_block[s_r_len] = 0x80;
  SHAF_UNPACK32(len_b, s_r_block + pm_len - 4);
  compress(s_r_block, block_nb);
  for (i = 0; i < 8; i++) {
    SHAF_UNPACK32(s_r[i], &digest[i << 2]);
  }
}

std::string sha256(std::string input) {
  unsigned char digest[hash_functions::PADD_SIZE];
  memset(digest, 0, hash_functions::PADD_SIZE);

  hash_functions obj = hash_functions();
  obj.stateregister();
  obj.adjust_digest((unsigned char *)input.c_str(), input.length());
  obj.digest_final(digest);

  char buf[2 * hash_functions::PADD_SIZE + 1];
  buf[2 * hash_functions::PADD_SIZE] = 0;
  for (int i = 0; i < hash_functions::PADD_SIZE; i++)
    sprintf(buf + i * 2, "%02x", digest[i]);
  return std::string(buf);
}

using std::cout;
using std::endl;
using std::string;

int main(int argc, char *argv[]) {
  string text_in = "Paris";
  string text_out = sha256(text_in);

  cout << "Final Output('" << text_in << "'):" << text_out << endl;
  return 0;
}

Producción :

sha256('Paris'):5dd272b4f316b776a7b8e3d0894b37e1e42be3d5d3b204b8a5836cc50597a6b1

--------------------------------
Process exited after 0.03964 seconds with return value 0
Press any key to continue . . .

Conclusión

Este artículo ha explicado en detalle la criptografía de la conversión SHA256 usando C++. El algoritmo hash y la compresión del programa de mensajes, que constituye el núcleo del hash criptográfico, se demuestran con fragmentos de código para facilitar la comprensión.

También se adjunta un programa C++ listo para usar para que se ponga a trabajar directamente para aprender y realizar pruebas retrospectivas.

Artículo relacionado - C++ Algorithm