C++ で SHA256 に変換

Jay Shaw 2023年10月12日
  1. SHA256 ハッシュ アルゴリズム
  2. プレーン テキストを SHA256 に変換する C++ プログラム
  3. まとめ
C++ で SHA256 に変換

SHA256 変換は、1990 年代から使用されている暗号化ハッシュ技術ですが、ビットコインとブロックチェーンの登場後に注目を集めました。

不可逆的な一方向ハッシュ技術を使用しているため、最終的な回答を元のメッセージに戻すことはできないため、非常に安全です。

最終的なメッセージは 256 ビット形式で表されるため、名前に 256 が含まれています。

SHA256 ハッシュ アルゴリズム

SHA256 変換に使用されるハッシュ アルゴリズムには、パディング、初期バッファー、メッセージ スケジュールの作成、および圧縮の 4つの部分があります。 それぞれを C++ で実装する方法を理解しましょう。

SHA256 のパディング ビット

SHA256 変換のハッシュ アルゴリズムの最初のステップは、元のメッセージのパディングです。 パディングは、メッセージが 448 ビット (512 ビットより 64 ビット少ない) になるように行う必要があります。

abc という文字を含むメッセージをパディングする必要がある場合、最初の 24 ビットは abc のバイナリ値で埋められ、その後に 424 ビットのゼロが続き、合計 448 ビットになります。 長さは最後の 64 ビットに格納されます。

SHA256 のパディングの長さ

元のメッセージは、ハッシュに使用する前に 512 ビット長になるようにパディングされます。 最初の 448 ビットがメッセージを格納し、残りの 64 ビットのデータが長さのパディングになるように、メッセージをパディングする必要があります。

これらの未使用ビットは、メッセージの長さを追跡するために使用されます。 これらの 64 ビットは、1 でなければならない最初の桁と、メッセージの長さを格納する残りの最後の桁を除いて、ほとんどゼロで埋められます。

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 ビット長で、このスケジュールは 2 進数の 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 $$

sigma 0 および 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))

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 変換ハッシュ関数は、変更される前に同じ状態レジスタから始まります。 この関数は、次を使用して 2つの一時的な単語を作成します。

$$ 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) は機能のメジャーを表します。 これら 2つの機能について、以下で詳しく説明します。

これらの一時的な単語が作成されると、関数は 2つの一時的な単語を追加し、状態レジスタ内のすべての単語を 1 桁下にシフトし、追加された単語を最初の状態レジスタ内に埋め、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))

どこ:

  • x & y = x と y
  • ~x = x の否定
  • ^ = XOR

このメッセージ ブロックの圧縮の最後の部分は、最初に使用した初期ハッシュ値を取得し、それらを圧縮の結果に加算することです。 最終的なハッシュ値は 16 進数に変換され、連結されて最終的なメッセージ ダイジェストが作成されます。

プレーン テキストを SHA256 に変換する C++ プログラム

このプログラムには 2つの部分があります。定義されたすべての重要な関数を含む 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