C言語でSHA256を自作した
ある日、うちの学校でのチーム課題でこのようなものを出された
- webアプリを作成してください
- Laravel以外のパッケージを使わないでください
- php以外のサーバを建てないでください
私の個人的な野望
JSON Web Tokenを使用して開発をしてみたい
JWTでアカウントのセッション管理をしてみてぇ!
だがしかし…
Laravel以外のパッケージを使用しないでください
なんてことだ…
firebase/php-jwtくんが使えないじゃないか…
…ないなら作ればいいか☆
そういやphpにはFFIとかいう標準機能があったな…
というわけでどうせならということで
作った
#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もいるって?
よし、やるか…
続く…かもしれない