【Go】暗号化やハッシュ化について考えるついでにSHA256を実装する

今週から仕事始めの方も多いことでしょう。
連休終わりの気怠さを感じると「特に連休なんていらないな」という気持ちになりますよね。
飲みすぎた次の日の「もうお酒なんて飲まない」に似てるなと思います。
でも翌々日くらいには体が欲するんですよねー休日も酒も。

さて前回は通信について色々調べました。
そして今回は通信にも多分に使用される暗号について考えてみます!
暗号といえば自来也が死の間際にフカサク様の背中に刻んだモノが思い出されて涙がちょちょぎれますね!

暗号(あんごう)とは、セキュア通信の手法の種類で、第三者が通信文を見ても特別な知識なしでは読めないように変換する、というような手法をおおまかには指す。いわゆる「通信」(telecommunications)に限らず、記録媒体への保存などにも適用できる。

暗号 – Wikipedia https://ja.wikipedia.org/wiki/%E6%9A%97%E5%8F%B7

暗号なんて小難しそうなモノですが「通信文を第三者が簡単にはわからないようにする手法」なんて簡単に書いてくれちゃってます。

停車中にブレーキランプを5回点滅させると日本人には「アイシテル」と伝わるこれも暗号ということになりますね。
暗号のハードルがガックリ下がったところで入門にピッタリの暗号「シーザー暗号」についてみてみよー。

シーザー暗号

「ブルータスお前もか」でおなじみのシーザー(ユリウス・カエサルの英語読み)が使用した暗号です。

この暗号化は簡単で「アルファベットを何文字か後ろ(前)にずらす」だけです。例えば「HELLO」を「KHOOR」のように表したりします。

シーザー暗号を復号してみる

「KHOOR」から元の単語(HELLOのことは忘れてください)を導きたいと思います。このような暗号化された文字列を元に戻すことを復号と呼びます。

この暗号の優れている点は、以下の2つの事柄が復号のために必要なことです。

  1. これは文字をずらして作ったものだと判断すること
  2. 何文字ずらしているか

いきなり友達から「KHOOR」が送られてきて、「暗号だ!」ってなる人は相当おかしい人ですよね。普通の人はこれを暗号と認識することから難しいのです。ってか私ならまずアナグラム(文字を入れ替えて単語にするやつ)を疑いますし。

さて、これがシーザー暗号っぽいなと思ったとして、問題は何文字ずらしているかです。アルファベットは全部で26文字なので25回試行すれば絶対に答えに行き当たるはずです。
じゃあ1つずつ前にずらしてみましょうか。

「KHOOR」=>「JGNNQ」=>「IFMMP」=>「HELLO」

お?3回ずらしたら単語になりました!おそらく「HELLO」で正解でしょう!
もし1つずつ後ろにずらしてたら23回もやるところでしたよ。

暗号化は可逆変換

暗号化というのは突き詰めれば文字列の変換です。その変換によって生み出されたモノは復号という作業によって元に戻すことができます。
このように「元に戻せる変換」のことを「可逆変換」と呼びます。

「当たり前じゃん!暗号が元に戻せなかったら迷宮入りになっちゃうじゃん!」

えぇ。わかります。でも元に戻せるというのは同時に弱点でもあるのです。

可逆は危険がいっぱい

みなさんも会員制サイトに登録する時にパスワードを設定しますよね?
そのパスワードってどう管理されてると思います?
極稀にニュースで「パスワードを平文でデータベースに保存・・・」みたいなの見かけませんか?平文ってのは何も加工されていない生の文字列のことです。
ってことは「info@air-h.jp,airHakodate1234」みたいに保存されてるわけです。

もし、そのサイトが乗っ取られてパスワードが流出したらどうなると思います?
多くの人は1つのパスワードを複数のサイトで使いまわしています。
このメールアドレスとパスワードのセットを色んなサイトで試されれば、別のサイトのアカウントを乗っ取られちゃうかもしれないんです!

元の文字列を暗号化して保存すれば大丈夫だと思います?
じゃあ暗号化しました。「info@air-h.jp,zkfNz,lczgdqwer」
これが流出しても元の文字列は分からないだろうと思ったら大間違い。
「キーボードのホームポジションを一段下げて元の文字列と同じストロークで入力した感じっぽい。」ってバレたら「zkfNz,lczgdqwer = airHakodate1234」さっきと同じ末路です。

今の技術の暗号化はもっと高度でバレづらくなっています。
でも後述する「ハッシュ化」に比べると元に戻せてしまうのは危険すぎます。
みなさんもパスワードをデータベースに保存する際は暗号化だけはしないでくださいね。

そこでハッシュ化ですよ

これも暗号化のように文字列を別の文字列へ変換することを指します。が、こちらは「不可逆変換」となっています。

つまり「airHakodate1234」は「asli3hLiuqywDG23howfa86oDFjf(適当)」に変換されるが「asli3hLiuqywDG23howfa86oDFjf」から「airHakodate1234」には元に戻せないのです。

先ほどのパスワードのような案件には絶対ハッシュ化を使用してくださいね。
流出したところで元の文字列に戻せないんですから、安全安心です。

ハッシュ化とは?

ハッシュ関数という「入力に対してユニークなハッシュ値を出力する計算」を使用して文字列を不可逆変換することです。(この表現はあまり正しく無いです)
このハッシュ関数には以下の特性があります(あることが求められる)。

1. ハッシュ値から、そのようなハッシュ値となるメッセージを得ることが(事実上)不可能であること(原像計算困難性、弱衝突耐性)。

2. 同じハッシュ値となる、異なる2つのメッセージのペアを求めることが(事実上)不可能であること(強衝突耐性)。

3. メッセージをほんの少し変えたとき、ハッシュ値は大幅に変わり、元のメッセージのハッシュ値とは相関がないように見えること。

暗号学的ハッシュ関数- Wikipedia https://ja.wikipedia.org/wiki/%E6%9A%97%E5%8F%B7%E5%AD%A6%E7%9A%84%E3%83%8F%E3%83%83%E3%82%B7%E3%83%A5%E9%96%A2%E6%95%B0#%E7%89%B9%E6%80%A7

1.が不可逆であることの説明ですね!

2.は入力に対してユニークなハッシュ値を出力しますよーってことです。
入力1と入力2で同じハッシュ値が出力されることを衝突と呼びます。

3.が重要で、相関があったらなんとなく近いとかわかっちゃいますからね。

ハッシュ関数といえばSHAですよ

ハッシュ関数と聞けばSHAが挙げられます。
Secure Hash Algorithm(セキュア ハッシュ アルゴリズム)の略でして、通信やら認証やら色んなところで使われています。
ビットコインはこれが無いと成り立ちません。正しくはビットコインを根元から支える技術、ブロックチェーンはSHAによって成り立っています。

SHAにはバージョンがある

最初のバージョンはSHA-1と呼ばれていて、もうほとんど使用されていないかと思われます。というのも衝突が出たんです。
そこから一気にSHA-1の信頼は地に落ちてSHA-2へバトンタッチしたわけです。

そのSHA-2は今の主流のハッシュ関数です。
SHA-1の強化版って感じです。計算方法もよく似ています。
SHA256とSHA512なんかがよく使われていますね。

そしてそしてSHA-3です。
SHA-1とSHA-2はほぼ同じようなアルゴリズムだったのに対して、全く別のアプローチの仕方をとりました。
今のところSHA-2が破られていないので、まだ息を潜めています。

計算方法は大っぴらに公開されています。(FIPS180-4)
計算方法は解っても攻撃方法が分からないのが強みですよね。

SHA256をGoで実装してみる

コードの全貌

package main

import (
	"fmt"
	"os"
)

func main() {
	if len(os.Args) != 2 {
		os.Exit(1)
	}

	// ハッシュの初期値
	H := []uint32{
		0x6a09e667,
		0xbb67ae85,
		0x3c6ef372,
		0xa54ff53a,
		0x510e527f,
		0x9b05688c,
		0x1f83d9ab,
		0x5be0cd19,
	}

	// 演算に使用するK定数
	k := []uint32{
		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,
	}

	// 入力の受け取り
	input := []byte(os.Args[1])
	// パディング処理
	pad := Padding(input, 64)

	// 64byteで分割してメッセージブロックの作成
	msgblocks := Split(pad, 64)
	for _, bl := range msgblocks {
		// メッセージブロックから64個のワード(uint32)配列を作成
		words := uint32Array(bl)
		for i := 16; i < 64; i++ {
			w := SmallSigma1(words[i-2]) + words[i-7] + SmallSigma0(words[i-15]) + words[i-16]
			words = append(words, w)
		}

		// ローテーション処理前の初期値
		a := H[0]
		b := H[1]
		c := H[2]
		d := H[3]
		e := H[4]
		f := H[5]
		g := H[6]
		h := H[7]

		// ローテーション処理
		for t, w := range words {
			T1 := h + LargeSigma1(e) + Ch(e, f, g) + k[t] + w
			T2 := LargeSigma0(a) + Maj(a, b, c)
			h = g
			g = f
			f = e
			e = d + T1
			d = c
			c = b
			b = a
			a = T1 + T2
		}

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

		// 次のメッセージブロックへ
	}

	// ハッシュ値の表示
	for _, h := range H {
		fmt.Printf("%x", h)
	}
}

// 入力のバイト配列をlengthの倍数にする
func Padding(input []byte, length int) []byte {
	l := len(input)
	bits := l * 8
	mod := l % length
	padcount := length - mod
	if mod > length-8 {
		padcount += 64
	}
	for i := 0; i < padcount; i++ {
		if i == 0 {
			// 入力の区切りに0x80を入れる
			input = append(input, 0x80)
		} else {
			// そのほかは0x00で埋める
			input = append(input, 0x00)
		}
	}
	// 最後の8バイト(uint64)は入力のビット数
	for i := 1; i <= 8; i++ {
		input[len(input)-i] = byte(bits & 0xff)
		bits = bits >> 8
	}
	return input
}

// 入力のバイト配列をlength個ずつに分割
func Split(input []byte, length int) [][]byte {
	var barr [][]byte
	n := len(input) / length
	for i := 0; i < n; i++ {
		barr = append(barr, input[i*length:(i+1)*length])
	}
	return barr
}

// バイト配列をuint32の配列に変換
func uint32Array(b []byte) []uint32 {
	var arr []uint32
	for i := 0; i < len(b)/4; i++ {
		var v uint32
		v += uint32(b[i*4]) << 24
		v += uint32(b[i*4+1]) << 16
		v += uint32(b[i*4+2]) << 8
		v += uint32(b[i*4+3])
		arr = append(arr, v)
	}
	return arr
}

func Ch(x, y, z uint32) uint32 {
	return (x & y) ^ (^x & z)
}

func Maj(x, y, z uint32) uint32 {
	return (x & y) ^ (x & z) ^ (y & z)
}

func SmallSigma0(x uint32) uint32 {
	return Rotr(x, 7) ^ Rotr(x, 18) ^ Shr(x, 3)
}
func SmallSigma1(x uint32) uint32 {
	return Rotr(x, 17) ^ Rotr(x, 19) ^ Shr(x, 10)
}
func LargeSigma0(x uint32) uint32 {
	return Rotr(x, 2) ^ Rotr(x, 13) ^ Rotr(x, 22)
}
func LargeSigma1(x uint32) uint32 {
	return Rotr(x, 6) ^ Rotr(x, 11) ^ Rotr(x, 25)
}

// 右ローテーション
func Rotr(x, n uint32) uint32 {
	return x<<(32-n) | x>>n
}

// 右シフト
func Shr(x, n uint32) uint32 {
	return x >> n
}

仕様書の通りに作りました。ちょっとずつ追いかけてみますか。

ハッシュの初期値 H

...
	// ハッシュの初期値
	H := []uint32{
		0x6a09e667,
		0xbb67ae85,
		0x3c6ef372,
		0xa54ff53a,
		0x510e527f,
		0x9b05688c,
		0x1f83d9ab,
		0x5be0cd19,
	}
...

ハッシュの初期値です。
入力byte配列を使用してこの値を変えていきます。

定数 K

...
	// 演算に使用するK定数
	k := []uint32{
		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,
	}
...

演算に使用する定数です。
64個の素数の立方根の少数部分とかを表しているとかなんとか。

演算に必要な関数

...
func Ch(x, y, z uint32) uint32 {
	return (x & y) ^ (^x & z)
}

func Maj(x, y, z uint32) uint32 {
	return (x & y) ^ (x & z) ^ (y & z)
}

// σ0
func SmallSigma0(x uint32) uint32 {
	return Rotr(x, 7) ^ Rotr(x, 18) ^ Shr(x, 3)
}

// σ1
func SmallSigma1(x uint32) uint32 {
	return Rotr(x, 17) ^ Rotr(x, 19) ^ Shr(x, 10)
}

// Σ0
func LargeSigma0(x uint32) uint32 {
	return Rotr(x, 2) ^ Rotr(x, 13) ^ Rotr(x, 22)
}

// Σ1
func LargeSigma1(x uint32) uint32 {
	return Rotr(x, 6) ^ Rotr(x, 11) ^ Rotr(x, 25)
}

// 右ローテーション
func Rotr(x, n uint32) uint32 {
	return x<<(32-n) | x>>n
}

// 右シフト
func Shr(x, n uint32) uint32 {
	return x >> n
}
...

仕様書にある関数を素直に作りました。
Goはこの辺の演算が楽で助かります。

入力を受け取る

...
  // 入力の受け取り
	input := []byte(os.Args[1])
...

受け取った入力(string)はbyte配列にキャストします。

パディング処理

...
	// パディング処理
	pad := Padding(input, 64)
...
...
// 入力のバイト配列をlengthの倍数にする
func Padding(input []byte, length int) []byte {
	l := len(input)
	bits := l * 8
	mod := l % length
	padcount := length - mod
	if mod > length-8 {
		padcount += 64
	}
	for i := 0; i < padcount; i++ {
		if i == 0 {
			// 入力の区切りに0x80を入れる
			input = append(input, 0x80)
		} else {
			// そのほかは0x00で埋める
			input = append(input, 0x00)
		}
	}
	// 最後の8バイト(uint64)は入力のビット数
	for i := 1; i <= 8; i++ {
		input[len(input)-i] = byte(bits & 0xff)
		bits = bits >> 8
	}
	return input
}
...

(入力bytes+以下の9bytes)が64 bytesに満たない場合には、0x00で埋めて64bytesにします。64bytesを超える場合は(64の倍数)bytesになるように調整します。

  • 入力bytes直後に0x80を入れる
  • byte配列の末尾8byteにビッグエンディアンで入力のビット数(入力bytes数 ×8bits)

「aiueo」という入力をした場合には、以下のbyte配列となります。

[ 61 69 75 65 6f 80 00 00 00 00 00 00 00 00 00 00
  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 28 ]

入力の5bytes直後に0x80がつき、入力が5bytes = 40bitsなので末尾に0x28 = 40が配列末尾に0x28がつきます。ほかは0で埋めて64bytesの配列になりました。

メッセージブロック(64bytes)に分割

...
	// 64byteで分割してメッセージブロックの作成
	msgblocks := Split(pad, 64)
...
...
// 入力のバイト配列をlength個ずつに分割
func Split(input []byte, length int) [][]byte {
	var barr [][]byte
	n := len(input) / length
	for i := 0; i < n; i++ {
		barr = append(barr, input[i*length:(i+1)*length])
	}
	return barr
}
...

パディング処理したメッセージを64bytes毎の配列に分割します。
パディング処理したメッセージが128bytesなら2つのメッセージブロックになります。
「aiueo」なら1つのメッセージブロックです。
ここからメッセージブロックを1つずつ使ってハッシュ値を演算していきます。

メッセージスケジュールを準備

...
	for _, bl := range msgblocks {
		// メッセージブロックから64個のワード(uint32)配列(メッセージスケジュール)を作成
		words := uint32Array(bl)
		for i := 16; i < 64; i++ {
			w := SmallSigma1(words[i-2]) + words[i-7] + SmallSigma0(words[i-15]) + words[i-16]
			words = append(words, w)
		}
...
...
// バイト配列をuint32の配列に変換
func uint32Array(b []byte) []uint32 {
	var arr []uint32
	for i := 0; i < len(b)/4; i++ {
		var v uint32
		v += uint32(b[i*4]) << 24
		v += uint32(b[i*4+1]) << 16
		v += uint32(b[i*4+2]) << 8
		v += uint32(b[i*4+3])
		arr = append(arr, v)
	}
	return arr
}
...

メッセージブロック(64bytes)からメッセージスケジュール(uint32 × 64 = 256bytes)を作成します。

まずはbytes配列をuint32の配列に変換します。
指定の計算方法でその配列に要素数が64個になるまで値を追加していきます。
これがメッセージスケジュールになります。

ローテーション処理

...
		// ローテーション処理前の初期値
		a := H[0]
		b := H[1]
		c := H[2]
		d := H[3]
		e := H[4]
		f := H[5]
		g := H[6]
		h := H[7]

		// ローテーション処理
		for t, w := range words {
			T1 := h + LargeSigma1(e) + Ch(e, f, g) + k[t] + w
			T2 := LargeSigma0(a) + Maj(a, b, c)
			h = g
			g = f
			f = e
			e = d + T1
			d = c
			c = b
			b = a
			a = T1 + T2
		}
...

a〜hの値を現在のハッシュ値で初期化し、先ほどのメッセージスケジュールのワードたちで演算していきます。
d = c, c = b, b = a…みたいな感じで値がローテーションするので、ローテーション処理とか呼ばれてます。aとeのところで演算が加わるのでa~hの値がだんだん変わっていく感じです。

左からa b c d e f g hの値の変遷です。
同じ値が斜めに走ってるのがわかります。

6a09e667 bb67ae85 3c6ef372 a54ff53a 510e527f 9b05688c 1f83d9ab 5be0cd19
5d71fdb2 6a09e667 bb67ae85 3c6ef372 fa315807 510e527f 9b05688c 1f83d9ab
91955bbe 5d71fdb2 6a09e667 bb67ae85 956a02f3 fa315807 510e527f 9b05688c
cc2250a4 91955bbe 5d71fdb2 6a09e667 178df70 956a02f3 fa315807 510e527f
e53564c2 cc2250a4 91955bbe 5d71fdb2 32a18b68 178df70 956a02f3 fa315807
65fad80f e53564c2 cc2250a4 91955bbe b36e0a0c 32a18b68 178df70 956a02f3
bd82bcd4 65fad80f e53564c2 cc2250a4 778023ca b36e0a0c 32a18b68 178df70
441088 bd82bcd4 65fad80f e53564c2 237d5290 778023ca b36e0a0c 32a18b68
f6b68649 441088 bd82bcd4 65fad80f 22162c7c 237d5290 778023ca b36e0a0c
c1a5a55f f6b68649 441088 bd82bcd4 dd1eb4aa 22162c7c 237d5290 778023ca
6a971132 c1a5a55f f6b68649 441088 1b732e41 dd1eb4aa 22162c7c 237d5290
28c85d94 6a971132 c1a5a55f f6b68649 f6e28f63 1b732e41 dd1eb4aa 22162c7c
d5ede739 28c85d94 6a971132 c1a5a55f 9c3a56e8 f6e28f63 1b732e41 dd1eb4aa
ad7ee204 d5ede739 28c85d94 6a971132 b2e003d 9c3a56e8 f6e28f63 1b732e41
6ca9dbc5 ad7ee204 d5ede739 28c85d94 68614420 b2e003d 9c3a56e8 f6e28f63
74632b10 6ca9dbc5 ad7ee204 d5ede739 8ccf9f92 68614420 b2e003d 9c3a56e8
7e87ce8c 74632b10 6ca9dbc5 ad7ee204 1ed2a2b5 8ccf9f92 68614420 b2e003d
a86b8e0f 7e87ce8c 74632b10 6ca9dbc5 644e6e28 1ed2a2b5 8ccf9f92 68614420
48e21029 a86b8e0f 7e87ce8c 74632b10 4c8bf64 644e6e28 1ed2a2b5 8ccf9f92
f25f26f2 48e21029 a86b8e0f 7e87ce8c a2ac5dbc 4c8bf64 644e6e28 1ed2a2b5
1afc3382 f25f26f2 48e21029 a86b8e0f b97d6b57 a2ac5dbc 4c8bf64 644e6e28
f0c85228 1afc3382 f25f26f2 48e21029 53d3da2b b97d6b57 a2ac5dbc 4c8bf64
16761b05 f0c85228 1afc3382 f25f26f2 e03ec781 53d3da2b b97d6b57 a2ac5dbc
57b3a142 16761b05 f0c85228 1afc3382 f13d940c e03ec781 53d3da2b b97d6b57
9813693c 57b3a142 16761b05 f0c85228 aa22d2b f13d940c e03ec781 53d3da2b
a54087b2 9813693c 57b3a142 16761b05 5d90c822 aa22d2b f13d940c e03ec781
40f9ef88 a54087b2 9813693c 57b3a142 2b80a7de 5d90c822 aa22d2b f13d940c
344e96f8 40f9ef88 a54087b2 9813693c 7fedeb54 2b80a7de 5d90c822 aa22d2b
32e13a3a 344e96f8 40f9ef88 a54087b2 262234a3 7fedeb54 2b80a7de 5d90c822
f67c454b 32e13a3a 344e96f8 40f9ef88 9151dcf9 262234a3 7fedeb54 2b80a7de
39fe2829 f67c454b 32e13a3a 344e96f8 1db673ce 9151dcf9 262234a3 7fedeb54
56ef4563 39fe2829 f67c454b 32e13a3a 60aad314 1db673ce 9151dcf9 262234a3
39bbe364 56ef4563 39fe2829 f67c454b b1e9ecba 60aad314 1db673ce 9151dcf9
68504e80 39bbe364 56ef4563 39fe2829 2a0a8c8a b1e9ecba 60aad314 1db673ce
5f63cd1 68504e80 39bbe364 56ef4563 97cbcd17 2a0a8c8a b1e9ecba 60aad314
899b87a7 5f63cd1 68504e80 39bbe364 379179b8 97cbcd17 2a0a8c8a b1e9ecba
1cc1314c 899b87a7 5f63cd1 68504e80 9b66d31c 379179b8 97cbcd17 2a0a8c8a
b06c2d19 1cc1314c 899b87a7 5f63cd1 8153abab 9b66d31c 379179b8 97cbcd17
af38c3b0 b06c2d19 1cc1314c 899b87a7 6802ec8e 8153abab 9b66d31c 379179b8
ba4a238e af38c3b0 b06c2d19 1cc1314c b237fe87 6802ec8e 8153abab 9b66d31c
b682aad4 ba4a238e af38c3b0 b06c2d19 7e72582d b237fe87 6802ec8e 8153abab
bb433f70 b682aad4 ba4a238e af38c3b0 3bf67c7b 7e72582d b237fe87 6802ec8e
b8c6635c bb433f70 b682aad4 ba4a238e d4142410 3bf67c7b 7e72582d b237fe87
3c6c0957 b8c6635c bb433f70 b682aad4 e9ad58a d4142410 3bf67c7b 7e72582d
9616c1c6 3c6c0957 b8c6635c bb433f70 5ecb8382 e9ad58a d4142410 3bf67c7b
9a7d5297 9616c1c6 3c6c0957 b8c6635c a8c43612 5ecb8382 e9ad58a d4142410
8b2aafc3 9a7d5297 9616c1c6 3c6c0957 1e0af822 a8c43612 5ecb8382 e9ad58a
671f9dac 8b2aafc3 9a7d5297 9616c1c6 d2e2e6b3 1e0af822 a8c43612 5ecb8382
ca557cd6 671f9dac 8b2aafc3 9a7d5297 4a5a310a d2e2e6b3 1e0af822 a8c43612
def4e40 ca557cd6 671f9dac 8b2aafc3 db7c8c5a 4a5a310a d2e2e6b3 1e0af822
7e1f0350 def4e40 ca557cd6 671f9dac eda79972 db7c8c5a 4a5a310a d2e2e6b3
45cfa22a 7e1f0350 def4e40 ca557cd6 e5867fb2 eda79972 db7c8c5a 4a5a310a
bc3df39 45cfa22a 7e1f0350 def4e40 c9a0e9ef e5867fb2 eda79972 db7c8c5a
ce4eabfd bc3df39 45cfa22a 7e1f0350 d82a2906 c9a0e9ef e5867fb2 eda79972
e5e6ece6 ce4eabfd bc3df39 45cfa22a fd64154a d82a2906 c9a0e9ef e5867fb2
253fd2c7 e5e6ece6 ce4eabfd bc3df39 554b755b fd64154a d82a2906 c9a0e9ef
6d2b1770 253fd2c7 e5e6ece6 ce4eabfd f34249e7 554b755b fd64154a d82a2906
3aeedb2e 6d2b1770 253fd2c7 e5e6ece6 97794315 f34249e7 554b755b fd64154a
6e11a3a 3aeedb2e 6d2b1770 253fd2c7 d2f15a64 97794315 f34249e7 554b755b
63fe16a5 6e11a3a 3aeedb2e 6d2b1770 86523495 d2f15a64 97794315 f34249e7
ce0b482e 63fe16a5 6e11a3a 3aeedb2e 2ba359a 86523495 d2f15a64 97794315
a2844a3f ce0b482e 63fe16a5 6e11a3a b6b3f1d6 2ba359a 86523495 d2f15a64
1a1a19d6 a2844a3f ce0b482e 63fe16a5 95f7f1ba b6b3f1d6 2ba359a 86523495
35c33dbe 1a1a19d6 a2844a3f ce0b482e ef65fd58 95f7f1ba b6b3f1d6 2ba359a
8ffcac06 35c33dbe 1a1a19d6 a2844a3f bf0e40d5 ef65fd58 95f7f1ba b6b3f1d6

ハッシュ値の更新

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

		// 次のメッセージブロックへ
	}
...

先ほどローテーション処理して計算したa〜hをH[0〜7]にそれぞれ足して次のメッセージブロックでも同様に行います。

出力!

	// ハッシュ値の表示
	for _, h := range H {
		fmt.Printf("%x", h)
	}

全てのメッセージブロックでのローテーション処理によって更新されたハッシュの値を16進数で出力します。

入力が「aiueo」なら「fa06926df12aec4356890d4847d43f79101c93548a6b65e4b57bcb651294beef」が出力されて、これで出来上がりになります。

終わりに

何がどうしてこんな計算方法が生まれたんでしょうね?
そしてSHA-3は計算方法がガラッと変わったようなので、その辺りも見てみたいですね!

何はともあれ日頃使ってるハッシュがどういう計算を経て出力されているかを知ることができました。
そもそもGoには暗号・ハッシュのパッケージcryptoがあります。また、安心安全のためには自家製SHA256は使用しないことをお勧めします。
勉強のための実装で留めておいた方が良いですよ!

あとパスワードは最低でも16文字!
英子文字、英大文字、数字、記号を入れること!
使いまわさないこと!
私はパスワードを覚えるのに脳のリソースを使いたく無いので1Passwordを導入しています。

出典