1. kkent030315

    No comment

    kkent030315
Changes in body
Source | HTML | Preview
@@ -1,832 +1,833 @@
高校生の僕が**SHA256**暗号化アルゴリズムを外部ライブラリやランタイムに一切頼らず、独自で実装してみました。
当記事は、マイクロソフト様にケンカを売る記事ではございません。
#前置き
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/535270/ac274c17-ff57-c11d-2eb9-00b0dfaa1b2e.png)
この記事をご覧になっている方々は、様々な分野でお仕事をされている方がいらっしゃると思います。
そういった方々含め、プログラミングを実際にされていない方も「SHA256」という単語は一度でも目に入れた方も多くいらっしゃるのではないでしょうか。
**SHA256**とは、アメリカ国家安全保障局によって設計され、
2001年にアメリカ国立標準技術研究所によって連邦情報処理標準とされた暗号化アルゴリズムのひとつです。
SHA256の他にも、「SHA-1」や「SHA-512」といった多くの暗号化アルゴリズムがあります。
そのひとつである「SHA-1」は、2017年にGoogleによって攻撃手段が公開され、
それまでSHA-1を使用していたシステムは半強制的に、より安全な暗号化アルゴリズムへの移行を余儀なくされた。
という事件がありましたね。
> 該当記事: [Googleがハッシュ関数「SHA-1」を破ることに成功](https://gigazine.net/news/20170224-google-cwi-break-sha-1/)
そういった背景もある暗号化アルゴリズム達ですが、今回はなかでも多くの場面で使われている
「SHA-256」をプログラミング言語 C# を使って独自で実装してみたいと思います。
ビットコインを支えている**ブロックチェーン**でも、SHA-256は活躍しています。
ブロックチェーンに関しましては、下記記事をご参照頂くとわかりやすいと思います。
> 参考記事: [【Python】10分でイキリ高校生がブロックチェーンを初心者に解説してやる!!!](https://qiita.com/sa2shun/items/f4991357ece3303c7167#4blockchain%E3%81%AE%E5%AE%9F%E8%A3%85)
#SHA256の特徴
![weodnmgvbws.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/535270/d2984d08-160c-a760-5cd4-2d61ec393d9d.png)
ここまでで、様々な暗号化アルゴリズムがあることがわかりました。
SHA256の特徴は
- **一方向性**
- ▶復号化が困難である。
ここで、”困難”と言ったのは、**総当り攻撃**による突破が可能であるからです。
論理的には、2^80(2の80乗)という回数総当たりを仕掛ければどれかが当たる、ということです。
現在では、遥かに少ない2^63(2の63乗)という回数で突破できる手法が見つかっているそうです。
- **衝突困難**
- ▶SHA-1で使用された攻撃手法。衝突させることが困難である。
簡単に言うと、**異なる2つの平文から同一のハッシュ値が得られる脆弱性に強い**ということです。
詳しく知りたい方は、下記の記事を参照下さい。
> 参考記事: [巷で話題のGoogleのSHA-1衝突やってみた](http://73spica.tech/blog/sha1-collision/)
#実装してみる
ここからは、実際にプログラムを書いてC#で暗号化アルゴリズムを再現してみたいと思います。
##大まかな処理フロー
かなり大雑把ではありますが、大まかな処理フローは以下の通りです。
![sha256flow.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/535270/58e6a6dc-829e-d4ed-1b75-f3c83c300b64.png)
###1.平文(16進数)を2進数に変換
`a`であれば16進数`61` 2進数 `1100001` に変換されます。
###2.パディング処理
パディング(Padding)と呼ばれる空埋め処理を行います。
1ブロックのサイズは`64byte`ですので、それ以下であれば空の部分を埋める必要があります。
但し、ただ埋めるだけではダメです。
例として以下のようなブロックがあったとします。
`14byte`分足りません。これを埋めます。
```
//50byte
0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000
0000 0000 0000 0000 0000 0000 0000 0000 0000 0000
```
これを、ただ埋めると何が起こるかは安易に予想できます。
`X`の部分を0で埋めたとすると、異なるブロックであっても全く同じメッセージブロックが生まれてしまいます。
```
//46byte
1111 0101 0111 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000
0000 0100 0000 0110 0000 0000 0000 0000 XXXX XXXX XXXX XXXX XXXX XXXX XXXX XXXX XXXX
//50byte
1111 0101 0111 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000
0000 0100 0000 0110 0000 0000 0000 0000 0000 0000 XXXX XXXX XXXX XXXX XXXX XXXX XXXX
全く同じブロックが生まれる。
```
そこで、もとのブロックのブロック長を末端に記録します。
`B`の部分がブロック長を記録した部分です。
```
//46byte
1111 0101 0111 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000
0000 0100 0000 0110 0000 0000 0000 0000 XXXX XXXX XXXX XXXX XXXX BBBB BBBB BBBB BBBB
//50byte
1111 0101 0111 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000
0000 0100 0000 0110 0000 0000 0000 0000 0000 0000 XXXX XXXX XXXX BBBB BBBB BBBB BBBB
```
ブロック長が64byte丁度だったり、万が一記録する余裕が無かっただった場合は次のブロックで記録します。
###3.ブロックをメッセージブロックに分割
パディングが済んだ512byteのブロックを64byte長のメッセージブロックへと分割します。
###4.初期ベクトルを使用してハッシュ値を算出
初回ラウンドでは、初期ベクトル(IV)を使用します。
```c#
/// <summary>
/// 初期ハッシュ
/// </summary>
private uint[] initial_hash =
{
0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a,
0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19
};
```
###5.合計64ラウンド処理を行う
行う処理は簡単に以下の通りです。
![sha256flow2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/535270/d50dace5-54c8-4d50-44e4-7457e2be6efb.png)
####論理代数
ここからは論理代数の分野です。
正直なところ、僕もふわっとしか理解できていません。
一通り調べてはみましたが、情報そのものが正しいものであるか区別する手段がありませんでしたので、
あくまで参考として記載します。
詳しい方がいらっしゃいましたら、コメント欄にて解説をお願い致します。
> 参考記事: [論理代数を学ぼう](http://maicommon.ciao.jp/ss/dscrtMath2/alg/)
参考記事: [論理代数と論理関数 湊 真一](http://www.ieice-hbkb.org/files/01/01gun_08hen_01.pdf)
####Ch
```math
Ch(E,F,G) = (E \Lambda F) Xor (\rightharpoondown E \Lambda G)
```
```c#
private uint Ch(uint x, uint y, uint z)
{
return (x & y) ^ (~x & z);
}
```
####Maj (変数多数決関数)
```math
Maj(A,B,C) = (A \Lambda B) Xor (A \Lambda C) Xor (B \Lambda C)
```
```c#
private uint Maj(uint x, uint y, uint z)
{
return (x & y) ^ (x & z) ^ (y & z);
}
```
####RotR (Rotation Right)
```c#
private uint Rot_R(uint x, byte n)
{
return (x >> n) | (x << (32 - n));
}
```
####RotL (Rotation Left)
```c#
/// <summary>
/// 左ローテート
/// </summary>
/// <param name="x"></param>
/// <param name="n"></param>
/// <returns></returns>
private uint Rot_L(uint x, byte n)
{
return (x << n) | (x >> (32 - n));
}
```
####Σ0 (Sigma0)
```math
\sum 0 (A) = (A \ggg 2) Xor (A \ggg 13) Xor (A \ggg 22)
```
```c#
private uint Sigma0(uint x)
{
return Rot_R(x, 2) ^ Rot_R(x, 13) ^ Rot_R(x, 22);
}
```
####Σ1 (Sigma1)
```math
\sum 1 (E) = (E \ggg 6) Xor (E \ggg 11) Xor (E \ggg 25)
```
```c#
private uint Sigma1(uint x)
{
return Rot_R(x, 6) ^ Rot_R(x, 11) ^ Rot_R(x, 25);
}
```
##C#ソースコード
ここからは、実際のソースコードを紹介します。
###定数K
```c#
/// <summary>
/// Kと呼ばれる定数
/// </summary>
private readonly uint[] const_k = new uint[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
};
```
###ComputeHash() 関数 - ハッシュ値を算出
```c#
/// <summary>
/// SHA256ハッシュを生成します。
/// </summary>
/// <param name="plainText">暗号化したい文字列</param>
/// <returns>暗号化された文字列</returns>
public string ComputeHash(string plainText)
{
var p = Padding(plainText);
var block_list = Parse(p);
var s = new uint[8];
Array.Copy(initial_hash, s, initial_hash.Length);
//ブロック数分ループ
foreach (var blocks in block_list)
{
//ブロックリストの中のブロックにスコープを当てる
foreach(var block in block_list)
{
var pair = MakePair(s);
var expanded_block = ExpandBlock(block);
for (int n = 0; n < 64; ++n)
{
var CH = Ch(pair["e"], pair["f"], pair["g"]);
var MAJ = Maj(pair["a"], pair["b"], pair["c"]);
var SIG0 = Sigma0(pair["a"]);
var SIG1 = Sigma1(pair["e"]);
var WJ_KJ = (const_k[n] + expanded_block[n]);
var T1_TEMP = (pair["h"] + WJ_KJ + CH);
var T1 = (T1_TEMP + SIG1);
var T2 = (SIG0 + MAJ);
pair["h"] = pair["g"];
pair["g"] = pair["f"];
pair["f"] = pair["e"];
pair["e"] = (pair["d"] + T1);
pair["d"] = pair["c"];
pair["c"] = pair["b"];
pair["b"] = pair["a"];
pair["a"] = (T1 + T2);
}
s[0] = (pair["a"] + s[0]);
s[1] = (pair["b"] + s[1]);
s[2] = (pair["c"] + s[2]);
s[3] = (pair["d"] + s[3]);
s[4] = (pair["e"] + s[4]);
s[5] = (pair["f"] + s[5]);
s[6] = (pair["g"] + s[6]);
s[7] = (pair["h"] + s[7]);
}
}
return MakeHash(s);
}
```
###ToUInt32Array() 関数 - 2進数に変換
```c#
/// <summary>
/// Stringを2進数に変換します。
/// </summary>
/// <param name="plain_text">暗号化する文字列</param>
/// <returns>2進数配列</returns>
private uint[] ToUInt32Array(string plain_text)
{
//文字列を16進数に変換. 実体はbyte配列
var a = Encoding.ASCII.GetBytes(plain_text);
//パディング結果を格納する配列
uint[] result = { };
foreach (var n in a)
{
//16進数を2進数に変換
var j = int.Parse(Convert.ToString(n, 2));
//2進数を8桁に揃える 0を先頭に追加
result = Extend<uint>(result, 0);
//8桁に揃えたものを配列化
for (int nb = 0; nb < 7; nb++)
{
result = Extend<uint>(result, (uint)StrMid(j, nb));
}
}
return result;
}
```
###CalculateK() 関数 - Kを算出
```c#
/// <summary>
/// 与えられた配列から定数Kを算出します。
/// </summary>
/// <param name="plain_bits">算出された数</param>
/// <returns>定数K</returns>
private uint CalculateK(uint[] plain_bits)
{
uint k = 0;
var length = plain_bits.Length;
while ((length + 1 + k) % 512 != 448)
{
k += 1;
}
return k;
}
```
###Padding() 関数 - パディング
```c#
/// <summary>
/// パディングと呼ばれる、空埋め処理を行います。
/// </summary>
/// <param name="plain_text">パディングする2進数配列</param>
/// <returns>パディングされた2進数配列</returns>
private uint[] Padding(string plain_text)
{
var plain_bits = ToUInt32Array(plain_text);
var length = plain_bits.Length;
var k = CalculateK(plain_bits);
//処理する値を保持するバッファ
uint[] buf = { };
buf = Extend<uint>(plain_bits, 1);
for(int r = 0; r < k; r++)
{
buf = Extend<uint>(buf, 0);
}
var bytStr = Convert.ToString(length, 2);
//64桁右寄せゼロ埋め
//0000000000000000000000000000000000000000000000000000000000011000
bytStr = bytStr.ToString().PadLeft(64, '0');
//↑で得た64桁の数列を配列に変換
//(64)[ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0 ]
uint[] bytStr_toArray = { };
for(int x = 0; x <= 63; x++)
{
var num_str = bytStr.Substring(x, 1);
var num = uint.Parse(num_str);
bytStr_toArray = Extend<uint>(bytStr_toArray, num);
}
//位取り バッファにAppend
foreach(var b in bytStr_toArray)
{
buf = Extend<uint>(buf, b);
}
//ブロックサイズはパディング後512である
//Assert(buf.Length != 512)
return buf;
}
```
###Parse() 関数 - ブロックの分割
```c#
/// <summary>
/// パディングされた配列を512バイトのブロック長に分割します。
/// </summary>
/// <param name="plain_bits">分割する2進数配列</param>
/// <returns>分割された2進数ブロック一覧を格納したジャグ配列</returns>
private uint[][] Parse(uint[] plain_bits)
{
const int BLOCK_SIZE = 512;
var length = plain_bits.Length;
var num_blocks = length / BLOCK_SIZE;
//ブロックを保持するジャグ配列
var blocks = new uint[num_blocks][];
for(int n = 0; n < num_blocks; n++)
{
var block = new uint[BLOCK_SIZE];
//Array.Copy(plain_bits, block, BLOCK_SIZE); ←[追記]で発生した問題の原因
Array.Copy(plain_bits, n * BLOCK_SIZE, block, 0, BLOCK_SIZE);
//ブロックリストに追加
blocks[n] = block;
}
return blocks;
}
```
###ExpandBlock() 関数 - ブロックのプレ処理
```c#
/// <summary>
/// ブロックを処理します。
/// </summary>
/// <param name="block">64バイトの2進数ブロック配列</param>
/// <returns>処理された2進数ブロック配列</returns>
private uint[] ExpandBlock(uint[] block)
{
uint[] result = { };
for(int x = 0; x < 16; x++)
{
//例
//バイナリ -> ヘックスデミカル -> uint OR バイナリ -> decimal
//↓チャンクバイナリ 01100001011000100110001110000000 は ヘックスデミカル 0x61626380 である.
//CHUNK: [ 32 ] [0, 1, 1, 0, 0, 0, 0, 1, 0, 1, 1, 0, 0, 0, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0]
//= 0x61626380 したがって uint は 1633837952
//コピー先のチャンク配列
uint[] chunk_array = new uint[32];
//ブロックから、 x*32 から 32先までをコピー
Array.Copy(block, x*32, chunk_array, 0, 32);
//バイナリに変換
var chunk_binary_str = ToBinary(chunk_array);
var chunk_demical = Convert.ToUInt32(chunk_binary_str, 2);
result = Extend<uint>(result, chunk_demical);
for(int y = 16; y < 64; y++)
{
var T1 = Sub0(result[y - 15]) + result[y - 16];
var T2 = T1 + result[y - 7];
var T3 = T2 + Sub1(result[y - 2]);
}
result = Extend<uint>(result, T3);
}
return result;
}
```
###MakePair() 関数 - ペアを生成
```c#
/// <summary>
/// 計算をより楽に、分かりやすくするためにStringとのペアを生成します。
/// </summary>
/// <param name="hash">ペアと対になる2進数配列</param>
/// <returns>ペアを格納したディクショナリ</returns>
private Dictionary<string, uint> MakePair(uint[] hash)
{
var dictionary = new Dictionary<string, uint>();
dictionary.Add("a", hash[0]);
dictionary.Add("b", hash[1]);
dictionary.Add("c", hash[2]);
dictionary.Add("d", hash[3]);
dictionary.Add("e", hash[4]);
dictionary.Add("f", hash[5]);
dictionary.Add("g", hash[6]);
dictionary.Add("h", hash[7]);
return dictionary;
}
```
###MakeHash() 関数 - 16進数に変換
```c#
/// <summary>
/// 2進数を16進数の文字列に変換します。
/// </summary>
/// <param name="s">変換したい2進数配列</param>
/// <returns>変換された文字列</returns>
private string MakeHash(uint[] s)
{
string result = "";
foreach(var v in s)
{
result += Convert.ToString((int)v, 16);
}
return result;
}
```
###ToBinary() 関数 - バイナリに変換
```c#
/// <summary>
/// チャンクをバイナリに変換します。
/// </summary>
/// <param name="chunk"></param>
/// <returns>変換されたバイナリ</returns>
private string ToBinary(uint[] chunk)
{
string result = string.Empty;
foreach(var n in chunk)
{
result += n.ToString();
}
return result;
}
```
###BinaryTodecimal() 関数 - デシマルに変換
`Convert.ToString(value, 16)` を使う方法もあります。
```c#
/// <summary>
/// バイナリをデミカル Integer に変換します。
/// </summary>
/// <param name="binary_str">バイナリ</param>
/// <returns>デミカル Integer</returns>
private int BinaryToDecimal(string binary_str)
{
char[] binary_char_array = binary_str.ToCharArray();
Array.Reverse(binary_char_array);
int result = 0;
for (int i = 0; i < binary_char_array.Length; i++)
{
if (binary_char_array[i] == '1')
{
if (i == 0)
{
result += 1;
}
else
{
result += (int)Math.Pow(2, i);
}
}
}
return result;
}
```
##自作便利関数
やっていて、色々と不便に感じた部分や簡略化したい部分を関数化したものです。
###Extend() 関数 - 配列を拡張
C#では型付けが厳しい為、定義後に配列の大きさを柔軟に変更することができません。
そこで、既存の配列を拡張する関数を作ってみました。
```c#
/// <summary>
/// 配列を右辺へ拡張し、値を格納します。
/// </summary>
/// <typeparam name="T">アンマネージド型</typeparam>
/// <param name="source">ソース元である配列</param>
/// <param name="num">格納する値</param>
/// <returns>拡張された配列</returns>
private T[] Extend<T>(IList<T> source, T num)
{
var result = new T[source.Count+1];
for (int n = 0; n < source.Count; n++)
{
result[n] = source[n];
}
result[source.Count] = num;
return result;
}
```
使い方:
```c#
uint[] test = { 1 };
//before [ 1 ]
uint[] res = Extend<uint>(test, 3);
//after [ 1, 3 ]
```
###PrintArray() 関数 - 配列をいい感じに出力
C#では、`Console.WriteLine()`で大体のことはできますが、
配列をそのまま出力しようとすると`System.Int32`と、型がそのまま出力されてしまいます。
そのままではデバッグするのもストレスなので、いい感じに出力してくれる関数を作ってみました。
```c#
/// <summary>
/// 配列をいい感じに出力します。
/// </summary>
/// <typeparam name="T">アンマネージド型</typeparam>
/// <param name="source">ソース元である配列</param>
private void PrintArray<T>(IList<T> source)
{
Console.WriteLine(string.Format("({0})", source.Count) + "[ " + string.Join(", ", source) + " ]");
}
```
```c#
//こんな配列が
int[] hoge = { 0, 1, 2, 3, 4 };
//こうすると
Console.WriteLine(hoge);
//こうなるから
//Output: System.Int32
//自作関数でラップすると
PrintArray(hoge);
//いい感じに出力される
//Output: [ 0, 1, 2, 3, 4 ]
```
###StrMid() 関数 - 数値の中から数値を取り出す
例えば、こんな数字から
```
123456
```
`3`のインデックス部分を取り出したいとします。
パッと思いつくのは、`string`にして`SubString()`する方法でした。
```c#
//int文字列から特定の位置にある数字を取り出す
//12345
// ↑indexN
//return 2
/// <summary>
/// Integer から指定されたインデックスにある数値を取り出します。
/// 12345678 の インデックス 5 の場合 返り値は 5 です。
/// </summary>
/// <param name="source">ソース元である数値</param>
/// <param name="indexN">インデックス</param>
/// <returns>取り出された数値</returns>
private int StrMid(int source, int indexN)
{
var s = source.ToString().Substring(indexN, 1);
return int.Parse(s);
}
```
##実行してみた!
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/535270/7bf71298-6c7b-b488-be1e-b75a6c63e212.png)
環境は .NET Framework 4.7.2 です。
WinFormで適当にそれっぽいものを作って実際に実行してみます。
使用例:
```c#
using (SHA256NotManaged sha256 = SHA256NotManaged.Create())
{
var plainText = textBox1.Text;
var encryptedText = sha256.ComputeHash(plainText);
textBox2.Text = encryptedText;
}
```
実行結果:
![afqbu-uhkdc.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/535270/249da2e8-8711-3fcf-1cbb-492fa3b451fb.gif)
出てきたハッシュをSHA256データーベースで検索してみます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/535270/1960c983-71b2-b8f1-8e5a-32c19b3578b7.png)
正しいようです。
###処理時間
今回は、アルゴリズムに集点をおいて、可読性を優先してコードを書いたのもあり
速度については一切考慮しておりませんが、一応ランタイムのものと比較してみます。
比較にあたって、下記記事を参考にソースコードを拝借いたしました。
ありがとうございました。
> 参考記事: [C#でStopwatchを使った時間計測を1行でできるようにする](https://takachan.hatenablog.com/entry/2019/03/05/230952)
```c#
using (SHA256NotManaged sha256 = SHA256NotManaged.Create())
{
TimeSpan elapsed = StopwatchEx.Context(() => { sha256.ComputeHash("abc"); }, 1000);
Console.WriteLine($"自作: {elapsed.TotalMilliseconds}");
}
using (System.Security.Cryptography.SHA256 sha256_ = System.Security.Cryptography.SHA256.Create())
{
TimeSpan elapsed = StopwatchEx.Context(() => { sha256_.ComputeHash(abc); }, 1000);
Console.WriteLine($"本家: {elapsed.TotalMilliseconds}");
}
```
今回は5回ほど計測してみました。
それぞれ1000回の試行にかかった平均時間(ミリ秒)です。
ランタイムのものと比較すると、約800倍近く時間がかかっていますね。
手も足も出ませんでした。
~~正直ランタイムといい勝負するのではないかと期待していた~~ 打ち砕かれました。
```
自作: 857.9315
本家: 1.1778
自作: 920.8264
本家: 1.8223
自作: 804.2666
本家: 1.009
自作: 803.6653
本家: 1.0139
自作: 821.0931
本家: 1.0187
```
#最後に
一番苦戦したのは理論代数の分野の理解です。
一向に理解できないせいで計算があわず、2時間格闘しました。。。
結局ふわっとしか理解できていません。
詳しい方がいらっしゃれば、些細な情報でもコメント欄にて解説頂けるとありがたいです。
フルソースコードは僕のGithubリポジトリにあります。
コピペで動きます。
[![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/535270/472a7984-d9cb-5a9b-84a7-22e8efb1bfa5.png)](https://github.com/kkent030315/SHA256-Algorithm-CSharp)
当記事内で間違いや誤字脱字等ありましたら、お手数ですがコメント欄にてご指摘いただけますと幸いです。
**最後まで読んでいただき、ありがとうございました。**
#追記 (不具合修正)
- 一定の文字数を超えると、生成されるハッシュの値が異なる現象
- その他一部で意図しない動作
を確認しました。
本題です。
なぜこのプログラムで**一定以上の文字数を超えると算出されるハッシュ値が異なる**か?
-結論から申し上げますと、ブロック長の記録の例外処理を完全に忘れていました。
+結論から申し上げますと、~~ブロック長の記録の例外処理を完全に忘れていました。
「何らかの理由で記録するスペースがなかった場合、次のブロックで記録する」
-という処理です。
+という処理です。~~
+ただのヒューマンエラーでした。
最初、この謎現象の原因を掴むべくデバッグしていましたら
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/535270/500e0fab-eda8-a8b3-db99-1ac5c814eb58.png)
右が正しいものです。
ブロック末端&チャンクが異なっています。
デバッグを続けていくと、この現象は
**一定の文字数以上(ブロック数2以上)で発生する**ことがわかりました。
したがって、ブロック分割部分で何かしらの問題が発生していると予想できます。
そこで、実際に中身を見てました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/535270/bed70202-8f3e-cf25-c9bf-dbc852f6ad2d.png)
予想的中。
**1ブロック目と2ブロック目のブロックがそれぞれ同一の値となっている**ことが確認できます。
問題の分割関数を覗いてみます。
![5799d40641a72eedc85e110d8d2b3e14.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/535270/6d97ad64-9cae-1980-dfc8-4563c7f0d622.png)
問題箇所を特定できました。
人為的ミスは、一歩引いてソースを見てみないと中々発見できませんね。。
<font color="red">**2進数のプレーンテキスト配列の0から512までを毎回コピーしていた**</font>ことが原因でした。
ブロックが複数ある場合にのみ発生する不具合だったので、発見に時間がかかりました。
修正後のソースコードは以下の通りです。
```c#
//ブロックが複数ある場合も想定されるので、
//n*512から512先までをコピーしなければいけない。
Array.Copy(plain_bits, n * BLOCK_SIZE, block, 0, BLOCK_SIZE);
```
---
以上、折角の機会なので不具合に対する適切な対処法と僕なりのデバッグ手法を紹介してみました。