0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

sha256くんを自作した

Last updated at Posted at 2025-07-18

C言語でSHA256を自作した

ある日、うちの学校でのチーム課題でこのようなものを出された

  1. webアプリを作成してください
  2. Laravel以外のパッケージを使わないでください
  3. php以外のサーバを建てないでください

私の個人的な野望

JSON Web Tokenを使用して開発をしてみたい
JWTでアカウントのセッション管理をしてみてぇ!

だがしかし…

Laravel以外のパッケージを使用しないでください
なんてことだ…
firebase/php-jwtくんが使えないじゃないか…
…ないなら作ればいいか☆
そういやphpにはFFIとかいう標準機能があったな…
というわけでどうせならということで

作った

sha256.c
#include "sha256.h"
#include <string.h> 
#include <stdint.h> 
#include <stdio.h> 
#include <stdlib.h> 

// junkan_shift_rightくんのおしゃれな書き方… この数年で私は成長しました
#define ROTR(x, n) (((x) >> (n)) | ((x) << (32 - (n))))

// 初期ハッシュ値 (H0-H7)
static const uint32_t H[] = {
    0x6a09e667UL, 0xbb67ae85UL, 0x3c6ef372UL, 0xa54ff53aUL,
    0x510e527fUL, 0x9b05688cUL, 0x1f83d9abUL, 0x5be0cd19UL
};

// ラウンド定数 (K0-K63)
static const uint32_t K[] = {
    0x428a2f98UL, 0x71374491UL, 0xb5c0fbcfUL, 0xe9b5dba5UL, 0x3956c25bUL, 0x59f111f1UL, 0x923f82a4UL, 0xab1c5ed5UL,
    0xd807aa98UL, 0x12835b01UL, 0x243185beUL, 0x550c7dc3UL, 0x72be5d74UL, 0x80deb1feUL, 0x9bdc06a7UL, 0xc19bf174UL,
    0xe49b69c1UL, 0xefbe4786UL, 0x0fc19dc6UL, 0x240ca1ccUL, 0x2de92c6fUL, 0x4a7484aaUL, 0x5cb0a9dcUL, 0x76f988daUL,
    0x983e5152UL, 0xa831c66dUL, 0xb00327c8UL, 0xbf597fc7UL, 0xc6e00bf3UL, 0xd5a79147UL, 0x06ca6351UL, 0x14292967UL,
    0x27b70a85UL, 0x2e1b2138UL, 0x4d2c6dfcUL, 0x53380d13UL, 0x650a7354UL, 0x766a0abbUL, 0x81c2c92eUL, 0x92722c85UL,
    0xa2bfe8a1UL, 0xa81a664bUL, 0xc24b8b70UL, 0xc76c51a3UL, 0xd192e819UL, 0xd6990624UL, 0xf40e3585UL, 0x106aa070UL,
    0x19a4c116UL, 0x1e376c08UL, 0x2748774cUL, 0x34b0bcb5UL, 0x391c0cb3UL, 0x4ed8aa4aUL, 0x5b9cca4fUL, 0x682e6ff3UL,
    0x748f82eeUL, 0x78a5636fUL, 0x84c87814UL, 0x8cc70208UL, 0x90befffaUL, 0xa4506cebUL, 0xbef9a3f7UL, 0xc67178f2UL
};

// ビッグエンディアンからホストバイトオーダーへの変換 (32ビット)
static uint32_t big_endian_to_host_32(const unsigned char *bytes) {
    return ((uint32_t)bytes[0] << 24) |
           ((uint32_t)bytes[1] << 16) |
           ((uint32_t)bytes[2] << 8)  |
           ((uint32_t)bytes[3]);
}

// ホストバイトオーダーからビッグエンディアンへの変換 (32ビット)
static void host_to_big_endian_32(uint32_t val, unsigned char *bytes) {
    bytes[0] = (unsigned char)((val >> 24) & 0xFF);
    bytes[1] = (unsigned char)((val >> 16) & 0xFF);
    bytes[2] = (unsigned char)((val >> 8) & 0xFF);
    bytes[3] = (unsigned char)(val & 0xFF);
}

void sha256_hash(const unsigned char *message, size_t len, unsigned char *output_hash) {
    uint32_t h[8];
    memcpy(h, H, sizeof(H)); // 初期ハッシュ値をコピー

    // メッセージのパディング
    size_t original_len_bits = len * 8;
    size_t padded_len_bits = ((original_len_bits + 1 + 64 + 511) / 512) * 512; // +1 for '1', +64 for length, +511 for ceiling to 512 multiple
    size_t padded_len_bytes = padded_len_bits / 8;

    unsigned char *padded_message = (unsigned char *)malloc(padded_len_bytes);
    if (padded_message == NULL) {
        // エラー処理
        return;
    }
    memset(padded_message, 0, padded_len_bytes);
    memcpy(padded_message, message, len);

    padded_message[len] = 0x80; // '1'ビットを追加

    // 元の長さを64ビットで追加 (ビッグエンディアン)
    uint64_t original_len_bits_be = (uint64_t)original_len_bits;
    // ホストバイトオーダーからビッグエンディアンへの変換
    padded_message[padded_len_bytes - 8] = (unsigned char)((original_len_bits_be >> 56) & 0xFF);
    padded_message[padded_len_bytes - 7] = (unsigned char)((original_len_bits_be >> 48) & 0xFF);
    padded_message[padded_len_bytes - 6] = (unsigned char)((original_len_bits_be >> 40) & 0xFF);
    padded_message[padded_len_bytes - 5] = (unsigned char)((original_len_bits_be >> 32) & 0xFF);
    padded_message[padded_len_bytes - 4] = (unsigned char)((original_len_bits_be >> 24) & 0xFF);
    padded_message[padded_len_bytes - 3] = (unsigned char)((original_len_bits_be >> 16) & 0xFF);
    padded_message[padded_len_bytes - 2] = (unsigned char)((original_len_bits_be >> 8) & 0xFF);
    padded_message[padded_len_bytes - 1] = (unsigned char)(original_len_bits_be & 0xFF);

    // 512ビットのブロックごとに処理
    for (size_t i = 0; i < padded_len_bytes; i += 64) {
        uint32_t w[64];
        // 最初の16ワード
        for (int j = 0; j < 16; ++j) {
            w[j] = big_endian_to_host_32(&padded_message[i + j * 4]);
        }

        // 17番目以降のワードを生成
        for (int j = 16; j < 64; ++j) {
            uint32_t s0 = ROTR(w[j-15], 7) ^ ROTR(w[j-15], 18) ^ (w[j-15] >> 3);
            uint32_t s1 = ROTR(w[j-2], 17) ^ ROTR(w[j-2], 19) ^ (w[j-2] >> 10);
            w[j] = (w[j-16] + s0 + w[j-7] + s1); // & 0xFFFFFFFF はuint32_tで自動的に行われる
        }

        // 圧縮関数 (ワーキング変数の初期化)
        uint32_t a = h[0];
        uint32_t b = h[1];
        uint32_t c = h[2];
        uint32_t d = h[3];
        uint32_t e = h[4];
        uint32_t f = h[5];
        uint32_t g = h[6];
        uint32_t _h = h[7];

        // 64ラウンドの計算
        for (int j = 0; j < 64; ++j) {
            uint32_t S1 = ROTR(e, 6) ^ ROTR(e, 11) ^ ROTR(e, 25);
            uint32_t ch = (e & f) ^ (~e & g);
            uint32_t temp1 = (_h + S1 + ch + K[j] + w[j]);

            uint32_t S0 = ROTR(a, 2) ^ ROTR(a, 13) ^ ROTR(a, 22);
            uint32_t maj = (a & b) ^ (a & c) ^ (b & c);
            uint32_t temp2 = (S0 + maj);

            _h = g;
            g = f;
            f = e;
            e = (d + temp1);
            d = c;
            c = b;
            b = a;
            a = (temp1 + temp2);
        }

        // ハッシュ値の更新
        h[0] += a;
        h[1] += b;
        h[2] += c;
        h[3] += d;
        h[4] += e;
        h[5] += f;
        h[6] += g;
        h[7] += _h;
    }

    // 最終的なハッシュ値の生成
    // 8つの32ビット値をビッグエンディアンのバイト配列に変換
    for (int j = 0; j < 8; ++j) {
        host_to_big_endian_32(h[j], &output_hash[j * 4]);
    }

    free(padded_message);
}


自力+AI... AI9割かもしれない

各部説明

使用する定数たち


// 初期ハッシュ値 (H0-H7)
static const uint32_t H[] = {
    0x6a09e667UL, 0xbb67ae85UL, 0x3c6ef372UL, 0xa54ff53aUL,
    0x510e527fUL, 0x9b05688cUL, 0x1f83d9abUL, 0x5be0cd19UL
};

// ラウンド定数 (K0-K63)
static const uint32_t K[] = {
    0x428a2f98UL, 0x71374491UL, 0xb5c0fbcfUL, 0xe9b5dba5UL, 0x3956c25bUL, 0x59f111f1UL, 0x923f82a4UL, 0xab1c5ed5UL,
    0xd807aa98UL, 0x12835b01UL, 0x243185beUL, 0x550c7dc3UL, 0x72be5d74UL, 0x80deb1feUL, 0x9bdc06a7UL, 0xc19bf174UL,
    0xe49b69c1UL, 0xefbe4786UL, 0x0fc19dc6UL, 0x240ca1ccUL, 0x2de92c6fUL, 0x4a7484aaUL, 0x5cb0a9dcUL, 0x76f988daUL,
    0x983e5152UL, 0xa831c66dUL, 0xb00327c8UL, 0xbf597fc7UL, 0xc6e00bf3UL, 0xd5a79147UL, 0x06ca6351UL, 0x14292967UL,
    0x27b70a85UL, 0x2e1b2138UL, 0x4d2c6dfcUL, 0x53380d13UL, 0x650a7354UL, 0x766a0abbUL, 0x81c2c92eUL, 0x92722c85UL,
    0xa2bfe8a1UL, 0xa81a664bUL, 0xc24b8b70UL, 0xc76c51a3UL, 0xd192e819UL, 0xd6990624UL, 0xf40e3585UL, 0x106aa070UL,
    0x19a4c116UL, 0x1e376c08UL, 0x2748774cUL, 0x34b0bcb5UL, 0x391c0cb3UL, 0x4ed8aa4aUL, 0x5b9cca4fUL, 0x682e6ff3UL,
    0x748f82eeUL, 0x78a5636fUL, 0x84c87814UL, 0x8cc70208UL, 0x90befffaUL, 0xa4506cebUL, 0xbef9a3f7UL, 0xc67178f2UL
};

H[]には三乗根の少数部分を整数にしたものが入っている
W[]には平方根の少数部分を整数にしたものが入っている

ビックエンディアンくんの変換

// ビッグエンディアンからホストバイトオーダーへの変換 (32ビット)
static uint32_t big_endian_to_host_32(const unsigned char *bytes) {
    return ((uint32_t)bytes[0] << 24) |
           ((uint32_t)bytes[1] << 16) |
           ((uint32_t)bytes[2] << 8)  |
           ((uint32_t)bytes[3]);
}

// ホストバイトオーダーからビッグエンディアンへの変換 (32ビット)
static void host_to_big_endian_32(uint32_t val, unsigned char *bytes) {
    bytes[0] = (unsigned char)((val >> 24) & 0xFF);
    bytes[1] = (unsigned char)((val >> 16) & 0xFF);
    bytes[2] = (unsigned char)((val >> 8) & 0xFF);
    bytes[3] = (unsigned char)(val & 0xFF);
}

Cの辛い? 好き?なところ
数値型をbyteに変換すると、メモリ的には逆になっているから
それを治すためのプログラム

メッセージのパディング

// メッセージのパディング
    size_t original_len_bits = len * 8;
    size_t padded_len_bits = ((original_len_bits + 1 + 64 + 511) / 512) * 512; // +1 for '1', +64 for length, +511 for ceiling to 512 multiple
    size_t padded_len_bytes = padded_len_bits / 8;

    unsigned char *padded_message = (unsigned char *)malloc(padded_len_bytes);
    if (padded_message == NULL) {
        // エラー処理
        return;
    }
    memset(padded_message, 0, padded_len_bytes);
    memcpy(padded_message, message, len);

    padded_message[len] = 0x80; // '1'ビットを追加

    // 元の長さを64ビットで追加 (ビッグエンディアン)
    uint64_t original_len_bits_be = (uint64_t)original_len_bits;
    // ホストバイトオーダーからビッグエンディアンへの変換
    padded_message[padded_len_bytes - 8] = (unsigned char)((original_len_bits_be >> 56) & 0xFF);
    padded_message[padded_len_bytes - 7] = (unsigned char)((original_len_bits_be >> 48) & 0xFF);
    padded_message[padded_len_bytes - 6] = (unsigned char)((original_len_bits_be >> 40) & 0xFF);
    padded_message[padded_len_bytes - 5] = (unsigned char)((original_len_bits_be >> 32) & 0xFF);
    padded_message[padded_len_bytes - 4] = (unsigned char)((original_len_bits_be >> 24) & 0xFF);
    padded_message[padded_len_bytes - 3] = (unsigned char)((original_len_bits_be >> 16) & 0xFF);
    padded_message[padded_len_bytes - 2] = (unsigned char)((original_len_bits_be >> 8) & 0xFF);
    padded_message[padded_len_bytes - 1] = (unsigned char)(original_len_bits_be & 0xFF);

sha256は、メッセージを512bitを一ブロックとして変換する
これに加えて、メッセージの終了地点には80を、ブロックの最後には変換前のデータサイズを挿入する

立役者Wくん

    uint32_t w[64];
        // 最初の16ワード
        for (int j = 0; j < 16; ++j) {
            w[j] = big_endian_to_host_32(&padded_message[i + j * 4]);
        }

        // 17番目以降のワードを生成
        for (int j = 16; j < 64; ++j) {
            uint32_t s0 = ROTR(w[j-15], 7) ^ ROTR(w[j-15], 18) ^ (w[j-15] >> 3);
            uint32_t s1 = ROTR(w[j-2], 17) ^ ROTR(w[j-2], 19) ^ (w[j-2] >> 10);
            w[j] = (w[j-16] + s0 + w[j-7] + s1); // & 0xFFFFFFFF はuint32_tで自動的に行われる
        }

sha256の中の一番大事な部分
最初の16ワードは512bitをそのまま使用

ここから64ワードまでは先の16ワードを使用して計算していく
計算の細かいことは…コードから読み取るか先人たちの記事を読んで…
(まあ実際このままではあるんだが)

abcdefghくん

        uint32_t a = h[0];
        uint32_t b = h[1];
        uint32_t c = h[2];
        uint32_t d = h[3];
        uint32_t e = h[4];
        uint32_t f = h[5];
        uint32_t g = h[6];
        uint32_t _h = h[7];

        // 64ラウンドの計算
        for (int j = 0; j < 64; ++j) {
            uint32_t S1 = ROTR(e, 6) ^ ROTR(e, 11) ^ ROTR(e, 25);
            uint32_t ch = (e & f) ^ (~e & g);
            uint32_t temp1 = (_h + S1 + ch + K[j] + w[j]);

            uint32_t S0 = ROTR(a, 2) ^ ROTR(a, 13) ^ ROTR(a, 22);
            uint32_t maj = (a & b) ^ (a & c) ^ (b & c);
            uint32_t temp2 = (S0 + maj);

            _h = g;
            g = f;
            f = e;
            e = (d + temp1);
            d = c;
            c = b;
            b = a;
            a = (temp1 + temp2);
        }

        // ハッシュ値の更新
        h[0] += a;
        h[1] += b;
        h[2] += c;
        h[3] += d;
        h[4] += e;
        h[5] += f;
        h[6] += g;
        h[7] += _h;

一番最初のHくんを初期値として、W,Kを絡ませて変換をしていく
なんでか、どの記事を読んでもここabcdefghになってるのはなんでなんだろう

というわけで

for (int j = 0; j < 8; ++j) {
        host_to_big_endian_32(h[j], &output_hash[j * 4]);
}

あとはいい感じに変換してやれば…
出来上がり!

実践

#include <stdio.h>
#include <string.h>
#include "sha256.h"
int main() {
    const char *input_text = "Hello world!";
    unsigned char output_hash[32];

    sha256_hash((const unsigned char *)input_text, strlen(input_text), output_hash);

    printf("入力: '%s'\n", input_text);
    printf("SHA-256 ハッシュ: ");
    for (int i = 0; i < 32; ++i) {
        printf("%02x", output_hash[i]);
    }
    printf("\n");


    return 0;
}

こいつは適当なテストコード
こいつのinput_textを変換しながら実行していく

結果

入力: 'Hello world!'
SHA-256 ハッシュ: c0535e4be2b79ffd93291305436bf889314e4a3faec05ecffcbb7df31ad9e51a

いいね!

入力: ''
SHA-256 ハッシュ: e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855

この数列、一部の人は丸暗記してるって…ほんと?

入力: 'password'
SHA-256 ハッシュ: 5e884898da28047151d0e56f8dc6292773603d0d6aabbdd62a11ef721d1542d8

passwordってsha256だとこうなのか…覚えとこ

ちゃんとできてる!

終わりに

SHA256はhmacやらなんやらで、今後もまだ使用されていくhashの一つだと考えられるので、こうして学生の段階でロジックを学べたのは、幸運かもしれない
これからも現代技術のロジックを調べ、実装し、自分の糧としていきたい

...え?jwtにはsha256だけじゃなくて、base64もいるって?
よし、やるか…

続く…かもしれない

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?