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

借用gcc原始碼中的sha1.c進行SHA1計算

起初打算用sha1校驗進行密碼比對,然後想找個能算SHA1校驗碼的C語言函式,想到這個演算法很可能在開原始碼中能找到,於是在gcc-4.9.2的原始碼中找到了一個名叫"sha1.c"的檔案,在原始碼樹的gcc-4.9.2\libiberty目錄下,就拿來用用看.

把該檔案拷出來,拿到VC6下開啟編譯,自動建立了一個工程,但是第26行有個嚴重錯誤:

#include <config.h>

找不到config.h,這是一個很常見的錯誤,原始碼樹下又搜不到這個檔案,於是,要麼新建一個空的檔案命成這個名字,要麼直接刪掉這行包含語句.咱不想破壞原始碼檔案,所以新建了一個空的config.h放在了工程目錄下,但,重新編譯還是提示找不到,大概是因為用尖括號包含的緣故.接下來修改工程配置,在C/C++的預處理路徑中新增一個小數點,代表工程檔案所在目錄,重新編譯,這回能找到了,但是出現了新的錯誤:在第28行:

#include "sha1.h"

回到原始碼樹中搜索,在gcc-4.9.2\include路徑下找到了sha1.h,拷出來放到工程路徑下,重新編譯,於是在sha1.h的第29行:

#include "ansidecl.h"

繼續搜,仍然在gcc-4.9.2\include路徑下,找到了這個ansidecl.h檔案,拷出來放到工程路徑下,重新編譯,終於編譯通過了.

分析下sha1.h檔案,可以看出,sha1_buffer函式就是我們要找的計算SHA1的函式

很好,新建個測試檔案,起名叫sha1_test.c,檔案內容如下:

#include <stdio.h>
#include "sha1.h"

int main()
{
    char buffer[64] = { 0 };
    unsigned char resblock[20] = "";
    size_t i = 0;

    sha1_buffer (buffer, sizeof (buffer), resblock);
    for (i = 0; i < 20; i++)
    {
        printf ("%02X", resblock[i]);
    }
    putchar ('\n');
    
    return 0;
}
程式碼很簡單,只是給了一組資料,64個位元組的零,計算其SHA1值,將計算結果以位元組為單位,用十六進位制打印出來,跑一下,結果如下:

C8D7D0EF0EEDFA82D2EA1AA592845B9A6D4B02B7

看起來挺像那麼回事的.然後,因為VC6新建工程後,預設為Debug模式,咱把它切換到Release模式下跑跑看,這一跑,壞了,結果不一樣了:

7076B3F1F5B3F7440BB4BFCC04C469EBD9FBC129

解決辦法有兩個:其一,修改工程配置,把sha1.c這個檔案的優化關掉,重新編譯,啊,這回結果變得和Debug下的結果一樣了.

其二,用舊版本的gcc原始碼,比如gcc-4.7.2的原始碼,這個版本沒有上述問題.

用檔案對比工具(比如SVN)對比下兩個版本的原始碼,發現只有一處差異,第303行,舊版本是

  if (ctx->total[0] < len)
    ++ctx->total[1];
新版本是
  ctx->total[1] += ((len >> 31) >> 1) + (ctx->total[0] < len);
果然新版本是把((len >> 31) >> 1)優化錯了,據我猜測,新版本這麼個改法,是為了在64位程式上,當記憶體中申請了超過2的32次方大小的記憶體後,對這塊記憶體求SHA1校驗碼,((len >> 31) >> 1)才有意義.32位程式由於不存在如此大的buf所以舊版本的沒有加.

手頭沒有條件測試這個情況,但是根據猜測,舊版本不加((len >> 31) >> 1),會導致對超過2的32次方大小的記憶體直接計算校驗碼出現錯誤吧.新版本之所以要加上這麼個量,想必是為了防止這樣的錯誤.但是禁止優化這種事情,很容易忘記,所以,最好的辦法是利用config.h檔案,在這個檔案中加上這麼一行:

#pragma optimize("",off)
強制關掉優化(僅在VC6下試過能用,其他編譯環境請自行試驗),OK了,有新版咱還是用新版比較安心什麼的


PS:在原始碼樹的gcc-4.9.2\libiberty目錄下,還能看到其他很多大概能用上的函式,像是md5.c什麼的,拿來用用吧