隨着數字時代的發展,每天都有海量的數據產生,並且用戶也越來越重視個人隱私數據的安全,從某種意義上講,用戶個人數據的價值正逐步高於設備本身。實現數據安全保護的基礎是【密鑰 + 加密算法】;對於加密算法,kernel其實早在linux-2.5.45版本中就引入了crypto基礎能力。本篇文章主要講關於kernel crypto算法框架,以及結合它在文件系統加密這一場景中的應用,分析內部的實現細節,以便讀者對crypto框架有相關的認識,並能基於它做開發。
本篇文章的所有代碼都是圍繞Linux-4.19,以OOP思想進行闡述,大家可以結合源碼進行閱讀。
為方便理解,我們約定一些術語:
-
數據轉換(transformation/TFM):不管是對數據做加/解密,還是做hash,都定義成是對數據所做的轉換操作。
-
算法:本文當中的算法並不特指加/解密算法。
一、Crypto子系統簡介
1.功能
Kernel crypto是內核實現的一套通用crypto算法框架,是一個獨立的子系統,源碼在kernel/crypto下,它實現了對算法的統一管理,並提供出統一的數據處理接口給其他子系統使用;因此基於這套框架,我們不僅可以使用kernel已有的crypto算法對數據做轉換,還能自行擴展添加算法。
Kernel crypto 當前實現了對稱加解密,非對稱加解密,認證加解密,hash,Hmac,DRBG偽隨機數生成算法和壓縮算法。
2.適用場景
Kernel crypto主要用於kernel層的安全特性實現,但在user-space也可以通過系統調用的方式來使用它;因為在Linux-2.6.38中已經通過socket (addr family: AF_ALG)方式導出接口到user-space.
在開發時如要快速確認kernel中是否支持某種算法,可以cat /proc/crypto 查看,如下圖1.1。
圖1.1 kernel支持的加密算法
name代表算法名稱,hmac是對應的模式(抽象成template);priority代表算法的優先級(在相同名稱下,數字越大代表優先級越高,默認使用高優先級的算法);selftest代表開機算法自檢結果;type指算法類型;async指異步方式調用;blocksize指最小單個數據處理塊大小;min keysize和max keysize指算法的最小/最大密鑰長度;ivsize指算法的IV初始向量長度。
*selftest之后的所有字段其實都是crypto_type->show()所提供的,后面會提及。
3.整體架構
下面通過一張圖來展示crypto的整體框架。
圖1.2 kernel crypto框架
crypto core是最基本骨架 ,它提供crypto的核心組件(包括crypto_alg,crypto_template的管理,cryptd內核線程等);基於crypto core,內核實現了8類常用的算法,DRBG偽隨機數算法,Hash算法,SKCIPHER對稱加解密算法,AKCIPHER非對稱加解密算法,AEAD認證加密算法,HMAC算法,COMPRESS壓縮算法,KPP密鑰協商算法。
一些用於secure的硬件模塊(如hw_rng硬件隨機數產生器,qce硬加密模塊)的驅動程序,會通過crypto core提供的算法注冊接口(crypto_register_alg)將其注冊到crypto子系統中,並且在注冊時會對算法做靜態正確性自檢,並在/proc/crypto中的selftest中呈現到userspace。除了注冊到crypto子系統以外,驅動也可以通過VFS以設置節點形式提供給用戶空間使用(如/dev/qce,/dev/hw_rng)。
Crypto core通過socket方式,將kernel層的算法能力提供給用戶空間。
二、Crypto核心數據結構及邏輯實現
算法本質上就是一些對數據進行邏輯處理的函數,在應用層的實現普遍是直接實現一個加解密接口,應用程序直接調用,對使用者來說極為方便。但在kernel中,使用起來會較為復雜些,因為它需要考慮到算法的易擴展性,通用性,易維護性等,因此對算法做了高度的抽象化;所以要理解kernel crypto的設計思想,必須要理解它的幾個核心struct。
1.核心數據結構
在軟件界有句話寫的很實在:”軟件=文件+程序,程序=數據結構+算法”;由此能看出弄清數據結構的重要性。
Kernel crypto中基本所有操作都是圍繞着幾個核心數據結構展開:struct crypto_alg,struct crypto_template,struct crypto_instance,struct crypto_tfm,struct crypto_type。其他算法都可以基於它們做擴展。例如struct skcipher_alg,struct shash_alg都是繼承自struct crypto_alg,見下圖2.1:
圖2.1 crypto數據結構uml類圖
下面對這6個結構體做相關說明:
1)struct crypto_template
算法模板,一般在module_init時通過調用crypto_register_template接口注冊到crypto_template_list鏈表中。
在算法加密中,分塊加密模式分為很多種,以對稱加解密為例,有CBC,ECB,GCM,CTR,XTS,而這些加密模式適用於所有的對稱加密算法,如AES,DES;因此kernel就將加密模式抽象成模板,在開發新的算法時只需要實現單個block的數據處理(加密,hmac等);在申請使用算法時,我們通過算法名來組合出相應的算法(kernel會將組合出來的算法動態注冊到crypto子系統),格式為template(single block cipher),例如cbc(aes),ecb(des)。
-
list用於模塊的crypto_template_list鏈表管理;
-
instance用於管理當前模板下所有的crypto_instance;
-
alloc接口用於申請算法實例;
-
free用於釋放算法實例;
2)struct crypto_alg
crypto_alg是個基類,任何算法都可以基於它派生出衍生類;每個算法都對應着一個struct crypto_alg實例,一般在module_init中調用crypto_register_alg接口將具體的crypto_alg對象添加到crypto_alg_list鏈表中。這里有一個很重要的數據成員cra_u,因為它體現了kernel crypto架構設計者的設計思想:它將四種比較常用的算法類型的處理方式抽象到基類當中,即如果你要添加的算法為這4類,就只需要實現這4類算法所對應的方法,如果不是這4類當中,就需要在基類上做派生,實現特定的crypto_type。
-
cra_list:是用作鏈表管理
-
cra_users:此算法被引用的所有crypto_spawn實例鏈表。
-
cra_blocksize:是單個處理數據塊大小
-
cra_ctxsize:為transformation context大小
-
cra_alignmask:指待處理數據buffer的對齊要求
-
cra_priority:是當前算法優先級
-
cra_refcnt:為當前算法的引用計數
-
cra_name和cra_driver_name:分別指代算法名及驅動名
-
cra_type:指算法類型;cra_u將四大類算法類型進行了統一。
-
cra_init:是用於每次數據操作上下文前的初始化,比如在硬件加密中,會實現此接口對相關寄存器做初始化;cra_exit則與前者相反。
-
cra_destroy:是用於crypto在kernel中注銷的相關操作。
3)struct crypto_instance
這個結構體是代表kernel通過template動態創建的算法實例,並且會與crypto_template相關聯,可以看到這里的alg並不是個指針。它是通過template->alloc()創建的,創建的同時,會將算法name初始化。
-
__ctx:當前只指向crypto_spawn,我個人理解可能是架構設計者考慮到未來擴展性,就將crypto_spawn與crypto_instance拆分開來了。
4)struct crypto_spawn
通過模板動態生成的算法實例的一部分。
-
list:添加到crypto_alg->cra_users鏈表中。
-
frontend:見下文。
5)struct crypto_type
crypto_type就是用於重載crypto_alg中的cra_u中的各個類中的成員函數,當通過crypto_alloc_base,crypto_create_tfm等接口申請相應的crypto的TFM上下文時,若有傳入crypto_type參數,TFM優先使用crypto_type中的init_tfm成員函數去初始化crypto_tfm衍生類的操作方法。
-
ctxsize:獲取當前算法類型TFM上下文大小(crypto_tfm+crypto_tfm.__crt_ctx)
-
extsize:獲取當前算法類型TFM上下文大小(即crypto_tfm衍生類的大小)。
-
init:一般為空(功能與init_tfm類似,通常在后者中初始化TFM)。
-
init_tfm:顧名思義,初始化TFM。
-
show:呈現當前算法類型的基本信息,/proc/crypto后半段信息就是從這獲取的。
-
free:釋放crypto_instance。
6)struct crypto_tfm
具體算法處理(transformation)上下文的實例,里面會將此次算法上下文的key,IV等信息設置到__crt_ctx中。
-
crt_u:算法的operation,kernel會在__crypto_alloc_tfm接口中關聯到crypto_type或xxx_alg中的實現方法。
2.通過用例介紹crypto子系統邏輯
那我們在使用kernel中的算法時,框架內部是如何做處理的呢?下面通過一個例子來說明這個問題。在文件系統加密(FBE)中通過kernel crypto做密鑰派生。
背景:在Android 7.0時,引入了文件加密功能,所謂文件加密,即每個文件都用不同的key對文件進行加密。
原理:密鑰派生中,使用了crypto中的ecb(aes)算法通過類密鑰及inode.nonce派生出每個文件的密鑰。
具體實現在kernel/fs/crypto/keyinfo.c,如下圖2.2所示:
圖2.2 crypto在FBE中的應用
1)申請“ecb(aes)”算法的tfm上下文。這里會涉及到“算法動態注冊”,即如果在crypto_alg_list鏈表中沒有找到name為”ecb(aes)”的crypto_alg對象,那crypto子系統會通過一個名為”cryptomgr_probe”的內核線程查找到name為“ecb”的crypto_template對象,以及查找到name為”aes”的crypto_alg對象,動態創建出一個name為“ecb(aes)”的crypto_alg並注冊到鏈表當中。調用流程如圖2.3
圖2.3 算法動態注冊流程
在獲取到了crypto_alg后,就會申請crypto_tfm,並用crypto_alg->cra_init()或crypto_type->init_tfm()對其進行初始化(主要是當前算法的各個函數指針)。
2) 在tfm上下文中申請一個數據處理請求(req)。一個tfm中,可以做多次數據加解密。這里只是申請內存,並關聯到tfm的操作。
3) 設置密鑰到tfm->__crt_ctx中。
4) 把待加密數據信息放入req當中。
5)以異步方式調用crypto_skcipher_encrypt對req做加密處理,線程在此會block一段時間,直到req請求被處理完成。因此不能在中斷上下文中使用。
3.算法自檢
出於安全性考慮,FIPS等相關標准要求在系統開機時必須做算法正確性自檢,如果自檢失敗,則停止系統的啟動;因此算法自檢這部分自然也在框架中實現了。(源碼位於:kernel/crypto/testmgr.c)
Kernel中的算法自檢為靜態自檢,即給定輸入參數,及正確結果,如果算法計算出來的結果與正確結果不匹配,則自檢失敗。
算法自檢的時間點固定為每個crypto_alg注冊的時候,具體流程如下圖2.4所示:
圖2.4 算法自檢流程
三、總結
Kernel crypto在設計時進行了高度的抽象化,本篇文章主要通過一些核心數據結構,並采用OOP角度來揭示各個struct的功能及之間的關系,以便讀者能了解架構設計者的初衷。由於篇幅所限,本文未對kernel中已支持的所有算法的使用方式做介紹,這部分讀者可直接參考算法自檢中的代碼實現。
參考文獻
[1] Kernel Crypto API Architecture:https://kernel.org/doc/html/crypto/architecture.html
[2] Linux kernel 4.19源代碼

“內核工匠”微信公眾號
Linux 內核黑科技 | 技術文章 | 精選教程