LoginSignup
2
2

More than 3 years have passed since last update.

BLAKE2s

Last updated at Posted at 2019-07-25

はじめに

BLAKEは、SHA-3の最終選考まで残ったハッシュ関数です。
これを改良したものが、BLAKE2と呼ばれるもので、64ビットのBLAKE2bと32ビットのBLAKE2sがあります。
詳しくは、BLAKE - Wikipediaを参照ください。

ここでは、RFC-7693の手順で、BLAKE2sの実装をjavascriptで行いたいと思います。
(javascriptは64ビット演算が通常の整数で出来ない為)

BLAKE2s

今回は、BLAKE2sクラスを作成します。

class BLAKE2s {
    // ここに各処理を記述します。
}

コンストラクタ

各値の初期化を設定して、インスタンスを返します。

  • パラメータ
    • ハッシュサイズ(nn
    • キー(key
  • リターン
    • BLAKE2sオブジェクト

①キーサイズ    :32バイトまでのキーを使う事が出来ます。なしでも可能です。
②ハッシュサイズ  :1~32バイトが設定可能です。
③インプットサイズ :入力されたメッセージのバイトサイズです。64ビットの為、32ビット2つとしています。
④初期ベクトル   :32バイトを8個です。SHA2と同じく素数の小数部分です。
⑤メッセージワードスケジュール
          :メッセージを16バイト毎に処理するので、一行には0~15までが必ずあります。
⑥ハッシュ値    :初期値は初期ベクトルです。
⑦パラメータブロック:ハッシュ値の0番目にパラメータブロックのXORを設定します。※1
⑧キャッシュ    :キーがある場合、キャッシュに設定します。
⑨ゼロ埋め     :64バイトになるまでゼロで埋めます。

※1:パラメータブロックは32ビットでオフセット0にはハッシュサイズ、1にはキーサイズを設定します。

byte offset:    3 2 1 0     (otherwise zero)
      p[0] = 0x0101kknn     p[1..7] = 0

javascriptで実装します。

    constructor(nn, key) {
        if (nn < 1 || 32 < nn) {
            throw new Error("blake2s: invalid hash size");
        }
        let kk = key.length;                       // ①
        if (32 < kk) {
            throw new Error("blake2s: invalid key size");
        }
        this.nn = nn;                              // ②
        this.ll = [0, 0];                          // ③
        this.IV = [
            0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a,
            0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19,
        ];                                         // ④
        this.SIGMA = [
            [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15],
            [14, 10, 4, 8, 9, 15, 13, 6, 1, 12, 0, 2, 11, 7, 5, 3],
            [11, 8, 12, 0, 5, 2, 15, 13, 10, 14, 3, 6, 7, 1, 9, 4],
            [7, 9, 3, 1, 13, 12, 11, 14, 2, 6, 5, 10, 4, 0, 15, 8],
            [9, 0, 5, 7, 2, 4, 10, 15, 14, 1, 11, 12, 6, 8, 3, 13],
            [2, 12, 6, 10, 0, 11, 8, 3, 4, 13, 7, 5, 15, 14, 1, 9],
            [12, 5, 1, 15, 14, 13, 4, 10, 0, 7, 6, 3, 9, 2, 8, 11],
            [13, 11, 7, 14, 12, 1, 3, 9, 5, 0, 15, 4, 8, 6, 2, 10],
            [6, 15, 14, 9, 11, 3, 0, 8, 12, 2, 13, 7, 1, 4, 10, 5],
            [10, 2, 8, 4, 7, 6, 1, 5, 15, 11, 9, 14, 3, 12, 13, 0],
        ];                                         // ⑤
        this.h = this.IV.concat();                 // ⑥
        // Parameter block p[0]
        this.h[0] ^= 0x01010000 ^ (kk << 8) ^ nn;  // ⑦
        this.cash = [];
        if (kk > 0) {
            this.cash = key.concat();              // ⑧
            while (this.cash.length < 64) {        // ⑨
                this.cash.push(0);
            }
        }
    }

ミキシング関数

  • パラメータ
    • 作業ベクトル(v:16バイト)
    • ベクトルワードインデックス(abcd:4ワード)
    • インプットワード(xy:2ワード)

2つのインプットワードを4つのベクトルワードでミキシングし作業ベクトルを更新します。

    g(v, a, b, c, d, x, y) {
        v[a] = (v[a] + v[b] + x) >>> 0;
        v[d] = ((v[d] ^ v[a]) >>> 16) | ((v[d] ^ v[a]) << 16);
        v[c] = (v[c] + v[d]) >>> 0;
        v[b] = ((v[b] ^ v[c]) >>> 12) | ((v[b] ^ v[c]) << 20);
        v[a] = (v[a] + v[b] + y) >>> 0;
        v[d] = ((v[d] ^ v[a]) >>> 8) | ((v[d] ^ v[a]) << 24);
        v[c] = (v[c] + v[d]) >>> 0;
        v[b] = ((v[b] ^ v[c]) >>> 7) | ((v[b] ^ v[c]) << 25);
    }

圧縮関数

  • パラメータ
    • メッセージブロック(m:16バイト)
    • 最終フラグ(f

①  :ハッシュに初期ベクトルを連結します。(8バイト+8バイトの16バイト)
②②':連結したベクトルのインデックス1213にインプットサイズをXORします。
③  :最終フラグがの場合、インデックス140xffffffffXORします。
④  :10ラウンドミキシングします。(ChaCha20と同じように縦と斜めです。)
⑤  :ベクトルを16バイトから8バイトにXORで圧縮します。

    f(m, f) {
        let v = this.h.concat(this.IV);                // ①
        v[12] ^= this.ll[0];                           // ②
        v[13] ^= this.ll[1];                           // ②'
        if (f) {
            v[14] ^= 0xffffffff;                       // ③
        }
        for (let i = 0; i < 10; i++) {                 // ④
            let s = this.SIGMA[i % 10];
            this.g(v, 0, 4, 8, 12, m[s[0]], m[s[1]]);
            this.g(v, 1, 5, 9, 13, m[s[2]], m[s[3]]);
            this.g(v, 2, 6, 10, 14, m[s[4]], m[s[5]]);
            this.g(v, 3, 7, 11, 15, m[s[6]], m[s[7]]);
            this.g(v, 0, 5, 10, 15, m[s[8]], m[s[9]]);
            this.g(v, 1, 6, 11, 12, m[s[10]], m[s[11]]);
            this.g(v, 2, 7, 8, 13, m[s[12]], m[s[13]]);
            this.g(v, 3, 4, 9, 14, m[s[14]], m[s[15]]);
        }
        for (let i = 0; i < 8; i++) {
            this.h[i] ^= v[i] ^ v[i + 8];              // ⑤
        }
    }

更新関数(ダイジェスト計算)

  • パラメータ
    • メッセージ(バイト配列)

①:パラメータのメッセージをバイト数分ループします。
②:キャッシュサイズが64であるかチェックします、64バイトであれば③〜⑥までの処理をします。
③:インプットサイズに64を加算します。
④:キャッシュをメッセージブロックに変換します。
⑤:メッセージブロックで圧縮関数を実行します。最終フラグはです。
⑥:キャッシュを空にします。
⑦:キャッシュにメッセージのバイトを追加します。

    update(bs) {
        for (let i = 0; i < bs.length; i++) {          // ①
            if (this.cash.length == 64) {              // ②
                this.ll[0] += 64;                      // ③
                if (this.ll[0] > 0xffffffff) {
                    this.ll[1] += 1;
                    this.ll[0] >>>= 0;
                }
                let m = [0, 0, 0, 0, 0, 0, 0, 0,       // ④
                    0, 0, 0, 0, 0, 0, 0, 0];
                for (let i = 0; i < m.length; i++) {
                    m[i] = this.cash[i * 4 + 3] << 24 |
                        this.cash[i * 4 + 2] << 16 |
                        this.cash[i * 4 + 1] << 8 |
                        this.cash[i * 4];
                }
                this.f(m, false);                      // ⑤
                this.cash = [];                        // ⑥
            }
            this.cash.push(bs[i]);                     // ⑦
        }
    }

最終関数(ダイジェスト計算)

  • リターン
    • ハッシュ値

①:最終のインプットサイズを設定します。
②:キャッシュサイズが64未満の場合は64バイトになるまで0で埋めます。
③:キャッシュをメッセージブロックに変換します。
④:メッセージブロックで圧縮関数を実行します。最終フラグはです。
⑤:ハッシュをバイト配列にします。
⑥:指定のハッシュサイズ分、返します。

    final() {
        let size = this.cash.length;                   // ①
        this.ll[0] += size;
        if (this.ll[0] > 0xffffffff) {
            this.ll[1] += 1;
            this.ll[0] >>>= 0;
        }
        while (this.cash.length < 64) {                // ②
            this.cash.push(0);
        }
        let m = [0, 0, 0, 0, 0, 0, 0, 0,               // ③
            0, 0, 0, 0, 0, 0, 0, 0];
        for (let i = 0; i < m.length; i++) {
            m[i] = this.cash[i * 4 + 3] << 24 |
                this.cash[i * 4 + 2] << 16 |
                this.cash[i * 4 + 1] << 8 |
                this.cash[i * 4];
        }
        this.f(m, true);                              // ④
        let out = [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];
        for (let i = 0; i < this.h.length; i++) {
            out[i * 4 + 3] = this.h[i] >>> 24 & 0xff;
            out[i * 4 + 2] = this.h[i] >>> 16 & 0xff;
            out[i * 4 + 1] = this.h[i] >>> 8 & 0xff;
            out[i * 4 + 0] = this.h[i] & 0xff;
            if ((i * 4) > this.nn) {
                break;
            }
        }
        return out.slice(0, this.nn);                 // ⑥
    }

ダイジェスト計算

  • パラメータ
    • ハッシュサイズ(nn
    • キー(key
    • メッセージ(in、バイト配列)
  • リターン
    • ハッシュ値
    static blake2s(nn, key, in_) {
        let b2s = new BLAKE2s(nn, key);
        b2s.update(in_);
        return b2s.final();
    }

さいごに

高速で強度の高いハッシュ関数ですが、案外簡単に実装できます。
自身の得意とする言語等で実装してみてください。
ソースコードは、Githubにあります。

参考(Reference)

2
2
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
2
2