How to Convert to SHA256 in C++

Jay Shaw Feb 02, 2024
  1. the SHA256 Hashing Algorithm
  2. C++ Program to Convert Plain Text Into SHA256
  3. Conclusion
How to Convert to SHA256 in C++

SHA256 conversion is a cryptographic hashing technique that has been in use since the ’90s but gained prominence after the onset of Bitcoin and Blockchain.

It uses an irreversible one-way hashing technique, which means that the final answer can never be reversed back to its original message, thus making it very secure.

The final message is represented in a 256-bit format, thus boasting the 256 in its name.

the SHA256 Hashing Algorithm

The hashing algorithm used for SHA256 conversion has four parts: padding, initial buffers, creation of message schedule, and compression. Let’s understand how to implement each of them in C++.

Padding Bits in SHA256

The first step of the hashing algorithm for SHA256 conversion is padding the original message. Padding must be done such that the message has 448 bits, which is 64 bits less than 512 bits.

If a message containing the letters abc has to be padded, the first 24 bits will be filled with binary values of abc, followed by 424 bits of zeroes, totaling 448 bits. The length will be stored in the last 64 bits.

Padding Length in SHA256

The original message is padded to make it 512 bits long before using it for hashing. The message must be padded so that the first 448 bits store the message, while the remaining 64 bits of data are length padding.

These unused bits are used to keep track of the message’s length. These 64 bits are filled mostly with zeros, except the first digit, which must be 1, and the remaining last digits, which store the message length.

Buffers for Hashing in SHA256

SHA256 conversion algorithm needs to initialize buffers or hash values for compression. These buffers are called state registers.

In SHA256 hashing, the process always starts with the same set of state registers, and then these are appended repeatedly with new values taken from the message digest.

There are a total of 8 state registers. To better understand, perceive these state registers as slots carrying predefined random values.

The random values are found by taking the modulus of the square root of the first 8 prime numbers and then multiplying it with 2 raised to the power of 32.

The SHA256 algorithm creates the 8 state registers using this method.

In C++, these state registers are initialized by assigning hexadecimal values of those 8 prime numbers directly into an array.

The initialization is demonstrated below:

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

Another set of keys is also required, which stores 64 hexadecimal values in an array, ranging from hash_keys[0] to 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};

Message Schedule in SHA256

A message schedule comprises 16 words and is created from each message block. Each of which is 32 bits long, this schedule is assembled into 16 distinct blocks of binary numbers.

The message block, however, must be 64 words long. This is done by compiling words already present inside the message schedule to create new blocks.

A message schedule needs to be prepared before the message is compressed. The 512-bit message block is broken down into 16 32-bit words.

The convention is to create a new word with the existing words using bitwise operations like XOR and AND operations. The 17th block is created using the formula:

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

Where,

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

Initialization of sigma 0 and sigma 1 rotation functions:

#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))

Creation of the W(16) word:

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

These rotation functions use the existing data inside the block to fuse them and expand the message schedule with many new bits.

Compression in SHA256

This is the core of the hashing function. Every bit gets switched together and overlaid on top of each other to create the final message digest.

This function uses the state registers we created above and combines them with the message schedule and original message block to generate a digest.

Every SHA256 conversion hashing function starts with the same state registers before it gets modified. The function creates two temporary words using:

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

Here, Ch(e,f,g) stands for choice of function, and Maj(a,b,c) stands for major of function. These two functions are explained below in detail.

Once these temporary words are created, the function adds the two temporary words, shifts down every word in the state register by one place, and fills the added word inside the first state register, and T1 is added to register e.

It can be observed in the code below that buffer[] are state registers. The value inside the 8th register (buffer[7]) is swapped with the value inside the 7th register (buffer[6]).

The swapping process continues until all registers get a new value with buffer[0] or the 1st register receives the sum of T1 and T2.

This compression process goes on for all the 64 words, finally leaving an updated state register.

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

Here, CHOICE_OF() and MAJORITY_OF are choice and major functions. These are defined as:

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

Where:

  • x & y = x and y
  • ~x = negation of x
  • ^ = XOR

The last part of compression for this message block is to take the initial hash values we started with and add them up with the result of compression. The final hash values are converted to hexadecimal and concatenated to create the final message digest.

C++ Program to Convert Plain Text Into SHA256

This program has two parts - a SHA256 header file containing all the essential functions defined and a main C++ program file.

Header File for SHA256 Algorithm in 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

Main File For Executing SHA256 in 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;
}

Output:

sha256('Paris'):5dd272b4f316b776a7b8e3d0894b37e1e42be3d5d3b204b8a5836cc50597a6b1

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

Conclusion

This article has explained the cryptography of SHA256 conversion using C++ in detail. The hashing algorithm and compression of the message schedule, which forms the heart of cryptographic hashing, are demonstrated with code snippets to make understanding easier.

A ready-to-use C++ program is also attached to be directly put to work for learning and back-testing.

Related Article - C++ Algorithm