C++에서 SHA256으로 변환

Jay Shaw 2023년10월12일
  1. SHA256 해싱 알고리즘
  2. 일반 텍스트를 SHA256으로 변환하는 C++ 프로그램
  3. 결론
C++에서 SHA256으로 변환

SHA256 변환은 90년대부터 사용되어 왔지만 비트코인과 블록체인이 등장한 후 유명해진 암호화 해싱 기술입니다.

되돌릴 수 없는 단방향 해싱 기술을 사용합니다. 즉, 최종 응답을 원래 메시지로 되돌릴 수 없으므로 매우 안전합니다.

최종 메시지는 256비트 형식으로 표시되므로 이름에 256이 붙습니다.

SHA256 해싱 알고리즘

SHA256 변환에 사용되는 해싱 알고리즘은 패딩, 초기 버퍼, 메시지 일정 생성 및 압축의 네 부분으로 구성됩니다. 각각을 C++로 구현하는 방법을 알아봅시다.

SHA256의 패딩 비트

SHA256 변환을 위한 해싱 알고리즘의 첫 번째 단계는 원본 메시지를 채우는 것입니다. 패딩은 메시지가 512비트보다 64비트 작은 448비트를 갖도록 수행되어야 합니다.

abc 문자를 포함하는 메시지를 채워야 하는 경우 처음 24비트는 abc라는 이진 값으로 채워지고 그 다음에는 0으로 구성된 424비트(총 448비트)가 채워집니다. 길이는 마지막 64비트에 저장됩니다.

SHA256의 패딩 길이

원본 메시지는 해싱에 사용하기 전에 512비트 길이로 패딩됩니다. 메시지는 처음 448비트가 메시지를 저장하고 데이터의 나머지 64비트가 길이 패딩되도록 패딩되어야 합니다.

이 미사용 비트는 메시지 길이를 추적하는 데 사용됩니다. 이 64비트는 1이어야 하는 첫 번째 숫자와 메시지 길이를 저장하는 나머지 마지막 숫자를 제외하고 대부분 0으로 채워집니다.

SHA256의 해싱용 버퍼

SHA256 변환 알고리즘은 압축을 위해 버퍼 또는 해시 값을 초기화해야 합니다. 이러한 버퍼를 상태 레지스터라고 합니다.

SHA256 해싱에서 프로세스는 항상 동일한 상태 레지스터 세트로 시작한 다음 메시지 다이제스트에서 가져온 새 값을 반복적으로 추가합니다.

총 8개의 상태 레지스터가 있습니다. 더 잘 이해하려면 이러한 상태 레지스터를 미리 정의된 임의 값을 전달하는 슬롯으로 인식하십시오.

임의의 값은 처음 8개의 소수의 제곱근 계수를 취한 다음 2의 32제곱을 곱하여 구합니다.

SHA256 알고리즘은 이 방법을 사용하여 8개의 상태 레지스터를 생성합니다.

C++에서 이러한 상태 레지스터는 8개의 소수의 16진수 값을 배열에 직접 할당하여 초기화됩니다.

초기화는 아래에 설명되어 있습니다.

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

hash_keys[0]에서 hash_keys[63]까지 배열에 64개의 16진수 값을 저장하는 다른 키 세트도 필요합니다.

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

SHA256의 메시지 일정

메시지 일정은 16개의 단어로 구성되며 각 메시지 블록에서 생성됩니다. 각각 길이가 32비트인 이 일정은 16개의 개별 이진수 블록으로 어셈블됩니다.

그러나 메시지 블록의 길이는 64단어여야 합니다. 이는 메시지 일정 내에 이미 존재하는 단어를 컴파일하여 새 블록을 생성함으로써 수행됩니다.

메시지를 압축하기 전에 메시지 일정을 준비해야 합니다. 512비트 메시지 블록은 16개의 32비트 워드로 나뉩니다.

관례는 XORAND 연산과 같은 비트 연산을 사용하여 기존 단어로 새 단어를 만드는 것입니다. 17번째 블록은 다음 공식을 사용하여 생성됩니다.

<사업부>
$$
W(16) = ROTL(\sigma1(W(t-2)) + W(t-7) + \sigma0(W(t-15)) + W(t-16)
$$

어디,

<사업부>
$$
\sigma0(x) = ROTR(7)x + ROTR(18)x + SHR(3)x
$$

<사업부>
$$
\sigma1(x) = ROTR(17)x + ROTR(19)x + SHR(10)x
$$

시그마 0시그마 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))

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

이러한 회전 기능은 블록 내부의 기존 데이터를 사용하여 데이터를 융합하고 많은 새로운 비트로 메시지 일정을 확장합니다.

SHA256의 압축

이것이 해싱 기능의 핵심입니다. 모든 비트가 함께 전환되고 서로 겹쳐 최종 메시지 다이제스트를 생성합니다.

이 기능은 위에서 만든 상태 레지스터를 사용하고 이를 메시지 일정 및 원본 메시지 블록과 결합하여 다이제스트를 생성합니다.

모든 SHA256 변환 해싱 함수는 수정되기 전에 동일한 상태 레지스터로 시작합니다. 이 함수는 다음을 사용하여 두 개의 임시 단어를 만듭니다.

<사업부>
$$
T1 = \sigma1(e) + Ch(e,f,g) + h + k(0) + W(0)
$$

<사업부>
$$
T2 = \sigma0(a) + Maj(a,b,c)
$$

여기서 Ch(e,f,g)는 기능 선택을 의미하고 Maj(a,b,c)는 주요 기능을 의미합니다. 이 두 가지 기능은 아래에서 자세히 설명합니다.

이러한 임시 단어가 생성되면 함수는 두 개의 임시 단어를 추가하고 상태 레지스터의 모든 단어를 한 위치씩 아래로 이동하고 추가된 단어를 첫 번째 상태 레지스터 내부에 채우고 T1을 레지스터 e에 추가합니다.

아래 코드에서 buffer[]가 상태 레지스터임을 확인할 수 있습니다. 8번째 레지스터(buffer[7]) 내부의 값이 7번째 레지스터(buffer[6]) 내부의 값으로 교체됩니다.

스와핑 프로세스는 모든 레지스터가 buffer[0]로 새 값을 얻거나 첫 번째 레지스터가 T1T2의 합계를 받을 때까지 계속됩니다.

이 압축 프로세스는 64개 단어 모두에 대해 계속되며 최종적으로 업데이트된 상태 레지스터를 남깁니다.

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

여기서 CHOICE_OF()MAJORITY_OF는 선택 및 주요 기능입니다. 이들은 다음과 같이 정의됩니다.

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

어디:

이 메시지 블록에 대한 압축의 마지막 부분은 우리가 시작한 초기 해시 값을 가져 와서 압축 결과와 함께 추가하는 것입니다. 최종 해시 값은 16진수로 변환되고 연결되어 최종 메시지 다이제스트를 생성합니다.

일반 텍스트를 SHA256으로 변환하는 C++ 프로그램

이 프로그램은 정의된 모든 필수 기능을 포함하는 SHA256 헤더 파일과 기본 C++ 프로그램 파일의 두 부분으로 구성됩니다.

C++의 SHA256 알고리즘용 헤더 파일

#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

C++에서 SHA256을 실행하기 위한 기본 파일

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

출력:

sha256('Paris'):5dd272b4f316b776a7b8e3d0894b37e1e42be3d5d3b204b8a5836cc50597a6b1

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

결론

이 기사에서는 C++를 사용한 SHA256 변환의 암호화에 대해 자세히 설명했습니다. 암호화 해싱의 핵심인 해싱 알고리즘과 메시지 일정 압축을 코드 스니펫으로 시연하여 이해를 쉽게 합니다.

바로 사용할 수 있는 C++ 프로그램도 첨부되어 학습 및 백테스팅 작업에 바로 투입됩니다.

관련 문장 - C++ Algorithm