1. 程式人生 > >借用gcc原始碼中的sha1.c計算HMAC_SHA1

借用gcc原始碼中的sha1.c計算HMAC_SHA1

之前的文章介紹瞭如何用gcc原始碼中的sha1.c來計算SHA1值,也介紹瞭如何用M4的HASH模組進行硬體計算SHA1及其HMAC,並且將原始資料的精度提升到了bit。現在,為了驗證提升之後的計算結果的正確,順便糾正datasheet上的筆誤,在PC上進行軟體計算。

再重新介紹下HMAC的概念:

HMAC(message) = Hash[((key | pad) XOR 0x5C) | Hash(((key | pad) XOR 0x36) | message)]

用sha1_buffer函式即可計算SHA1的值,為了方便顯示,包裝成如下的函式:

inline static void sha1_bytes( char ref_hash[41], const void *buffer, size_t len )
{
    if (ref_hash != 0 && (buffer != 0 || len == 0))
    {
        unsigned char resblock[21] = "";
        size_t i = 0;
        
        sha1_buffer ((const char *)buffer, len, resblock);

        for (i = 0; i < 20; i++)
        {
            const char *hex = "0123456789ABCDEF";
            unsigned char ch = resblock[i];
            ref_hash[i * 2 + 0] = hex[(ch >> 4U) & 0x0F];
            ref_hash[i * 2 + 1] = hex[(ch >> 0U) & 0x0F];
        }
        
        ref_hash[40] = 0;
    }

    return;
}
然後根據HMAC的概念,寫出HMAC_SHA1的函式,考慮到計算時,需要將key和message拼接起來,所以並不直接用sha1_buffer函式,而是使用sha1_init_ctx、sha1_process_bytes、sha1_finish_ctx系列函式來計算。我們看一下sha1_buffer函式的原始碼:
/* Compute SHA1 message digest for LEN bytes beginning at BUFFER.  The
   result is always in little endian byte order, so that a byte-wise
   output yields to the wanted ASCII representation of the message
   digest.  */
void *
sha1_buffer (const char *buffer, size_t len, void *resblock)
{
  struct sha1_ctx ctx;

  /* Initialize the computation context.  */
  sha1_init_ctx (&ctx);

  /* Process whole buffer but last len % 64 bytes.  */
  sha1_process_bytes (buffer, len, &ctx);

  /* Put result in desired memory area.  */
  return sha1_finish_ctx (&ctx, resblock);
}
基本上就明白這組函式的用法了,沒錯,把sha1_process_bytes函式重複呼叫兩次,分別傳入帶拼接起來的兩部分,即可。HMAC函式如下:
inline static void hmac_sha1_bytes( char ref_hash[41],
    const void *key, size_t key_len,
    const void *msg, size_t msg_len )
{
    struct sha1_ctx ctx = { 0 };
    
    enum { HASH_LEN = 20 };
    enum { BLOCK_LEN = 64 };
    unsigned char hash[HASH_LEN] = { 0 };
    unsigned char key_pad[BLOCK_LEN] = { 0 };
    
    if (key_len > BLOCK_LEN)
    {
        ::sha1_buffer ((const char *)key, key_len, key_pad);
    }
    else
    {
        ::memcpy (key_pad, key, key_len);
    }
    
    unsigned char hash_temp[HASH_LEN] = { 0 };
    unsigned char key_pad_temp[BLOCK_LEN] = { 0 };
    size_t i = 0;
    for (i = 0; i < BLOCK_LEN; i++)
    {
        key_pad_temp[i] = key_pad[i] ^ 0x36;
        key_pad[i] ^= 0x5C;
    }
    
    ::sha1_init_ctx (&ctx);
    ::sha1_process_bytes (key_pad_temp, BLOCK_LEN, &ctx);
    ::sha1_process_bytes (msg, msg_len, &ctx);
    ::sha1_finish_ctx (&ctx, hash_temp);
    
    ::sha1_init_ctx (&ctx);
    ::sha1_process_bytes (key_pad, BLOCK_LEN, &ctx);
    ::sha1_process_bytes (hash_temp, HASH_LEN, &ctx);
    ::sha1_finish_ctx (&ctx, hash);
    
    for (i = 0; i < HASH_LEN; i++)
    {
        const char *hex = "0123456789ABCDEF";
        unsigned char ch = hash[i];
        ref_hash[i * 2 + 0] = hex[(ch >> 4U) & 0x0F];
        ref_hash[i * 2 + 1] = hex[(ch >> 0U) & 0x0F];
    }
    
    ref_hash[40] = 0;
    
    return;
}
不過,這兩個函式,就原始資料來說,包括HMAC演算法的key,都是以byte為單位的。雖然不知道實際應用中有沒有需要處理以bit為單位的情況,但是從SHA1的定義來看,其本身就是處理bit的。只不過,平時PC上常見的都是給檔案進行校驗,所以以byte為單位也就習以為常了。


為了改造這些函式,我們從sha1_buffer的原始碼入手進行分析。其過程分為三個階段:初始化、壓數、收尾。雖說要將其改造成以bit為單位,但實際上只要在收尾的時候添上0~7個bit就行了,中間部分亂添bit是沒有意義的。於是,我們跳過初始化和壓數,直接看收尾函式:

/* Process the remaining bytes in the internal buffer and the usual
   prolog according to the standard and write the result to RESBUF.

   IMPORTANT: On some systems it is required that RESBUF is correctly
   aligned for a 32-bit value.  */
void *
sha1_finish_ctx (struct sha1_ctx *ctx, void *resbuf)
{
  /* Take yet unprocessed bytes into account.  */
  sha1_uint32 bytes = ctx->buflen;
  size_t size = (bytes < 56) ? 64 / 4 : 64 * 2 / 4;

  /* Now count remaining bytes.  */
  ctx->total[0] += bytes;
  if (ctx->total[0] < bytes)
    ++ctx->total[1];

  /* Put the 64-bit file length in *bits* at the end of the buffer.  */
  ctx->buffer[size - 2] = SWAP ((ctx->total[1] << 3) | (ctx->total[0] >> 29));
  ctx->buffer[size - 1] = SWAP (ctx->total[0] << 3);

  memcpy (&((char *) ctx->buffer)[bytes], fillbuf, (size - 2) * 4 - bytes);

  /* Process last bytes.  */
  sha1_process_block (ctx->buffer, size * 4, ctx);

  return sha1_read_ctx (ctx, resbuf);
}

收尾分為三部分,首先將位元組數補齊到64*N+56的形式;然後綴上8個位元組的資料長度,即64bits,這樣一來全部資料都可以分成64位元組的組了;最後在補齊的資料上填充固定的位,函式中雖然用了一個fillbuf陣列來表示填充位,但實際上這個填充位只是1bit的1為首,其餘bit都是0。

修改方案如下:擴充兩個引數,用來表示收尾時要添的bits是什麼,以及添幾位;將結尾的資料長度上添上這個位數;將填充的第一個位元組改成待添的bits和1bit的1,當然函式要拷到別的檔案中,改改名字:

inline static void *sha1_finish_ctx_bit (struct sha1_ctx *ctx, void *resbuf,
                                         unsigned char last, size_t bits)
{
#ifdef WORDS_BIGENDIAN
# define SWAP(n) (n)
#else
# define SWAP(n) \
    (((n) << 24) | (((n) & 0xff00) << 8) | (((n) >> 8) & 0xff00) | ((n) >> 24))
#endif

/* This array contains the bytes used to pad the buffer to the next
   64-byte boundary.  (RFC 1321, 3.1: Step 1)  */
static const unsigned char fillbuf[64] = { 0x80, 0 /* , 0, 0, ...  */ };

    /* Take yet unprocessed bytes into account.  */
    sha1_uint32 bytes = ctx->buflen;
    size_t size = (bytes < 56) ? 64 / 4 : 64 * 2 / 4;
    
    /* Now count remaining bytes.  */
    ctx->total[0] += bytes;
    if (ctx->total[0] < bytes)
        ++ctx->total[1];
    
    /* Put the 64-bit file length in *bits* at the end of the buffer.  */
    ctx->buffer[size - 2] = SWAP ((ctx->total[1] << 3) | (ctx->total[0] >> 29));
    ctx->buffer[size - 1] = SWAP (ctx->total[0] << 3);
    
    memcpy (&((char *) ctx->buffer)[bytes], fillbuf, (size - 2) * 4 - bytes);
    
    if (bits > 0 && bits < 8)
    {
        ctx->buffer[size - 1] |= SWAP (bits);
        ((char *) ctx->buffer)[bytes] = (last & (0x7F00 >> bits)) | (0x80 >> bits);
    }
    
    /* Process last bytes.  */
    sha1_process_block (ctx->buffer, size * 4, ctx);
    
    return sha1_read_ctx (ctx, resbuf);

#undef SWAP
}
後兩個引數last表示收尾時要添的位是什麼,引數bits表示last中的最高多少位是有效的。同樣修改另一個函式如下:
inline static void *sha1_buffer_bit (const char *buffer, size_t len, void *resblock,
                                     unsigned char last, size_t bits)
{
    struct sha1_ctx ctx;
    
    /* Initialize the computation context.  */
    sha1_init_ctx (&ctx);
    
    /* Process whole buffer but last len % 64 bytes.  */
    sha1_process_bytes (buffer, len, &ctx);
    
    /* Put result in desired memory area.  */
    return sha1_finish_ctx_bit (&ctx, resblock, last, bits);
}
以這兩個函式為基礎,我們就可以修改之前的SHA1函式和HMAC函數了,修改成以bit為單位的版本:
inline static void sha1_bits( char ref_hash[41], const void *buffer, size_t len )
{
    if (ref_hash != 0 && (buffer != 0 || len == 0))
    {
        unsigned char resblock[21] = "";
        size_t i = 0;
        
        size_t bytes = len / 8;
        size_t bits = len % 8;

        if (bits == 0)
        {
            sha1_buffer ((const char *)buffer, bytes, resblock);
        }
        else
        {
            sha1_buffer_bit ((const char *)buffer, bytes, resblock,
                ((unsigned char *)buffer)[bytes], bits);
        }
        
        for (i = 0; i < 20; i++)
        {
            const char *hex = "0123456789ABCDEF";
            unsigned char ch = resblock[i];
            ref_hash[i * 2 + 0] = hex[(ch >> 4U) & 0x0F];
            ref_hash[i * 2 + 1] = hex[(ch >> 0U) & 0x0F];
        }
        
        ref_hash[40] = 0;
    }
    
    return;
}

inline static void hmac_sha1_bits( char ref_hash[41],
                                   const void *key, size_t key_len,
                                   const void *msg, size_t msg_len )
{
    struct sha1_ctx ctx = { 0 };
    
    enum { HASH_LEN = 20 };
    enum { BLOCK_LEN = 64 };
    unsigned char hash[HASH_LEN] = { 0 };
    unsigned char key_pad[BLOCK_LEN] = { 0 };
    
    size_t key_bytes = key_len / 8;
    size_t key_bits = key_len % 8;
    
    size_t msg_bytes = msg_len / 8;
    size_t msg_bits = msg_len % 8;

    if (key_len > BLOCK_LEN * 8)
    {
        if (key_bits == 0)
        {
            sha1_buffer ((const char *)key, key_bytes, key_pad);
        }
        else
        {
            ::sha1_buffer_bit ((const char *)key, key_bytes, key_pad,
            ((unsigned char *)key)[key_bytes], key_bits);
        }
    }
    else
    {
        if (key_bits == 0)
        {
            ::memcpy (key_pad, key, key_bytes);
        }
        else
        {
            ::memcpy (key_pad, key, key_bytes + 1);
            key_pad[key_bytes] &= 0x7F00 >> key_bits;
        }
    }
    
    unsigned char hash_temp[HASH_LEN] = { 0 };
    unsigned char key_pad_temp[BLOCK_LEN] = { 0 };
    size_t i = 0;
    for (i = 0; i < BLOCK_LEN; i++)
    {
        key_pad_temp[i] = key_pad[i] ^ 0x36;
        key_pad[i] ^= 0x5C;
    }
    
    ::sha1_init_ctx (&ctx);
    ::sha1_process_bytes (key_pad_temp, BLOCK_LEN, &ctx);
    ::sha1_process_bytes (msg, msg_bytes, &ctx);
    if (msg_bits == 0)
    {
        ::sha1_finish_ctx (&ctx, hash_temp);
    }
    else
    {
        ::sha1_finish_ctx_bit (&ctx, hash_temp,
            ((unsigned char *)msg)[msg_bytes], msg_bits);
    }
    
    ::sha1_init_ctx (&ctx);
    ::sha1_process_bytes (key_pad, BLOCK_LEN, &ctx);
    ::sha1_process_bytes (hash_temp, HASH_LEN, &ctx);
    ::sha1_finish_ctx (&ctx, hash);
    
    for (i = 0; i < HASH_LEN; i++)
    {
        const char *hex = "0123456789ABCDEF";
        unsigned char ch = hash[i];
        ref_hash[i * 2 + 0] = hex[(ch >> 4U) & 0x0F];
        ref_hash[i * 2 + 1] = hex[(ch >> 0U) & 0x0F];
    }
    
    ref_hash[40] = 0;
    
    return;
}
最後是測試,測試時使用的key和message,參照之前的文章《用M4晶片的HASH模組計算SHA1和HMAC_SHA1》:
int main()
{
    char key[256] = "";
    char message[256] = "";
    for (size_t i = 0; i < 256; i++)
    {
        key[i] = 8 + i * 13;
        message[i] = 3 + i * 5;
    }

    char sha1_1b            [41] = "";
    char sha1_5b            [41] = "";
    char sha1_8b            [41] = "";
    char sha1_13b           [41] = "";
    char sha1_21b           [41] = "";
    char sha1_34b           [41] = "";
    char sha1_377b          [41] = "";
    char sha1_610b          [41] = "";

    char hmac_0b_sha1_0b    [41] = "";
    char hmac_8b_sha1_8b    [41] = "";
    char hmac_8b_sha1_13b   [41] = "";
    char hmac_8b_sha1_610b  [41] = "";
    char hmac_13b_sha1_8b   [41] = "";
    char hmac_13b_sha1_13b  [41] = "";
    char hmac_13b_sha1_610b [41] = "";
    char hmac_512b_sha1_8b  [41] = "";
    char hmac_512b_sha1_13b [41] = "";
    char hmac_512b_sha1_610b[41] = "";
    char hmac_610b_sha1_8b  [41] = "";
    char hmac_610b_sha1_13b [41] = "";
    char hmac_610b_sha1_610b[41] = "";

    sha1_bits (sha1_1b  , message, 1  ); printf ("sha1_1b             = %s \n", sha1_1b  );
    sha1_bits (sha1_5b  , message, 5  ); printf ("sha1_5b             = %s \n", sha1_5b  );
    sha1_bits (sha1_8b  , message, 8  ); printf ("sha1_8b             = %s \n", sha1_8b  );
    sha1_bits (sha1_13b , message, 13 ); printf ("sha1_13b            = %s \n", sha1_13b );
    sha1_bits (sha1_21b , message, 21 ); printf ("sha1_21b            = %s \n", sha1_21b );
    sha1_bits (sha1_34b , message, 34 ); printf ("sha1_34b            = %s \n", sha1_34b );
    sha1_bits (sha1_377b, message, 377); printf ("sha1_377b           = %s \n", sha1_377b);
    sha1_bits (sha1_610b, message, 610); printf ("sha1_610b           = %s \n", sha1_610b);

    hmac_sha1_bits (hmac_0b_sha1_0b    , key, 0  , message, 0  ); printf ("hmac_0b_sha1_0b     = %s \n", hmac_0b_sha1_0b    );
    hmac_sha1_bits (hmac_8b_sha1_8b    , key, 8  , message, 8  ); printf ("hmac_8b_sha1_8b     = %s \n", hmac_8b_sha1_8b    );
    hmac_sha1_bits (hmac_8b_sha1_13b   , key, 8  , message, 13 ); printf ("hmac_8b_sha1_13b    = %s \n", hmac_8b_sha1_13b   );
    hmac_sha1_bits (hmac_8b_sha1_610b  , key, 8  , message, 610); printf ("hmac_8b_sha1_610b   = %s \n", hmac_8b_sha1_610b  );
    hmac_sha1_bits (hmac_13b_sha1_8b   , key, 13 , message, 8  ); printf ("hmac_13b_sha1_8b    = %s \n", hmac_13b_sha1_8b   );
    hmac_sha1_bits (hmac_13b_sha1_13b  , key, 13 , message, 13 ); printf ("hmac_13b_sha1_13b   = %s \n", hmac_13b_sha1_13b  );
    hmac_sha1_bits (hmac_13b_sha1_610b , key, 13 , message, 610); printf ("hmac_13b_sha1_610b  = %s \n", hmac_13b_sha1_610b );
    hmac_sha1_bits (hmac_512b_sha1_8b  , key, 512, message, 8  ); printf ("hmac_512b_sha1_8b   = %s \n", hmac_512b_sha1_8b  );
    hmac_sha1_bits (hmac_512b_sha1_13b , key, 512, message, 13 ); printf ("hmac_512b_sha1_13b  = %s \n", hmac_512b_sha1_13b );
    hmac_sha1_bits (hmac_512b_sha1_610b, key, 512, message, 610); printf ("hmac_512b_sha1_610b = %s \n", hmac_512b_sha1_610b);
    hmac_sha1_bits (hmac_610b_sha1_8b  , key, 610, message, 8  ); printf ("hmac_610b_sha1_8b   = %s \n", hmac_610b_sha1_8b  );
    hmac_sha1_bits (hmac_610b_sha1_13b , key, 610, message, 13 ); printf ("hmac_610b_sha1_13b  = %s \n", hmac_610b_sha1_13b );
    hmac_sha1_bits (hmac_610b_sha1_610b, key, 610, message, 610); printf ("hmac_610b_sha1_610b = %s \n", hmac_610b_sha1_610b);

    return 0;
}
測試結果如下:
sha1_1b             = BB6B3E18F0115B57925241676F5B1AE88747B08A
sha1_5b             = EF292519B5D8F9CF0449EA8A752C241F403579FD
sha1_8b             = 9842926AF7CA0A8CCA12604F945414F07B01E13D
sha1_13b            = DCF000F2759AB7D10EBD6A28BBBE9337997E2A0D
sha1_21b            = 9251748A9B44DC613F12D821ABB8098649A317B8
sha1_34b            = CEDECA9AC1821BEE1308085ADF6F4E4A9E18F99F
sha1_377b           = EF46518430F1D311C304F41DFC4508525865A1D3
sha1_610b           = E8B6E92B274FF98C33AF05A917090E5CFCB1802B
hmac_0b_sha1_0b     = FBDB1D1B18AA6C08324B7D64B71FB76370690E1D
hmac_8b_sha1_8b     = 70F7E312D4812C40858A1F5040BF3D5F6C38D445
hmac_8b_sha1_13b    = 26AFB4D0C18AFD17C8740C4D74AB6D5072BFD630
hmac_8b_sha1_610b   = CA0D1BDD307AA09636520AFF0645115F86A58B27
hmac_13b_sha1_8b    = 0D4A7B24AD4F74F01DA491104DA38D942F1701B4
hmac_13b_sha1_13b   = E14CA61F521F17E8D7DB29EF4BA25FB0FC3E0C5D
hmac_13b_sha1_610b  = EF1E2A041FE988E62A58693AC2712534A9062ED5
hmac_512b_sha1_8b   = C2126DC14B40188E92DB76029ADF19E48D1E7DEF
hmac_512b_sha1_13b  = 7D2B69F7702AE9E9F12EC7D8A7EA7101EF797870
hmac_512b_sha1_610b = E4699F8C4D5E7F7616EDA61AD04B4189FD79A0E2
hmac_610b_sha1_8b   = 82C2A0C8F30867E69693478B5C3B2FD8342A710B
hmac_610b_sha1_13b  = 09CEF56E641B4CFE81728CC49D877B92F7901CAC
hmac_610b_sha1_610b = 7FCB7623609E3B72B8A33D4A9BC3467EF0B62815
不錯,和M4硬體計算的結果是一樣的。測試工程可以到download.csdn.net/detail/sugar13/8563889下載

當然也可以從st網站直接下載:

在search中敲入stm32f439,就可以看到pdf格式的文件並且下載了。為了某些奇怪的目的,這裡就不粘pdf的直接地址了。

說說這個datasheet的第774頁那大名鼎鼎的筆誤吧:

Bits 4:0 NBLW: Number of valid bits in the last word of the message in the bit string
organization of hash processor
When these bits are written and DCAL is at ‘0’, they take the value on the AHB
databus:
0x00: All 32 bits of the last data written in the bit string organization of hash
processor (after data swapping) are valid.
0x01: Only bit [31] of the last data written in the bit string organization of hash
processor (after data swapping) are valid
0x02: Only bits [31:30] of the last data written in the bit string organization of
hash processor (after data swapping) are valid
0x03: Only bits [31:29] of the last data written in the bit string organization of
hash processor (after data swapping) are valid
...
0x1F: Only bits [0] of the last data written in the bit string organization of hash
processor (after data swapping) are valid
When these bits are written and DCAL is at
其中,0x00與眾不同,表示All 32 bits是有效的,接下來是:

0x01: Only bit [31] 
0x02: Only bits [31:30] 
0x03: Only bits [31:29] 

這沒問題,這裡的資料是以位元組的高位在前進行處理的,但是,用省略號一路點下去,到了0x1F:

0x1F: Only bits [0]

突然變成最低位了。這一定是筆誤,應該是bits [31:1]才對,或者不改這個,而是把後面的are valid改成are not valid。

再看看韌體庫的註釋,說道NBLW暫存器,那就是HASH_SetLastWordValidBitsNbr函式,它的註釋:

/**
  * @brief  Configure the Number of valid bits in last word of the message
  * @param  ValidNumber: Number of valid bits in last word of the message.
  *           This parameter must be a number between 0 and 0x1F.
  *             - 0x00: All 32 bits of the last data written are valid
  *             - 0x01: Only bit [0] of the last data written is valid
  *             - 0x02: Only bits[1:0] of the last data written are valid
  *             - 0x03: Only bits[2:0] of the last data written are valid
  *             - ...
  *             - 0x1F: Only bits[30:0] of the last data written are valid    
  * @note   The Number of valid bits must be set before to start the message 
  *         digest competition (in Hash and HMAC) and key treatment(in HMAC).    
  * @retval None
  */
整個變成低位元組部分了……這也一定是筆誤吧,大概……


PS:啊啊啊啊啊,雖然咱喜歡看別人寫的文章裡面帶原始碼的,但是自己寫寫才發現,原始碼太佔篇幅了,文章的意境都被破壞了。。。