借用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,檔案內容如下:
程式碼很簡單,只是給了一組資料,64個位元組的零,計算其SHA1值,將計算結果以位元組為單位,用十六進位制打印出來,跑一下,結果如下:#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; }
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什麼的,拿來用用吧