現(xiàn)代密碼學(xué):Hash函數(shù)Keccak

Hash函數(shù)的核心在于設(shè)計(jì)壓縮函數(shù)「宕妫可以證明,如果壓縮函數(shù)具有抗碰撞能力瞳秽,那么迭代Hash函數(shù)也具有抗碰撞能力瓣履。
2007年起,NIST開始向全球征集新的安全Hash算法SHA-3寂诱,最后的優(yōu)勝者是Keccak拂苹。Keccak以及SHA-3在正式成為標(biāo)準(zhǔn)之前有很多不同程度的更改,我想這也是網(wǎng)上有關(guān)Keccak和SHA-3算法的資料都多多少少不太一致的原因痰洒。本文僅介紹Keccak-224/256/384/512,以當(dāng)前Keccak官網(wǎng)中的算法描述為準(zhǔn)浴韭。參考文獻(xiàn)中列出了該算法的一些詳細(xì)描述丘喻,需要說明的是,除官網(wǎng)外的參考文獻(xiàn)都與官網(wǎng)中的描述有些出入念颈,請(qǐng)仔細(xì)甄別泉粉。

整體描述

Keccak算法采用海綿結(jié)構(gòu)(sponge construction),在預(yù)處理(padding并分成大小相同的塊)后榴芳,海綿結(jié)構(gòu)主要分成兩部分:

  • 吸入階段(absorbing phase):將塊x_i傳入算法并處理嗡靡。
  • 擠出階段(squeezing phase):產(chǎn)生一個(gè)固定長(zhǎng)度的輸出。

Keccak算法的整體結(jié)構(gòu)如圖:


High-level view on Keccak

在這兩個(gè)階段要是使用同一個(gè)壓縮函數(shù)Keccak-f窟感,下圖展示了算法“吸入”一個(gè)塊x_i并處理讨彼,最后擠出輸出的過程:


吸入和擠出階段

從圖中我們可以歸納出大致的流程:

  1. 對(duì)輸入串x做padding,使其長(zhǎng)度能被r整除柿祈,將padding后分割成長(zhǎng)度為r的塊哈误,即x=x_0||x_1||x_2||...||x_t-1。
  2. 初始化一個(gè)長(zhǎng)度為r+c bit的全零向量躏嚎。
  3. 輸入塊x_i蜜自,將x_i和向量的前r個(gè)bit亦或,然后輸入到Keccak-f函數(shù)中處理卢佣。
  4. 重復(fù)上一步重荠,直至處理完x中的每個(gè)塊。
  5. 輸出長(zhǎng)為r的塊作為y_0虚茶,并將向量輸入到Keccak-f函數(shù)中處理戈鲁,輸出y_1仇参,以此類推。得到的Hash序列即為y=y_0||y_1||y_2||...||y_u荞彼。在Keccak-224/256/384/512中冈敛,只需要在y_0中取出對(duì)應(yīng)長(zhǎng)度的前綴即可。

針對(duì)圖中的參數(shù)鸣皂,做出如下定義:

  • r:比特率(bit rate)抓谴,其值為每個(gè)輸入塊的長(zhǎng)度。
  • c:容量(capacity)寞缝,其長(zhǎng)度為輸出長(zhǎng)度的兩倍癌压。
  • b:向量的長(zhǎng)度,b=r+c荆陆。b的值依賴于指數(shù)l滩届,即b=25*(2^l)。當(dāng)l=0,1時(shí)被啼,b=25,50帜消。由于這時(shí)b太小,通常不能作為實(shí)際算法使用浓体。在本文中泡挺,取l=6,b=1600命浴。

在Keccak-224/256/384/512中娄猫,b、r生闲、c及輸出長(zhǎng)度的取值見下表媳溺。

b/bits r/bits c/bits 輸出長(zhǎng)度/bits 安全級(jí)別/bits
1600 1152 448 224 112
1600 1088 512 256 128
1600 832 768 384 192
1600 576 1024 512 256

Padding

由于目前計(jì)算機(jī)大多按照字節(jié)尋址,輸入的最小單位為字節(jié)碍讯,因此以下的描述中默認(rèn)輸入bit長(zhǎng)度能整除8悬蔽。

按照官網(wǎng)的描述,padding偽代碼如下:

P = Mbytes || d || 0x00 || … || 0x00
P = P xor (0x00 || … || 0x00 || 0x80)

Mbytes為輸入串冲茸,“||”符號(hào)表示比特串串聯(lián)屯阀。在SHA3的標(biāo)準(zhǔn)中,d為0x06轴术。由于Keccak中按照字節(jié)序?yàn)樾《四阉ィ陨厦枋鱿喈?dāng)于在輸入比特串后接0110*1以將長(zhǎng)度補(bǔ)齊到能被r整除。關(guān)于Keccak中字節(jié)序的問題可以參考官網(wǎng)中的Bits and bytes in Keccak逗栽。

壓縮函數(shù)Keccak-f

壓縮函數(shù)以b比特作為輸入盖袭,b比特作為輸出。內(nèi)部結(jié)構(gòu)如下:


Internel structure of Keccak-f

Keccak-f包含n_r輪。n_r的取值與我們之前計(jì)算b時(shí)用到的指數(shù)l有關(guān)鳄虱,具體地弟塞,n_r=12+2*l。Keccak-224/256/384/512中拙已,取l=6决记,因此n_r=24。在每一輪中倍踪,要以此執(zhí)行五步系宫,即θ(theta)、ρ(rho)建车、π(pi)扩借、χ(chi)、ι(iota)缤至。在處理過程中潮罪,我們把b=1600個(gè)比特排列成一個(gè)5*5*w的矩陣,其中w=2^l=64比特领斥,如圖:


比特排列方式

接下來嫉到,我們來描述這五步的具體內(nèi)容。

Theta Step (θ)

  C[x] = A[x,0] xor A[x,1] xor A[x,2] xor A[x,3] xor A[x,4],   for x in 0…4
  D[x] = C[x-1] xor rot(C[x+1],1),                             for x in 0…4
  A[x,y] = A[x,y] xor D[x],                           for (x,y) in (0…4,0…4)

該步驟的輸入輸出均存在A矩陣中月洛。額外說明一點(diǎn)是屯碴,rot(num, offset)表示將w比特的num向z軸正方向循環(huán)移動(dòng)offset位。具體實(shí)現(xiàn)中膊存,可以當(dāng)成循環(huán)左移offset位,我在代碼中的實(shí)現(xiàn)如下:

uint64_t Keccak::rot(uint64_t num, int offset) {
    if (offset == 0) {
        return num;
    }
    return (num << offset) | (num >> (64 - offset));
}

(一個(gè)玄學(xué)問題:zp同學(xué)向我指出忱叭,如果不加offset==0的特判隔崎,當(dāng)offset==0時(shí),num>>64的值不一定為0韵丑,而是一個(gè)不確定的值爵卒,這會(huì)導(dǎo)致該函數(shù)的輸出錯(cuò)誤。但是我加不加特判對(duì)結(jié)果的正確性沒有影響撵彻。為了保險(xiǎn)起見钓株,這里還是將特判加上了。)

Rho (ρ) and Pi (π) Steps

  B[y,2*x+3*y] = rot(A[x,y], r[x,y]),                 for (x,y) in (0…4,0…4)

該步的輸入為A數(shù)組陌僵,輸出為B數(shù)組轴合。rot含義同上一步,其中作為offset的r數(shù)組定義如下:

x=3 x=4 x=0 x=1 x=2
y=2 25 39 3 10 43
y=1 55 20 36 44 6
y=0 28 27 0 1 62
y=4 56 18 14 2 61
y=3 21 8 41 45 15

代碼中定義如下:

    const int R_CONS[MAT][MAT] = {
        {0, 36, 3, 41, 18}, 
        {1, 44, 10, 45, 2}, 
        {62, 6, 43, 15, 61},
        {28, 55, 25, 21, 56},
        {27, 20, 39, 8, 14}};

Chi (χ) Step

  A[x,y] = B[x,y] xor ((not B[x+1,y]) and B[x+2,y]),  for (x,y) in (0…4,0…4)

該步驟中碗短,輸入為B矩陣受葛,輸出為A矩陣。

Iota (ι) Step

  A[0,0] = A[0,0] xor RC

該步驟輸入和輸出均為A矩陣。RC值與輪數(shù)有關(guān)总滩,RC在24輪中的定義如下:

    const uint64_t RC[nr] = {
        0x0000000000000001, 0x0000000000008082, 0x800000000000808A, 0x8000000080008000,
        0x000000000000808B, 0x0000000080000001, 0x8000000080008081, 0x8000000000008009, 
        0x000000000000008A, 0x0000000000000088, 0x0000000080008009, 0x000000008000000A,
        0x000000008000808B, 0x800000000000008B, 0x8000000000008089, 0x8000000000008003,
        0x8000000000008002, 0x8000000000000080, 0x000000000000800A, 0x800000008000000A,
        0x8000000080008081, 0x8000000000008080, 0x0000000080000001, 0x8000000080008008};

輸出

通過海綿結(jié)構(gòu)中的擠出階段纲堵,可以獲得任意長(zhǎng)度的輸出。在Keccak-224/256/384/512中闰渔,我們只需要獲得y0中的前224/256/384/512個(gè)bit作為輸出即可席函。

具體實(shí)現(xiàn)

最后,附上Keccak的代碼實(shí)現(xiàn)冈涧。在使用時(shí)茂附,通過Keccak("224")(或"256" "384" "512")來指定Keccak的類型并初始化。通過uint8_t* encrypt(uint8_t *seq, int len);函數(shù)來獲得Hash序列炕舵。其中seq為輸入串(以字節(jié)為單位)何之,len為輸入的長(zhǎng)度(以字節(jié)為單位)。

keccak.h:

#include <cstdio>
#include <string>
#include <cstdint>
#include <iostream>

class Keccak {
    const int b = 200;
    int r, c; // In bytes
    uint8_t P;
    static const int nr = 24;
    const int w = 64;
    int output_len;

    static const int MAT = 5;
    const int R_CONS[MAT][MAT] = {
        {0, 36, 3, 41, 18}, 
        {1, 44, 10, 45, 2}, 
        {62, 6, 43, 15, 61},
        {28, 55, 25, 21, 56},
        {27, 20, 39, 8, 14}};
    const uint64_t RC[nr] = {
        0x0000000000000001, 0x0000000000008082, 0x800000000000808A, 0x8000000080008000,
        0x000000000000808B, 0x0000000080000001, 0x8000000080008081, 0x8000000000008009, 
        0x000000000000008A, 0x0000000000000088, 0x0000000080008009, 0x000000008000000A,
        0x000000008000808B, 0x800000000000008B, 0x8000000000008089, 0x8000000000008003,
        0x8000000000008002, 0x8000000000000080, 0x000000000000800A, 0x800000008000000A,
        0x8000000080008081, 0x8000000000008080, 0x0000000080000001, 0x8000000080008008};
    const int MIN_PAD = 1;

    uint64_t theta_C[MAT];
    uint64_t theta_D[MAT];

    uint64_t after_theta[MAT][MAT];
    uint64_t after_rho_pi[MAT][MAT];
    uint64_t after_chi[MAT][MAT];
    uint64_t after_f[MAT][MAT];

public:
    Keccak(std::string type) {
        if (type == "224") {
            output_len = 28;
            c = 56;
            // P = 0xCC;
        } else if (type == "256") {
            output_len = 32;
            c = 64;
            // P = 0xEC;
        } else if (type == "384") {
            output_len = 48;
            c = 96;
            // P = 0xCC;
        } else if (type == "512") {
            output_len = 64;
            c = 128;
            // P = 0xEC;
        } else {
            printf("Wrong type!\n");
        }
        P = 0x06;
        r = b - c;
    }

private:
    uint64_t rot(uint64_t num, int offset);
    void theta(uint64_t input[MAT][MAT], uint64_t output[MAT][MAT]);
    void rho_pi(uint64_t input[MAT][MAT], uint64_t output[MAT][MAT]);
    void chi(uint64_t input[MAT][MAT], uint64_t output[MAT][MAT]);
    void iota(uint64_t input[MAT][MAT], int round);
    void keccak_f(int round);

public:
    uint8_t result[64];
    uint8_t* encrypt(uint8_t *seq, int len);
};

keccak.cpp:

#include "keccak.h"
#include <cstring>
#include <cstdio>

uint64_t Keccak::rot(uint64_t num, int offset) {
    if (offset == 0) {
        return num;
    }
    return (num << offset) | (num >> (64 - offset));
}

void Keccak::theta(uint64_t input[MAT][MAT], uint64_t output[MAT][MAT]) {
    for (int i = 0; i < MAT; ++i) {
        theta_C[i] = 0x0;
        for (int j = 0; j < MAT; ++j) {
            theta_C[i] ^= input[i][j];
        }
    }

    for (int i = 0; i < MAT; ++i) {
        theta_D[i] = theta_C[(i - 1 + MAT) % MAT] ^ rot(theta_C[(i + 1) % MAT], 1);
    }

    for (int i = 0; i < MAT; ++i) {
        for (int j = 0; j < MAT; ++j) {
            output[i][j] = input[i][j] ^ theta_D[i];
        }
    }
}

void Keccak::rho_pi(uint64_t input[MAT][MAT], uint64_t output[MAT][MAT]) {
    for (int i = 0; i < MAT; ++i) {
        for (int j = 0; j < MAT; ++j) {
            output[j][(2 * i + 3 * j) % MAT] = rot(input[i][j], R_CONS[i][j]);
        }
    }
    // printf("%lld\n", output[0][0]);
}

void Keccak::chi(uint64_t input[MAT][MAT], uint64_t output[MAT][MAT]) {
    for (int i = 0; i < MAT; ++i) {
        for (int j = 0; j < MAT; ++j) {
            output[i][j] = input[i][j] ^ ((~input[(i + 1) % MAT][j]) & input[(i + 2) % MAT][j]);
        }
    }
}

void Keccak::iota(uint64_t input[MAT][MAT], int round) {
    input[0][0] ^= RC[round];
}

void Keccak::keccak_f(int round) {
    theta(after_f, after_theta);
    rho_pi(after_theta, after_rho_pi);
    chi(after_rho_pi, after_f);
    iota(after_f, round);
}

uint8_t* Keccak::encrypt(uint8_t *seq, int seq_len) {
    uint8_t *pad_seq;
    int block;
    int pad_zero = 0;
    if ((seq_len + MIN_PAD) % r != 0) {
        pad_zero = r - (seq_len + MIN_PAD) % r;
    }
    block = (seq_len + MIN_PAD + pad_zero) / r;
    pad_seq = new uint8_t[seq_len + MIN_PAD + pad_zero];
    memcpy(pad_seq, seq, seq_len);
    pad_seq[seq_len] = P;
    if (pad_zero > 0) {
        memset(pad_seq + seq_len + 1, 0, pad_zero);
    }
    pad_seq[seq_len + MIN_PAD + pad_zero - 1] |= 0x80;

    for (int i = 0; i < MAT; ++i) {
        for (int j = 0; j < MAT; ++j) {
            after_f[i][j] = 0;
        }
    }

    for (int i = 0; i < block; ++i) {
        int pos = 0;
        for (int j = i * r; j < (i + 1) * r; j += 8) {
            uint64_t t = 0;
            for (int k = j + 7; k >= j; --k) {
                t = (t << 8) | pad_seq[k];
            }
            after_f[pos % MAT][pos / MAT] ^= t;
            pos++;
        }
        for (int j = 0; j < nr; ++j) {
            keccak_f(j);
        }
    }

    for (int i = 0; i < output_len; ) {
        for (int j = 0; j < 8; ++j) {
            result[i] = after_f[(i / 8) % MAT][(i / 8) / MAT] & 0xff;
            after_f[(i / 8) % MAT][(i / 8) / MAT] >>= 8;
            i++;
            if (i >= output_len) {
                break;
            } 
        }
    }
    return result;
}

參考文獻(xiàn)

  1. Keccak官網(wǎng)
  2. Keccak算法介紹與分析 (注:本文中的padding方法和官網(wǎng)不一致)
  3. SHA-3 and The Hash Function Keccak(注:本文中的r咽筋、c的設(shè)置以及padding方法和官網(wǎng)不一致)

文中圖片均來自參考文獻(xiàn)[3]溶推,偽代碼均來自參考文獻(xiàn)[1]。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末奸攻,一起剝皮案震驚了整個(gè)濱河市蒜危,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌睹耐,老刑警劉巖辐赞,帶你破解...
    沈念sama閱讀 221,576評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異硝训,居然都是意外死亡响委,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,515評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門窖梁,熙熙樓的掌柜王于貴愁眉苦臉地迎上來赘风,“玉大人,你說我怎么就攤上這事纵刘⊙裕” “怎么了?”我有些...
    開封第一講書人閱讀 168,017評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵假哎,是天一觀的道長(zhǎng)瞬捕。 經(jīng)常有香客問我,道長(zhǎng)舵抹,這世上最難降的妖魔是什么肪虎? 我笑而不...
    開封第一講書人閱讀 59,626評(píng)論 1 296
  • 正文 為了忘掉前任,我火速辦了婚禮掏父,結(jié)果婚禮上笋轨,老公的妹妹穿的比我還像新娘秆剪。我一直安慰自己,他們只是感情好爵政,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,625評(píng)論 6 397
  • 文/花漫 我一把揭開白布仅讽。 她就那樣靜靜地躺著,像睡著了一般钾挟。 火紅的嫁衣襯著肌膚如雪洁灵。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,255評(píng)論 1 308
  • 那天掺出,我揣著相機(jī)與錄音徽千,去河邊找鬼。 笑死汤锨,一個(gè)胖子當(dāng)著我的面吹牛双抽,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播闲礼,決...
    沈念sama閱讀 40,825評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼牍汹,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了柬泽?” 一聲冷哼從身側(cè)響起慎菲,我...
    開封第一講書人閱讀 39,729評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎锨并,沒想到半個(gè)月后露该,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,271評(píng)論 1 320
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡第煮,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,363評(píng)論 3 340
  • 正文 我和宋清朗相戀三年解幼,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片包警。...
    茶點(diǎn)故事閱讀 40,498評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡书幕,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出揽趾,到底是詐尸還是另有隱情,我是刑警寧澤苛骨,帶...
    沈念sama閱讀 36,183評(píng)論 5 350
  • 正文 年R本政府宣布篱瞎,位于F島的核電站,受9級(jí)特大地震影響痒芝,放射性物質(zhì)發(fā)生泄漏俐筋。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,867評(píng)論 3 333
  • 文/蒙蒙 一严衬、第九天 我趴在偏房一處隱蔽的房頂上張望澄者。 院中可真熱鬧,春花似錦、人聲如沸粱挡。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,338評(píng)論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽询筏。三九已至榕堰,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間嫌套,已是汗流浹背逆屡。 一陣腳步聲響...
    開封第一講書人閱讀 33,458評(píng)論 1 272
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留踱讨,地道東北人魏蔗。 一個(gè)月前我還...
    沈念sama閱讀 48,906評(píng)論 3 376
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像痹筛,于是被迫代替她去往敵國(guó)和親莺治。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,507評(píng)論 2 359

推薦閱讀更多精彩內(nèi)容