Sodium密碼學演算法庫手冊(中文版)

上海交通大學密碼與計算機安全實驗室(LoCCS)軟體安全小組(GoSSIP)版權所有,轉載請與作者取得聯繫!

2017年暑期,GoSSIP軟體安全小組面向全國優秀的本科同學開放了暑期實習,在本次暑期實習,來自上海、武漢、成都、南京、福州、南昌、桂林等地的同學在為期兩個月的學習和實踐過程中產出了大量優秀的工作成果,其中,來自華中科技大學的王舒然同學和南京郵電大學的倪嘉慧同學在密碼學安全方面進行研究的過程中,順帶對當前非常優秀的一款密碼學演算法庫Sodium的英文文檔進行了翻譯工作,希望能夠將這款優秀的現代演算法庫推廣,整個演算法庫手冊的中文版可訪問 下載地址 下載,下面是Sodium庫的一些簡單介紹。

======================================================================

Sodium是一個易於使用的現代(modern)密碼學演算法庫,它支持加密、解密、簽名、口令Hash以及更多的一些密碼學函數實現。Sodium是一個基於[NaCl](Introduction)開發的可移植、可交叉編譯、可直接安裝的代碼庫,具有兼容NaCL的API以及擴展的API,進一步提高了可用性。Sodium支持多種編譯器和操作系統,包括Windows(MinGW或Visual Studio,x86和x86_64)、iOS和Android。

Sodium的目標是提供構建更高級加密工具所需的所有核心操作,同時,其設計選擇強調安全性,各種「魔術常量」(magic constants)有明確的理論基礎,對於內存的安全管理和抵禦側信道計時攻擊有著很好的考慮。儘管強調了高安全性,但是與NIST標準的大多數實現相比,Sodium的密碼學原語的運行速度要快得多。

除了典型的加密和簽名演算法等,Sodium還提供了很多安全的介面來防止密碼學誤用,下面是一些實例:

1 常量時間內的差異性測試

int sodium_memcmp(const void * const b1_, const void * const b2_, size_t len);

當一個比較涉及私密數據(例如:密鑰,認證標籤)的時候,使用一個常量時間比較函數來抵禦側信道攻擊是至關重要的。

sodium_memcmp()函數可以用於此目的。

當b1_指向的len位元組與b2_指向的len位元組相匹配的時候,該函數返回0,否則返回-1。

注意:sodium_memcmp()不是一個字典比較器,它並不是memcmp()的一個通用替代品。

2 全零測試

int sodium_is_zero(const unsigned char *n, const size_t nlen);

如果n指向nlen個位元組的向量只包含零,此函數返回1。如果找到非零的比特,就返回0.

其執行時間對於給定長度是恆定的。

3 安全內存分配

3.1 清空內存

void sodium_memzero(void * const pnt, const size_t len);

使用結束後,敏感數據應該被覆蓋,但是memset()和手寫的代碼會被優化編譯器或鏈接器悄悄地刪除。

即使代碼會被優化,sodium_memzero()這個函數嘗試從pnt開始有效地清空len個位元組。

3.2 鎖定內存

int sodium_mlock(void * const addr, const size_t len);

sodium_mlock()這個函數鎖定從addr開始的至少len個位元組的內存。這將有助於避免將敏感數據交換到硬碟上。

另外,建議在處理敏感數據的機器上完全禁用交換分區,或者作為第二選擇,使用加密的交換分區

出於類似的原因,在Unix系統上,當您在開發環境之外運行密碼相關的代碼時,您應該禁用核心轉儲。這可以通過使用內置的shell(例如ulimit)或者使用setrlimit(RLIMIT_CORE, &(struct rlimit) {0, 0})。在能夠實現此功能的操作系統上,還應禁用內核崩潰轉儲。

sodium_mlock()包裝了mlock()和VirtualLock()。注意:許多系統對進程可能鎖定的內存量進行限制。注意在必要時應提高這些限制(例如Unix ulimits)。當任何限制達到的時候,sodium_lock()將返回-1。

int sodium_munlock(void * const addr, const size_t len);

在鎖定的內存不再被使用之後,應該調用sodium_munlock()函數。在將頁面重新標記為可交換之前,它會從addr開始清空len個位元組。因此不需要在使用sodium_munlock()函數之前調用sodium_memzero()函數。

在支持它的系統上,sodium_mlock()也包裝了madvise(),並建議內核不要將鎖定的內存包含在核心轉儲中。 sodium_unlock()也會撤消這個額外的保護。

3.3 保護堆分配

Sodium提供用於存儲敏感數據的堆分配函數,這些函數不是一般的堆分配函數。 特別地,它們比malloc()慢,並且需要3或4個額外的虛擬內存頁。

在使用任何保護堆分配函數之前,必須先調用sodium_init()函數。

void *sodium_malloc(size_t size);

sodium_malloc()函數返回一個指針,這個指針可以訪問大小連續的內存位元組。

分配的區域放置在頁邊界的末尾,緊隨其後的是保護頁。 因此,訪問該區域結尾處的內存將立即終止應用程序。

一個canary也放置在返回的指針之前。嘗試使用sodium_free()釋放分配的區域時,如果檢測到這個canary的修改,會導致應用程序立即終止。

在這個canary之前放置一個附加的保護頁面,以便當讀取到超過無關區域的末尾時,敏感數據不太可能被訪問。

分配的區域填充0xd0個位元組,以幫助捕獲由於初始化數據引起的錯誤。

此外,在該區域調用了sodium_mlock()以幫助避免將其交換到磁碟。在支持MAP_NOCORE或MADV_DONTDUMP的操作系統上,以這種方式分配的內存也不會成為核心轉儲的一部分。

如果分配的大小不是所需對齊位元組的倍數,返回的地址將不會對齊。

因此,為了確保正確地對齊,sodium_malloc()函數不應該和包裝或可變長度的結構一起使用,除非給與sodium_malloc()的大小被舍入。

libsodium使用的所有結構都可以安全地使用sodium_malloc()進行分配,唯一需要額外注意的是crypto_generichash_state,其大小需要舍入到64位元組的倍數。

分配0個位元組是一個有效的操作,並返回一個可以成功傳遞給sodium_free()的指針。

void *sodium_allocarray(size_t count, size_t size);

sodium_allocarray()函數返回一個指針,這個指針可以訪問size位元組大小的count對象。

它提供與sodium_malloc()相同的保證,當count * size超過SIZE_MAX時可以防止算術溢出。

void sodium_free(void *ptr);

sodium_free()函數解鎖並釋放使用sodium_malloc()或sodium_allocarray()分配的內存。

在此之前,檢查canary以檢測可能的緩衝區下溢,並在需要時終止該過程。

在釋放之前,sodium_free()也會用零填充內存區域。

即使您以前使用sodium_mprotect_readonly()保護過該區域,也可以調用此函數; 保護將根據需要自動更改。

ptr可以為NULL,在這種情況下不執行任何操作。

int sodium_mprotect_noaccess(void *ptr);

sodium_mprotect_noaccess()函數使得使用sodium_malloc()或sodium_allocarray()分配的區域無法訪問。 它不能被讀取或寫入,但數據被保留。

此功能可用於使機密數據無法訪問,除了特定操作實際需要之外。

int sodium_mprotect_readonly(void *ptr);

sodium_mprotect_readonly()函數將使用sodium_malloc()或sodium_allocarray()分配的區域標記為只讀。

嘗試修改數據將導致進程終止。

int sodium_mprotect_readwrite(void *ptr);

在使用sodium_mprotect_readonly()或sodium_mprotect_noaccess()將區域置於保護的狀態之後,sodium_mprotect_readwrite()函數將使用sodium_malloc()或sodium_allocarray()分配的區域標記為可讀寫。

4 生成隨機數據

這個函數庫提供了一組函數來生成不可預知的數據,這適用於創建密鑰。

- 在Windows系統上,使用RtlGenRandom()函數

- 在OpenBSD和Bitrig上,使用arc4random()函數

- 在較新的Linux內核中,使用getrandom系統調用(從Sodium 1.0.3版本開始)

- 在其它類Linux系統上,使用/dev/urandom設備

- 如果上述選項都不能被安全地使用,Sodium也支持使用自定義實現。

4.1 用法

uint32_t randombytes_random(void);

這個randombytes_random()函數返回0到0xffffffff之間(閉區間)的一個不可預測的值。

uint32_t randombytes_uniform(const uint32_t upper_bound);

randombytes_uniform()函數返回0和upper_bound之間(開區間)的一個不可預測的值。與randombytes_random() % upper_bound不同,即使upper_bound不是2的冪次方,它也儘可能保證可能的輸出值是均勻分布的。

void randombytes_buf(void * const buf, const size_t size);

randombytes_buf()函數用不可預測的位元組序列填充從buf開始的size個位元組。

int randombytes_close(void);

這個函數釋放了偽隨機數生成器使用的全局資源。更具體地說,當/dev/urandom設備在被使用時,它會關閉描述符。顯然調用這個函數是幾乎不需要的。

void randombytes_stir(void);

如果它支持這種操作的話,randombytes_stir()函數會重新設置偽隨機生成器的種子。即使在調用了fork()後,調用這個函數也不需要使用默認的生成器,除非您使用了randombytes_close()函數來關閉/dev/urandom的描述符。

如果您使用了非默認實現(請參閱randombytes_set_implementation() 函數),

在調用了fork()後,必須再調用randombytes_stir()函數。

4.2 注意

如果上述討論的介面在VM中的一個應用程序中使用,並且VM恢復了快照,那麼上述的功能可能會產生相同的輸出。

======================================================================

後記

本翻譯工作難免會出現錯誤和不準確之處,歡迎大家指出並幫助我們改進,如果對翻譯有任何指正,請不吝郵件loccs at sjtu dot edu dot cn,感謝各位!!!


推薦閱讀:

為什麼比特幣地址不直接使用公鑰,而需要通過哈希生成?為什麼每次付款都應該設置找零地址?
密碼學大事件!研究人員公布第一例SHA-1哈希碰撞實例
對一堆文件中的每一個文件單獨加密,如果已知其中一些文件的明文和密文,是否會導致能推斷出密鑰?
求大觸們推薦一些比較系統的關於密碼加密解密的書籍?
存不存在一種匿名投票方式(計算機實現),滿足以下條件?

TAG:密码学 | 网络安全 |