密碼算法分類
- 對稱算法
- 非對稱算法
- 消息摘要(單向哈希)算法
- 這些算法作為加密函數框架的最底層,提供加密和解密的實際操作。這些函數可以在內核crypto文件夾下,相應的文件中找到。不過內核模塊不能直接調用這些函數,因為它們并沒有export。
- 內核提供一個統一的框架,來管理這些算法。加密算法通過crypto_register_alg()和crypto_unregister_alg()注冊。
對稱算法
- 頭文件
// 包含對稱密碼(symmetric key)算法API(該文件內部包含了linux/crypto.h所以無需再次引入linux/crypto.h)
#include <crypto/skcipher.h>
// 對稱密碼API需要使用的struct scatterlist結構(用來保存輸入/輸出緩沖區)
#include <linux/scatterlist.h>
- Linux內核scatterlist API介紹 - AlanTu - 博客園? ??
需要使用的重要數據結構 和? 函數
// 用來保存加/解密緩沖區的結構
struct scatterlist sg;
// 加密算法對象(上下文)
struct crypto_skcipher *tfm;
// 異步操作請求對象
struct skcipher_request *req;
// 異步操作等待對象
struct crypto_wait wait;// 該函數用于等待異步加密操作完成,通常需要將crypto_skcipher_decrypt的返回值作為err參數傳入
int crypto_wait_req(int err, struct crypto_wait *wait);// 該函數作為異步加密操作的回調函數傳入req對象中,在加密完成后被調用
void crypto_req_done(struct crypto_async_request *req, int err);// 該函數根據密碼算法名稱分配密碼算法對象,內核支持的密碼算法可以在/proc/crypto文件中查看
struct crypto_skcipher *crypto_alloc_skcipher(const char *alg_name,u32 type, u32 mask);struct skcipher_request *skcipher_request_alloc(struct crypto_skcipher *tfm, gfp_t gfp);// init function
// 初始化scatterlist時需要使用kmalloc分配的內存,如果使用vmalloc分配的內存會導致內存頁分配錯誤,目前還不知道具體原因
void sg_init_one(struct scatterlist *sg,const void *buf, unsigned int buflen);// 設置異步調用的回調函數,這里data是一個自定數據結構,其會被傳給回調函數。
void skcipher_request_set_callback(struct skcipher_request *req,u32 flags,crypto_completion_t compl,void *data);// 內核的對稱加密API可以“原地”加密,即加解密共用相同的緩沖區,因此這里的src和dst可以設置為同一個
void skcipher_request_set_crypt(struct skcipher_request *req,struct scatterlist *src, struct scatterlist *dst,unsigned int cryptlen, void *iv);// 設置密鑰和密鑰長度,密碼長度單位為字節
int crypto_skcipher_setkey(struct crypto_skcipher *tfm,const u8 *key, unsigned int keylen);// 解密,將返回值傳入crypto_wait_req函數來等待可能的異步操作完成
int crypto_skcipher_decrypt(struct skcipher_request *req);
// 加密
int crypto_skcipher_encrypt(struct skcipher_request *req)// 釋放資源
void crypto_free_skcipher(struct crypto_skcipher *tfm);
void skcipher_request_free(struct skcipher_request *req);
scatterlist.h
- scatterlist.h - include/linux/scatterlist.h - Linux source code (v5.15.11) - Bootlin
-
scatterlist類型數據可以認為是這些密碼算法操縱的數據對象。
crypto_skcipher
- Linux Kernel Crypto API — The Linux Kernel 4.7 documentation
-
crypto_tfm類型指針tfm可以理解為指代了一個算法對象
操作流程
- 內核加密編程接口 - 老僧非是愛花紅 - 博客園
?PPT中對內核加密算法的使用總結得很詳細。總的來說,在內核態使用加密算法的過程分為以下幾步:
- 分配tranform對象? ?也就是具體的算法
- 分配request對象? 異步操作等待對象
- 設置上下文 如加密密鑰/驗簽公鑰,填充數據源,給scatterlist設置緩沖區,給異步請求對象設置回調函數/初始化向量等,給密碼算法對象設置密鑰
- 完成加密/解密/摘要/驗簽
- 釋放transform,request等對象
?例子? SM3
- struct crypto_ahash * atfm = crypto_alloc_ahash("sm3-generic",0,0);
- struct ahash_request *req = ahash_request_alloc(atfm,GFP_KERNEL);
- struct crypto_wait wait;crypto_init_wait(&wait);
- ahash_request_set_callback(req,CRYPTO_TFM_REQ_MAY_BACKLOG,crypto_req_done,&wait);
- struct scatterlist sg;sg_init_one(&sg,data,size);
- ahash_request_set_crypt(req,&sg,result,size);
- ret=crypto_ahash_digest(req);
- ahash_request_free(req);? ? ?
- crypto_free_ahash(atfm);
相關數據結構關聯
- 上圖粗略描述了靜態算法構造transform、構造request的過程:分配空間/初始化函數指針/建立tfm、req和alg之間的關聯。
- 建立關聯的過程被各種看似復雜的對象之間的包含/被包含的關系掩蓋了簡單的實質,之所以實現得這么復雜,是為了未來能靈活地對加密模塊進行擴展,閱讀這部分代碼時我們不要被這種復雜的假象所嚇倒。
- 最終干活的為transform,transform中保存的相關函數指針是在構造transform時從對應的alg實例中拷貝過來的,對應上圖中棕色的一部分。
?例子
int linux_kernel_crypto_decrypt(void* data_in_out, int data_len, void* key, int key_len, void* iv, int iv_len) {struct crypto_skcipher* cipher;struct skcipher_request* req;struct crypto_wait wait;struct scatterlist sg;size_t block_size;int ret;// 分配算法對象,支持的算法可以在/proc/crypto文件中查看cipher = crypto_alloc_skcipher("cbc(aes)", 0, 0);if (IS_ERR(cipher)) {printk("fail to allocate cipher\n");return -1;}// skcipher api不支持填充,所以加/解密數據需要為加密塊的整數倍block_size = crypto_skcipher_blocksize(cipher);if (data_len % block_size != 0) {printk("data len not aligned");return -1;}// 分配req對象req = skcipher_request_alloc(cipher, GFP_KERNEL);if (IS_ERR(req)) {printk("fail to allocate req\n");return -1;}sg_init_one(&sg, data_in_out, data_len);skcipher_request_set_callback(req, CRYPTO_TFM_REQ_MAY_BACKLOG, crypto_req_done, &wait);skcipher_request_set_crypt(req, &sg, &sg, data_len, iv);ret = crypto_skcipher_setkey(cipher, key, key_len);if ( 0 != ret) {printk("fail to set key, error %d\n", ret);return -1;}// 執行解密操作ret = crypto_wait_req(crypto_skcipher_decrypt(req), &wait); if (0 != ret) {printk("decryption error %d\n", ret);return -1;}// 釋放資源crypto_free_skcipher(cipher);skcipher_request_free(req);printk("decryption finished");return 0;
}
相關學習
struct shash_desc {struct crypto_shash *tfm;u32 flags;void *__ctx[] CRYPTO_MINALIGN_ATTR;
};
- tfm: 加密handler
- ctx: 空數組的首地址,相當于匯編里的一個標號,指向結構體的后一個字節。
這個結構體可能會跟一段buffer一起被申請,而ctx就相當于這段buffer的首地址,所以為了防止訪問buffer時出現對齊錯誤,需要給ctx加上屬性CRYPTO_MINALIGN_ATTR - CRYPTO_MINALIGN_ATTR的含義是通過可能的字節填充,使得ctx是以最大的對齊標準對齊的,防止出現對齊錯誤。例如在我的機器上,CRYPTO_MINALIGN_ATTR的含義是強制以64位對齊。
static struct sdesc *init_sdesc(struct crypto_shash *alg)
{struct sdesc *sdesc;int size;size = sizeof(struct shash_desc) + crypto_shash_descsize(alg);sdesc = kmalloc(size, GFP_KERNEL);if (!sdesc)return ERR_PTR(-ENOMEM);sdesc->shash.tfm = alg;return sdesc;
}
- crypto_shash_descsize(alg): 得到這個加密算法需要的buffer大小。
- struct shash_desc的大小加上buffer大小就是總共需要申請的內存大小size。
- kmalloc: 申請小于一頁的內存。其中GFP_KERNEL表示申請正常的內核RAM,可以睡眠。
- kmalloc
- ERR_PTR: 把錯誤碼變成指針。解讀PTR_ERR,ERR_PTR,IS_ERR_窗外云天的專欄-CSDN博客
適用場景
- Kernel Crypto框架_內核工匠-CSDN博客_內核crypto
- Kernel crypto主要用于kernel層的安全特性實現,但在user-space也可以通過系統調用的方式來使用它;因為在Linux-2.6.38中已經通過socket (addr family: AF_ALG)方式導出接口到user-space.
- 在開發時如要快速確認kernel中是否支持某種算法,可以cat /proc/crypto 查看
- ?name代表算法名稱,hmac是對應的模式(抽象成template)
- priority代表算法的優先級(在相同名稱下,數字越大代表優先級越高,默認使用高優先級的算法);selftest代表開機算法自檢結果;type指算法類型;async指異步方式調用;blocksize指最小單個數據處理塊大小;min keysize和max keysize指算法的最小/最大密鑰長度;ivsize指算法的IV初始向量長度。
- *selftest之后的所有字段其實都是crypto_type->show()所提供的,后面會提及
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層的算法能力提供給用戶空間。
- 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
?結構體介紹
- algapi.h - include/crypto/algapi.h - Linux source code (v5.15.11) - Bootlin
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用于釋放算法實例;
struct crypto_alg?
?
- ?crypto.h - include/linux/crypto.h - Linux source code (v5.15.11) - Bootlin
- 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中注銷的相關操作。
struct crypto_instance
- ?algapi.h - include/crypto/algapi.h - Linux source code (v5.15.11) - Bootlin
- 這個結構體是代表kernel通過template動態創建的算法實例,并且會與crypto_template相關聯,可以看到這里的alg并不是個指針。它是通過template->alloc()創建的,創建的同時,會將算法name初始化。
- __ctx:當前只指向crypto_spawn,我個人理解可能是架構設計者考慮到未來擴展性,就將crypto_spawn與crypto_instance拆分開來了。
struct crypto_spawn
通過模板動態生成的算法實例的一部分。
- list:添加到crypto_alg->cra_users鏈表中。
- frontend:見下文。
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。
struct crypto_tfm
- 具體算法處理(transformation)上下文的實例,里面會將此次算法上下文的key,IV等信息設置到__crt_ctx中。
- crt_u:算法的operation,kernel會在__crypto_alloc_tfm接口中關聯到crypto_type或xxx_alg中的實現方法。
例子講解
- 通過用例介紹crypto子系統邏輯
- 在文件系統加密(FBE)中通過kernel crypto做密鑰派生。
- 背景:在Android 7.0時,引入了文件加密功能,所謂文件加密,即每個文件都用不同的key對文件進行加密。
- 原理:密鑰派生中,使用了crypto中的ecb(aes)算法通過類密鑰及inode.nonce派生出每個文件的密鑰。
- 具體實現在kernel/fs/crypto/keyinfo.c
- crypto.c - fs/crypto/crypto.c - Linux source code (v5.15.11) - Bootlin
- 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并注冊到鏈表當中。
- 在獲取到了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請求被處理完成。因此不能在中斷上下文中使用。
?算法自檢
- 出于安全性考慮,FIPS等相關標準要求在系統開機時必須做算法正確性自檢,如果自檢失敗,則停止系統的啟動;因此算法自檢這部分自然也在框架中實現了。(源碼位于:kernel/crypto/testmgr.c)
- Kernel中的算法自檢為靜態自檢,即給定輸入參數,及正確結果,如果算法計算出來的結果與正確結果不匹配,則自檢失敗。
- 算法自檢的時間點固定為每個crypto_alg注冊的時候,具體流程如下圖2.4所示:
Linux加密框架設計和實現
- Linux加密框架設計與實現(第一部份) - 內核源碼-Chinaunix?
內核 crypto介紹
- Linux kernel crypto的介紹_代碼改變世界-CSDN博客_crypto linux?
af_alg是linux kernel crypto算法接口
- 實現了底層算法的調用(skcipher、aead、hash、rng),并且將這些接口export出去,給linux kernel其它模塊使用(如tcrypt.c使用);
- 將這些接口注冊sock_register, 用戶程序通過sock通信來調用這些底層接口
- 在linux kernel中,僅支下四種crypto算法:
- algif_skcipher 對稱加解密算法
- algif_aead 也算一種對稱的加解密算法,具體介紹參見什么是AEAD加密
- algif_hash 數字摘要算法
- algif_rng 隨機數產生
- 在Linux kernel的module_init階段會將algif_type_skcipher、algif_type_aead、algif_type_hash、algif_type_rng 四種算法注冊.也就是添加到af_alg維護的alg_types鏈表種. alg_types鏈表種僅有這四個數據.
- 在userspace通過netlink調用了,kernel種的af_alg模塊收到消息后, 根據上層傳來的算法種類名字來選擇走哪一個結構體(alg_type_xxx)的ops函數
sendmsg/recvmsg如何調用到底層encrypt/decrypt
- 以skcipher為例, 在userspace調用send()和recive()函數,對應的底層調用recvmsg和sendmsg函數。先看skcipher_recvmsg()函數,接受數據然后再調用encrypt/decrypt處理數據
-
而skcipher_sendmsg()函數就是將處理后的數據,在發送到sock端.
static int skcipher_recvmsg(struct socket *sock, struct msghdr *msg,size_t ignored, int flags)
{return (msg->msg_iocb && !is_sync_kiocb(msg->msg_iocb)) ?skcipher_recvmsg_async(sock, msg, flags) :skcipher_recvmsg_sync(sock, msg, flags);
}
算法的底層實現(以為aes/hash為例)
- 在linux crypto底層,實現aes/hash的算法有四種方式
- (1)cpu的純軟實現,使用cpu的ALU,x0-x30等寄存器,加加減減的計算
- (2)ARM-CE,就是The Armv8 Cryptographic Extension了,調用arm-ce的指令和寄存器,進行加加減減計算
- (3)ARM-NEON : 調用arm neon指令(128bit的寄存器v0-v31),進行加加減減計算
- (4)SOC crypto engine的實現
參考鏈接
- Linux source code (v5.15.11) - Bootlin? ? ? ? ?Linux內核 在線源代碼 查詢函數定義和使用
- Linux Kernel(Android) 加密算法總結(一)(cipher、compress、digest)_萬能的終端和網絡-程序員宅基地 - 程序員宅基地
- 沒有調用crypto_skcipher_encrypt回調 | 碼農俱樂部 - Golang中國 - Go語言中文社區
- Linux內核加密接口分析_一些筆記雜談-CSDN博客_linux內核加密接口
- Linux內核中使用crypto進行sha1方法_Rain的博客-CSDN博客
- Linux內核模塊開發_j5856004的博客-CSDN博客
- Linux驅動開發1-內核入門之hello模塊_zusi_csdn-CSDN博客_linux內核驅動開發
- linux crypto_u014044624的博客-CSDN博客
- Linux內核crypto子系統學習筆記_scarecrow_byr的專欄-CSDN博客_crypto驅動