信息摘要算法之六:HKDF算法分析與實現


HKDF是一種特定的鍵衍生函數(KDF),即初始鍵控材料的功能,KDF從其中派生出一個或多個密碼強大的密鑰。在此我們想要描述的是基於HMAC的HKDF。

1HKDF概述

密鑰派生函數(KDF)是密碼系統的基本組成部分。它的目標是獲取一些初始的密鑰材料,並從中派生出一個或多個安全強度很大的密鑰。

我們將要介紹的是基於HMAC的KDF,稱之為HKDF,它可以應用於各種協議和應用程序的構建。HKDF在邏輯上由兩個部分組成。第一個部分采用輸入密鑰材料和“提取”,它是一個固定長度的偽隨機密鑰K。第二部分則將密鑰擴展為幾個隨機密鑰,並以我們需要的長度輸出。

在許多應用程序中,輸入的密鑰材料不一定是均勻分布的,而且攻擊者可能對它有一定的了解,甚至可以部分控制它。因此,“提取”階段的目標是將輸入密鑰材料的可能分散的熵“集中”到一個短的,但強度高的偽隨機密鑰。在某些應用程序中,輸入可能已經是一個好的偽隨機密鑰;在這些情況下,“提取”部分是不必要的,“擴展”部分可以單獨使用。

第二階段“擴展”偽隨機密鑰到所需的長度;輸出密鑰的數量和長度取決於所需密鑰的具體加密算法。

2、算法描述

HKDF就是利用實例化了的哈希函數的HMAC來實現密鑰生成。HMAC有兩個必要的參數:第一個是密鑰,第二個是輸入(或消息)。當消息由幾個元素組成時,我們在第二個參數中使用串聯(表示|);例如,HMAC(K, elem1|elem2|elem3)。

一般來說,HKDF算法所做的工作分為2步,其一稱之為提取;其二稱之為擴展。接下來我們說明這兩步。

(1)、提取

所謂提取就是將用戶輸入的密鑰盡量的偽隨機化。一般是使用一個哈希函數來實現,具體那種更具需要在HMAC中確定。這一過程可以表示如下:

HKDF-Extract(salt, IKM)得到PRK,而PRK的計算公式如下:

PRK = HMAC-Hash(salt, IKM) ,這其中有2個輸入和1個輸出,

Salt,輸入,加鹽操作的鹽,如果不提供則全部初始化為0的字符串,長度則為所采用哈希函數的散列值長度。

IKM,輸入,數額udemiyao材料。

PRK,輸出,偽隨機化后的密鑰,長度則為所采用哈希函數的散列值長度。

需要注意的是,在這一步中“IKM”被用作HMAC輸入,而不是HMAC密鑰。

(2)、擴展

所謂擴展,其實就是通過一系列的哈希運算將密鑰擴展到我們需要的長度。這個長度我們記為L。這一過程標識如下:

HKDF-Expand(PRK, info, L) -> OKM,其中有3個輸入量和1個輸出量,其中:

PRK,輸入,一般是在提取階段得到的輸出,是一個偽隨機的密鑰,長度不小於所采用的哈希算法的輸出摘要長度。

Info,輸入,可選上下文和應用程序特定信息(可以是零長度字符串)

L,輸入,以字節計算的密鑰原料的長度,一般不長於哈希函數輸出摘要長度的255倍。

OKM,輸出,長度為L的密鑰材料輸出,其計算方式如下:

計算一系列的哈希值,我們記為T(n),其中n為0-255的整數,具體取值由所計算的長度L來確定。n的最大值為:L除以所用哈希函數輸出摘要的長度,再向上取整,我們將其記為N。

T(0) = 長度為0的空字符串

T(1) = HMAC-Hash(PRK, T(0) | info | 0x01)

T(2) = HMAC-Hash(PRK, T(1) | info | 0x02)

T(3) = HMAC-Hash(PRK, T(2) | info | 0x03)

直到T(N)為止,需要注意的是,每個哈希操作都會串接一個字節的常量,從1到N,所以最大的N值只能到255。然后將計算得到的這些T(n)串接起來,我們記為T,即:

T = T(1) | T(2) | T(3) | ... | T(N)

而我們想要獲得的OKM正是取T的前L個字節組成。這要我們就得到了我們想要的密鑰材料。

3、代碼實現

上面我們已經描述了HKDF的實現過程,接下來我們將實現這一過程。首先,我們任然是定義一個上下文結構。

/** 該結構將為HKDF的提取-擴展秘鑰派生函數提供上下文信息*/

typedef struct HKDFContext {

  int whichSha;

  HMACContext hmacContext;

  int hashSize; 

  unsigned char prk[SHAMaxHashSize];

  int Computed;

  int Corrupted; 

} HKDFContext;

接着我們實現HDKF的抽取函數,該函數的實現如下:

int hkdfExtract(SHAversion whichSha,

                const unsigned char *salt, int salt_len,

                const unsigned char *ikm, int ikm_len,

                uint8_t prk[SHAMaxHashSize])

{

  unsigned char nullSalt[SHAMaxHashSize];

  if (salt == 0)

  {

    salt = nullSalt;

    salt_len = SHAHashSize(whichSha);

    memset(nullSalt,'\0',salt_len);

  }

  else if (salt_len < 0)

  {

    return shaBadParam;

  }

  return hmac(whichSha, ikm, ikm_len, salt, salt_len, prk);

}

還需要實現HKDF擴展函數,該函數的實現如下:

int hkdfExpand(SHAversion whichSha, const uint8_t prk[ ], int prk_len,

               const unsigned char *info, int info_len,

               uint8_t okm[ ], int okm_len)

{

  int hash_len, N;

  unsigned char T[SHAMaxHashSize];

  int Tlen, where, i;

 

  if (info == 0)

  {

    info = (const unsigned char *)"";

    info_len = 0;

  }

  else if (info_len < 0)

  {

    return shaBadParam;

  }

 

  if (okm_len <= 0) return shaBadParam;

  if (!okm) return shaBadParam;

  hash_len = SHAHashSize(whichSha);

  if (prk_len < hash_len) return shaBadParam;

  N = okm_len / hash_len;

  if ((okm_len % hash_len) != 0) N++;

  if (N > 255) return shaBadParam;

  Tlen = 0;

  where = 0;

 

  for (i = 1; i <= N; i++)

  {

    HMACContext context;

    unsigned char c = i;

    int ret = hmacReset(&context, whichSha, prk, prk_len) ||

              hmacInput(&context, T, Tlen) ||

              hmacInput(&context, info, info_len) ||

              hmacInput(&context, &c, 1) ||

              hmacResult(&context, T);

    if (ret != shaSuccess) return ret;

    memcpy(okm + where, T,(i != N) ? hash_len : (okm_len - where));

    where += hash_len;

    Tlen = hash_len;

  }

  return shaSuccess;

}

至此我們就實現了HKDF的全部功能。

4、結果

前面我們已經實現了HKDF,我們還需要對其進行驗證,我們采用SHA-256哈希函數,分別生成40位和80為的密鑰。

40位密鑰:

80位密鑰:

 

很明顯,40位的密鑰和80位的密鑰前40位都是相同的。

歡迎關注:


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM