概要
暗号シリーズ
ハッシュ関数Blake2をRustで実装&アルゴリズムの解説していく
元々はArgon2の記事を作成する予定だったけど、内部で思いっきりこれが使われていたので、Blake2を実装するついでに記事としてまとめることにした。
以下の人向け
- ハッシュ関数に興味がある
- Blake2ってなに?
- 名前を知っててもアルゴリズムを知らない
ソースコードは以下
Blake2の概要
基本的にはハッシュ関数で、sha3の選考に提出された物。
かの有名なダニエルバーンスタイン作ハッシュ関数。
彼が作成したBlakeの後継がBlake2。
追記:嘘を書いていたので修正。ChaCha20のアルゴリズムを基にして作成されているだけで、作成者ではなかった。
ダニエル・ジュリアス・バーンスタイン(通称djb)
..注釈にしようかとおもったけど、知らない人向けにしっかり書いておく。
ハッシュ関数や疑似乱数を調べていれば彼を知らない人はいないほどすごい人
知っていなくても絶対使っている。
彼の技術によって今のIT世界は成り立っているレベル。
ざっくり一覧
- Salsa20 疑似乱数生成でよく使われていた、様々なOS(Linux,MacOS,Windows..etc)で実装されている
- ChaCha20 Salsaの後継、同上、Blakeの元になったアルゴリズム
- Poly1305 メッセージ認証符号、ChaCha20とセットで使用されることが多い
- edDSA 楕円曲線の署名アルゴリズム、
ざっくり言えばこんなもん
マジですごい人なので、ここで知った人はせっかくなので覚えておいてほしい。
特に暗号系の分野を勉強中の人は、一覧の技術を見ると非常にためになるのでおすすめ。
2024年現在は、Blake3が出ておりそちらの解説は見当たらないので、勉強するならそっちのほうが良いと思う。
...というか絶対そっちのほうがいい。
なんならそっちのほうが興味ある
Blake2はArgon2というパスワードハッシュ関数に使用されている。
Argon2に関しては別で解説を行う。
実装はRFC7693を基にして実装していく。
他のライブラリでの実装では違いが発生しているかもしれないが、基礎理論は変わらないと思うので気になった人は調べて記事とかにしてほしい。
(自分でやるのは面倒くさい)
調べてみたら、細かなところで違いはあってもほとんど論文と一緒だったので安心してほしい。
アルゴリズム
アルゴリズムの解説を行うためにRFCを読んでいたら
...なんか論文に詳細にソースコード乗ってんだけど...もうこれ実装いらないね。
C言語(C++でも多分可)が読める人は論文のプログラム見たほうが速いので推奨する。
論文見れば良い、だと面白味もなければ意味もないのでできる限り解説していくし、実装する
Blake2は二種類あって、通常版のBlake2bと縮小版のBlake2s。
Blake2bは64bit用でBlake2sは8~32bit。
今回実装するのはBlake2b(sは気分が乗ったら)
理由は、単純にArgon2で使用されているから。
論文と同様ボトムアップ形式で実装&解説していく
定数やパラメータ
関数内で使用される定数や入力要求する値の説明
論文から抜粋
| BLAKE2b | BLAKE2s |
--------------+------------------+------------------+
Bits in word | w = 64 | w = 32 |
Rounds in F | r = 12 | r = 10 |
Block bytes | bb = 128 | bb = 64 |
Hash bytes | 1 <= nn <= 64 | 1 <= nn <= 32 |
Key bytes | 0 <= kk <= 64 | 0 <= kk <= 32 |
Input bytes | 0 <= ll < 2**128 | 0 <= ll < 2**64 |
--------------+------------------+------------------+
G Rotation | (R1, R2, R3, R4) | (R1, R2, R3, R4) |
constants = | (32, 24, 16, 63) | (16, 12, 8, 7) |
--------------+------------------+------------------+
上から
- 1ワードのビット数
- ラウンド数
- 1ブロックのバイト数
- 返すハッシュのバイト数(オプション)
- 秘密鍵のバイト数(オプション)
- 入力バイト数
- 圧縮関数Gの定数値
になっている。
その都度説明を入れるつもりなので必要ないと思うけど、迷った場合はこちら。
これに従ってデータ型を作成すれば問題はない。
メッセージスケジュール順列シグマ
AESのSbox的な感じ?
Sboxとは非線形変換を行うためのテーブルデータ。
小規模な一方向関数みたいな認識で良い
テーブルの内容としては、πの分数から取っているとか書いてあるけど意味が分からない。。
わかる人は教えてほしい。
SIGMA自体はBlake2b、Blake2s両方同様のものを使用する。
それぞれラウンド毎にその行のSIGMAを使用するため、Blake2bは12、Blake2sは10ラウンド分行が必要。
なので、Blake2bでは10,11は0,1として使ってほしいとのこと。
要はmod10で考えればいいってこと
例)SIGMA[(10 + 0)%10] = SIGMA[0], SIGMA[(10 + 1)%10] = SIGMA[1]
論文から抜粋
Round | 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
----------+-------------------------------------------------+
SIGMA[0] | 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
SIGMA[1] | 14 10 4 8 9 15 13 6 1 12 0 2 11 7 5 3 |
SIGMA[2] | 11 8 12 0 5 2 15 13 10 14 3 6 7 1 9 4 |
SIGMA[3] | 7 9 3 1 13 12 11 14 2 6 5 10 4 0 15 8 |
SIGMA[4] | 9 0 5 7 2 4 10 15 14 1 11 12 6 8 3 13 |
SIGMA[5] | 2 12 6 10 0 11 8 3 4 13 7 5 15 14 1 9 |
SIGMA[6] | 12 5 1 15 14 13 4 10 0 7 6 3 9 2 8 11 |
SIGMA[7] | 13 11 7 14 12 1 3 9 5 0 15 4 8 6 2 10 |
SIGMA[8] | 6 15 14 9 11 3 0 8 12 2 13 7 1 4 10 5 |
SIGMA[9] | 10 2 8 4 7 6 1 5 15 11 9 14 3 12 13 0 |
----------+-------------------------------------------------+
...テーブルを眺めてみるとGF(16)のガロア体を使っている?
規約多項式決めて、πの分数を調べて、上から16バイトとって数字に変換?
なんとなく、そういう方向な気もするけど、調べる気力はないので調査した人は教えてほしい
初期ベクトルIV
論文内では以下のように書かれている
IV[i] = floor(2**w * frac(sqrt(prime(i+1)))),
where prime(i) is the i:th prime number ( 2, 3, 5, 7, 11, 13, 17, 19 )
and sqrt(x) is the square root of x.
素数の平方根から少数をとり2^w(Blakebは64、Blake2sは32)をかける、簡単に言えば少数の上からwbitを取ってきたもの。
つまりこれ
IV0 = 0x6a09e667f3bcc908 // Frac(sqrt(2))
IV1 = 0xbb67ae8584caa73b // Frac(sqrt(3))
IV2 = 0x3c6ef372fe94f82b // Frac(sqrt(5))
IV3 = 0xa54ff53a5f1d36f1 // Frac(sqrt(7))
IV4 = 0x510e527fade682d1 // Frac(sqrt(11))
IV5 = 0x9b05688c2b3e6c1f // Frac(sqrt(13))
IV6 = 0x1f83d9abfb41bd6b // Frac(sqrt(17))
IV7 = 0x5be0cd19137e2179 // Frac(sqrt(19))
見覚えあると思ったら
sha-512と同じ初期化ベクトルを使用しているらしい、Blake2sはsha-256の初期ベクトル
SIGMAもそうだけど、無理数の少数を初期値とかに使うの多い気がする
規則性の無さが大事だからかな
混合関数G
実質的にこの混合関数がBlake2の本体と言える。
chachaの1/4ラウンドと同じかんじ?
インデックスを引数に入れるように論文に書かれているが、実装では直接値を入れる形にしている。
回転するパラメータR1,R2,R3,R4は定数パラメータに記載されている。
論文内の疑似コード
FUNCTION G( v[0..15], a, b, c, d, x, y )
|
| v[a] := (v[a] + v[b] + x) mod 2**w
| v[d] := (v[d] ^ v[a]) >>> R1
| v[c] := (v[c] + v[d]) mod 2**w
| v[b] := (v[b] ^ v[c]) >>> R2
| v[a] := (v[a] + v[b] + y) mod 2**w
| v[d] := (v[d] ^ v[a]) >>> R3
| v[c] := (v[c] + v[d]) mod 2**w
| v[b] := (v[b] ^ v[c]) >>> R4
|
| RETURN v[0..15]
|
END FUNCTION.
実装コード
fn g(a: u64, b: u64, c: u64, d: u64, x: u64, y: u64) -> (u64, u64, u64, u64) {
//let rotate = |x : u64,n : usize|{(x >> n) | (x << (64 - n))};
let mut a = a;
let mut b = b;
let mut c = c;
let mut d = d;
a = a.wrapping_add(b).wrapping_add(x);
d = (d ^ a).rotate_right(32);
c = c.wrapping_add(d);
b = (b ^ c).rotate_right(24);
a = a.wrapping_add(b).wrapping_add(y);
d = (d ^ a).rotate_right(16);
c = c.wrapping_add(d);
b = (b ^ c).rotate_right(63);
(a, b, c, d)
}
内部の処理理由を調べてみたけど、ChaCha20と似たようなコンセプトであること以外わからなかった。
簡単に言えば、ガシャガシャ混ぜる工程
...これは愚痴になるけど、テストベクター用意するなら関数Gのテストベクターもほしかった。
一々確認するのマジで面倒くさい!
いや分かるけどね!
単純な処理関数なんだからいらないだろうってなる気持ちも!
ただ、入力と出力から関数が正しいかを判断できるのは、テストの効率が段違いなのよ..
お願いだから残しておいてほしい
気を取り直して次..
圧縮関数F
状態h、入力情報m、オフセットt、ラストブロック判定f から状態h*を返す関数
$$ h_{next} = F(一つ前のハッシュ値h,入力文字列mのw文字,入力文字数t,ラスト判定f)$$
パラメータに従った回数関数Fを繰り返す。
FUNCTION F( h[0..7], m[0..15], t, f )
|
| // Initialize local work vector v[0..15]
| v[0..7] := h[0..7] // First half from state.
| v[8..15] := IV[0..7] // Second half from IV.
|
| v[12] := v[12] ^ (t mod 2**w) // Low word of the offset.
| v[13] := v[13] ^ (t >> w) // High word.
|
| IF f = TRUE THEN // last block flag?
| | v[14] := v[14] ^ 0xFF..FF // Invert all bits.
| END IF.
|
| // Cryptographic mixing
| FOR i = 0 TO r - 1 DO // Ten or twelve rounds.
| |
| | // Message word selection permutation for this round.
| | s[0..15] := SIGMA[i mod 10][0..15]
| |
| | v := G( v, 0, 4, 8, 12, m[s[ 0]], m[s[ 1]] )
| | v := G( v, 1, 5, 9, 13, m[s[ 2]], m[s[ 3]] )
| | v := G( v, 2, 6, 10, 14, m[s[ 4]], m[s[ 5]] )
| | v := G( v, 3, 7, 11, 15, m[s[ 6]], m[s[ 7]] )
| |
| | v := G( v, 0, 5, 10, 15, m[s[ 8]], m[s[ 9]] )
| | v := G( v, 1, 6, 11, 12, m[s[10]], m[s[11]] )
| | v := G( v, 2, 7, 8, 13, m[s[12]], m[s[13]] )
| | v := G( v, 3, 4, 9, 14, m[s[14]], m[s[15]] )
| |
| END FOR
|
| FOR i = 0 TO 7 DO // XOR the two halves.
| | h[i] := h[i] ^ v[i] ^ v[i + 8]
| END FOR.
|
| RETURN h[0..7] // New state.
|
END FUNCTION.
ラウンドの処理が見覚えしかない、どうみてもChaCha20の攪拌処理。
改めて見ると、やっぱりシグマはSboxと言っていいと思う。
ハッシュとかだと違う言い方があるのかな?
ざっくり見ると、
出力8wordに対して16wordのローカル配列を用意、
初期ベクターや初期状態、入力値とかを入れてガチャガチャに混ぜる、
最後に16wordを折りたたんで初期状態と合わせたら完成。
fn compress(h: Vec<u64>, chunk: Vec<u64>, t: u128, last: bool) -> Vec<u64> {
let mut v: Vec<u64> = h.clone();
v.extend(Self::IV);
v[12] ^= u64::try_from(t).ok().unwrap();
v[13] ^= u64::try_from(t >> 64).ok().unwrap();
if last {
v[14] ^= u64::MAX;
}
let m = chunk;
/*
| | v := G( v, 0, 4, 8, 12, m[s[ 0]], m[s[ 1]] )
| | v := G( v, 1, 5, 9, 13, m[s[ 2]], m[s[ 3]] )
| | v := G( v, 2, 6, 10, 14, m[s[ 4]], m[s[ 5]] )
| | v := G( v, 3, 7, 11, 15, m[s[ 6]], m[s[ 7]] )
| |
| | v := G( v, 0, 5, 10, 15, m[s[ 8]], m[s[ 9]] )
| | v := G( v, 1, 6, 11, 12, m[s[10]], m[s[11]] )
| | v := G( v, 2, 7, 8, 13, m[s[12]], m[s[13]] )
| | v := G( v, 3, 4, 9, 14, m[s[14]], m[s[15]] )
*/
for i in 0..12 {
let imod = (i % 10) * 16;
let s = SIGMA[imod..(imod + 16)].to_vec();
//縦に混ぜる工程は規則性があるのでまとめる
for j in 0..4 {
(v[j], v[j + 4], v[j + 8], v[j + 12]) = Blake2::g(
v[j],
v[j + 4],
v[j + 8],
v[j + 12],
m[s[j * 2]],
m[s[j * 2 + 1]],
);
}
// 斜めに混ぜる工程 実装時には規則を見てなくてベタ書きしてる
(v[0], v[5], v[10], v[15]) = Blake2::g(v[0], v[5], v[10], v[15], m[s[8]], m[s[9]]);
(v[1], v[6], v[11], v[12]) = Blake2::g(v[1], v[6], v[11], v[12], m[s[10]], m[s[11]]);
(v[2], v[7], v[8], v[13]) = Blake2::g(v[2], v[7], v[8], v[13], m[s[12]], m[s[13]]);
(v[3], v[4], v[9], v[14]) = Blake2::g(v[3], v[4], v[9], v[14], m[s[14]], m[s[15]]);
}
(0..8).map(|i| h[i] ^ v[i] ^ v[i + 8]).collect()
}
Blake2の計算処理(+秘密鍵の処理)
Blake2の大体の疑似コード
オプションパラメータの秘密鍵がある場合、少し処理が変わって追加処理が入っている。
基本的な処理としては、
- 1ブロック(=16ワード)区切りで入力dを作成
- 最初のブロックを圧縮関数Fに入力
- 結果を再度入力し、最後のブロックになるまで繰り返す
- 最後のブロックはちょっと変えた圧縮関数Fに入力
- ハッシュのnnバイトを返す
秘密鍵がある場合は、初期状態の変更とブロックが一つ増えるだけなのでそんなに違いはない。
FUNCTION BLAKE2( d[0..dd-1], ll, kk, nn )
|
| h[0..7] := IV[0..7] // Initialization Vector.
|
| // Parameter block p[0]
| h[0] := h[0] ^ 0x01010000 ^ (kk << 8) ^ nn
|
| // Process padded key and data blocks
| IF dd > 1 THEN
| | FOR i = 0 TO dd - 2 DO
| | | h := F( h, d[i], (i + 1) * bb, FALSE )
| | END FOR.
| END IF.
|
| // Final block.
| IF kk = 0 THEN
| | h := F( h, d[dd - 1], ll, TRUE )
| ELSE
| | h := F( h, d[dd - 1], ll + bb, TRUE )
| END IF.
|
| RETURN first "nn" bytes from little-endian word array h[].
|
END FUNCTION.
っていうか...秘密鍵ってなに?そんなのあったっけ?
と思ってみてみると確かにパラメータにあった
| BLAKE2b | BLAKE2s |
--------------+------------------+------------------+
Hash bytes | 1 <= nn <= 64 | 1 <= nn <= 32 |
Key bytes | 0 <= kk <= 64 | 0 <= kk <= 32 |
--------------+------------------+------------------+
ここでしか使ってない気がする。
使用するしないで、そこまでセキュリティが上がるんだろうか?
わからない。一旦おいておく
よくよく見たら、メッセージに追加鍵を1ブロック分追加して計算しているからSaltだこれ。
Saltを標準で持っているハッシュ関数って感じかな?
Saltとは適当な乱数で作成したバイト列。
hash関数にSaltを付けた入力mを入れることでmを推測できなくする
$$Hash(m | Salt) \neq Hash(m) $$
鍵があるときは、鍵の大きさで初期状態も変更されるのは面白い処理だと思う。
初期状態の変更分より解析が難しくなっている。
後の処理は十分混ざるように作られているから、この程度でもハッシュ結果は全然変わってしまう。
pub fn hash(&self, m: Vec<u8>, nn: u8, key: Key) -> Vec<u8> {
let key = key;
let kk = key.h.len() as u64;
let nn: u64 = nn.into();
let padding = |x: Vec<u8>| {
let mut ret = x;
let diff = 128 - ret.len() % 128;
for i in 0..diff {
ret.push(0);
}
ret
};
let le_to_u64 = |x: &[u8]| {
let mut ret = 0u64;
for x in x.iter().enumerate() {
ret ^= (*x.1 as u64) << (8u64 * x.0 as u64);
}
ret
};
//message block
let ll = m.len() % 128;
let m = padding(m);
let m = m.chunks(8).map(le_to_u64).collect::<Vec<u64>>();
//key block (option)
let key_block = if kk > 0 {
padding(key.h)
.chunks(8)
.map(le_to_u64)
.collect::<Vec<u64>>()
} else {
Vec::new()
};
//concat
let m = [key_block, m].concat();
let mut h = Self::IV.to_vec();
// Parameter block p[0]
// h[0] = h[0] ^ 0x0101kknn
// h[0] := h[0] ^ 0x01010000 ^ (kk << 8) ^ nn
h[0] = h[0] ^ 0x01010000u64 ^ kk.wrapping_shl(8) ^ nn;
let last_num = m.len() / 16 - 1;
let hdash = m.chunks(16).enumerate().fold(h, |hash, chunk| {
let last = chunk.0 == last_num;
let ret = Self::compress(
hash,
chunk.1.to_vec(),
if !last {
((chunk.0 + 1) as u128) * 128u128
} else {
//todo! change
((chunk.0) as u128) * 128u128 + ll as u128
},
last,
);
ret
});
//u64 to le bytes
hdash.into_iter().flat_map(|x| x.to_le_bytes()).collect()
}
...物凄くやっつけ仕事な感じ。
秘密鍵使えないし、Blake2sのことも考えてないし。
(追記:秘密鍵使えるようにしておいた)
元の実装あるし、わざわざ書く意味ないのかもしれないけど
とりあえず勉強のため全部Rustで書いてみた。
テスト
RFCにのっているテストベクターを実行
内部処理にかんしては、実際のテストベクターはないので自分で確認する必要がある。
探しても見つからなかったので、実装する人たちはこのテストを参考にしてほしい。
blake2bのテストに関しては、RFCとwikipediaに乗っているテストベクターを使用している。
hash作成に使用した値は、コメントに記載。
#[cfg(test)]
mod test {
use core::hash;
use super::*;
#[test]
fn g_test() {
/* (a,b,c,d) = g(a,b,c,d,x,y) = 3298534884874, 870327276700175364, 435160339815202818, 435160314045399040
input (a,b,c,d,x,y) = (0,1,2,3,4,5)
g function test
*/
let answer = (
3298534884874,
870327276700175364,
435160339815202818,
435160314045399040,
);
let a = 0u64;
let b = 1u64;
let c = 2u64;
let d = 3u64;
let x = 4u64;
let y = 5u64;
let ret = Blake2::g(a, b, c, d, x, y);
//println!("{:?}", ret);
assert_eq!(answer, ret);
}
#[test]
fn f_test() {
/*
f() = 6a09e667f2bdc948, bb67ae8584caa73b, 3c6ef372fe94f82b, a54ff53a5f1d36f1, 510e527fade682d1, 9b05688c2b3e6c1f, 1f83d9abfb41bd6b, 5be0cd19137e2179
h = [0;8]
chunk = [0;16]
t = 0
last = false
*/
let answer: Vec<u64> = vec![
0x4d3f506019115ac7,
0x2fa2733d57dc0ab8,
0xd1e1c1129f845613,
0x65061dc9c8e902ac,
0x8a5c682f464ae8ce,
0x3d9eb972a409d768,
0x61d9c25d696ae005,
0xee2e6936bda0ebc9,
];
let h: Vec<u64> = vec![0; 8];
let chunk: Vec<u64> = vec![0; 16];
let t: u128 = 0u128;
let last = false;
let v = Blake2::compress(h, chunk, t, last);
//println!("test f v is {:x?}", v);
assert_eq!(answer, v);
}
#[test]
fn blake2b_test() {
/* hash = BA 80 A5 3F 98 1C 4D 0D 6A 27 97 B6 9F 12 F6 E9
4C 21 2F 14 68 5A C4 B7 4B 12 BB 6F DB FF A2 D1
7D 87 C5 39 2A AB 79 2D C2 52 D5 DE 45 33 CC 95
18 D3 8A A8 DB F1 92 5A B9 23 86 ED D4 00 99 23
text = "abc"
blake2b
*/
let hash_ = "BA 80 A5 3F 98 1C 4D 0D 6A 27 97 B6 9F 12 F6 E9
4C 21 2F 14 68 5A C4 B7 4B 12 BB 6F DB FF A2 D1
7D 87 C5 39 2A AB 79 2D C2 52 D5 DE 45 33 CC 95
18 D3 8A A8 DB F1 92 5A B9 23 86 ED D4 00 99 23"
.split_whitespace()
.flat_map(|x| hex::decode(x).expect("test blake2b answer error"))
.collect::<Vec<u8>>();
let blake: Blake2 = Blake2::new();
let key: Key = Key { h: Vec::new() };
let mut m: Vec<u8> = vec![0; 3];
m[0] = 0x61;
m[1] = 0x62;
m[2] = 0x63;
let nn = 64;
let ret = blake.hash(m, nn, key);
//println!("hash is {:02x?}", ret);
assert_eq!(hash_, ret);
}
#[test]
fn blake2b_test2() {
/*
hash = A8ADD4BDDDFD93E4877D2746E62817B116364A1FA7BC148D95090BC7333B3673F82401CF7AA2E4CB1ECD90296E3F14CB5413F8ED77BE73045B13914CDCD6A918
text = "The quick brown fox jumps over the lazy dog"
blake2b
*/
let hash_ = hex::decode("A8ADD4BDDDFD93E4877D2746E62817B116364A1FA7BC148D95090BC7333B3673F82401CF7AA2E4CB1ECD90296E3F14CB5413F8ED77BE73045B13914CDCD6A918").expect("test2 blake2b answer error");
let blake = Blake2::new();
let key: Key = Key { h: Vec::new() };
let str: String = "The quick brown fox jumps over the lazy dog".to_string();
let m: Vec<u8> = str.as_bytes().to_vec();
let nn = 64;
let ret = blake.hash(m, nn, key);
//println!("hash test2 is {:02x?}", ret);
assert_eq!(ret, hash_);
}
#[test]
fn blake2b_key_test() {
/* output= 17de517e1278d00ac7a6bcf048881aa9a972e6b5cef843d3c61d3e252068a2f526c999f45cd96b172509d085b59170e388f845750c812781df582be3fc4a1972
text = "abc"
key = "abc"
*/
let hash_ = hex::decode("17de517e1278d00ac7a6bcf048881aa9a972e6b5cef843d3c61d3e252068a2f526c999f45cd96b172509d085b59170e388f845750c812781df582be3fc4a1972").expect("key test error");
let blake = Blake2::new();
let mut h = Vec::new();
h.push(0x61);
h.push(0x62);
h.push(0x63);
let key = Key { h: h };
let mut m: Vec<u8> = vec![0; 3];
m[0] = 0x61;
m[1] = 0x62;
m[2] = 0x63;
let nn = 64;
let ret = blake.hash(m, nn, key);
//println!("test key is {:x?}", ret);
assert_eq!(ret, hash_);
}
#[test]
fn blake2b_key_test2() {
/* output= 08ddabfa369e135c79ab30c8c8f954030ff0e9ff3d4f3eb23f4b2769b62283513e245ac2ee6dff13f4f9e7da87d042bc75f09fcb712a85ccbfe52527bba329a5
text = "test"
key = "test"
*/
let hash_ = hex::decode("08ddabfa369e135c79ab30c8c8f954030ff0e9ff3d4f3eb23f4b2769b62283513e245ac2ee6dff13f4f9e7da87d042bc75f09fcb712a85ccbfe52527bba329a5").expect("key test error");
let blake = Blake2::new();
let h = "test".as_bytes().to_vec();
let key = Key { h: h };
let mut m: Vec<u8> = "test".as_bytes().to_vec();
let nn = 64;
let ret = blake.hash(m, nn, key);
//println!("test key is {:x?}", ret);
assert_eq!(ret, hash_);
}
}
...かなり雑だけど、ちゃんとテスト通っているので良し!
おまけ
Blake2sのアルゴリズム自体は2bと違いはほとんどないので、変更は少なく実装できる。
方法は2つ
- パラメータの値をクラスにまとめる
- ジェネリクスで構造体を分ける
後者のほうが好みだが、オブジェクト指向的な考えでは前者のほうが良いと思う。
気が向いたら実装してみる。
まとめ
特に言うことなし。
というより言えることがない。
暗号学全般に言えるけど、肝心の推測不可能か判断する部分を理解できないからどこまでいっても、なんかすごいな~って感じになってしまう。
まぁ何はともあれ、Argon2で使用されている標準ハッシュHと圧縮関数Gは理解できたので良しとする。
Blake3に関しては別の記事としてまとめる
...いつ書くかは不明。
参照文献
感謝!