說明:
在默認情況下,本文講述的都是ARMV8-aarch64架構,linux kernel 5.14
思考:
1、Linux Kernel中支持哪些密碼學算法?分別都是怎么實現的?哪些是C語言實現?哪些是Neon指令實現?哪些是ARM Cryptography Extension硬件實現?這些不同的實現方式,他們之間的關系是怎樣的?并列關系?多選一?多選多?
2、應用程序的密碼學算法一般又是怎樣實現的?應用程序的密碼學算法實現,是否依賴Kernel底層的密碼學算法?
3、應用程序是如何調用到Kernel底層的密碼學算法?Kernel底層的其它模塊,如何調用密碼學算法?
4、如何在Kernel底層增加一種密碼學算法的實現?
5、Kernel的其它模塊中,有哪些需要使用密碼學算法的場景?
本文術語定義:算法 :算法的種類,如對稱密碼算法、非對稱密碼算法...算法實現 :具體的某一類算法,如aes-cbc、aes-ebc、sm4-cbc、twofish-ecb...
目錄
1、密碼學基礎知識
2、Kernel密碼學算法的軟件框架和接口模型
2.1、Userspace對底層密碼算法的訪問2.2、Kernelspace對底層密碼算法的訪問2.3、增加一個算法實現3、kernel中實現的算法實現
4、crypto engine的實現
5、代碼導讀
1、密碼學基礎知識
基本概念,如下請自行學習和理解:
-
對稱密碼
-
非對稱密碼
-
數字摘要
-
隨機數
2、Kernel密碼學算法的軟件框架和接口模型
Linux Kernel系統中實現了很多算法,這些算法被統一歸納為:對稱密碼算法、數字摘要算法、隨機數算法、認證加密算法、非對稱密碼算法等,并在Kernel層提供了統一操作的接口,供kernel其他模塊調用。部分算法又被封裝到了網絡層,開放暴露給Userspace。其具體的結構/接口模型如下所示:
2.1、Userspace對底層密碼算法的訪問
Userspace通過netlink接口方式( PF_ALG
)調用到底層算法的實現
在Userspace,需指定socket接口 PF_ALG
,需指定算法名稱(如skcipher)、需指定具體調用的"算法實現"(如aes-cbc),這樣命令傳輸到Kernel層,就能根據這些信息跳轉到響應的算法實現層。注意akcipher算法沒有暴露給網絡層,也就沒有開放給Userspace了,所以在User程序中,是無法調用Kernel層的非對稱密碼算法的。
如下是一個Userspace程序調用kernel底層算法的示例:
(1)建立一個socket會話的流程:
socket(AF_ALG,...)
bind()
setsockopt
accept
sendmsg
recvmsg
(2)相關代碼
static int linux_af_alg_socket(const char *type, const char *name)
{
struct sockaddr_alg sa;
int s;
s = socket(AF_ALG, SOCK_SEQPACKET, 0);
if (s < 0) {
LogErr("%s: Failed to open AF_ALG socket: %s ",
__func__, strerror(errno));
return -1;
}
os_memset(&sa, 0, sizeof(sa));
sa.salg_family = AF_ALG;
os_strlcpy((char *) sa.salg_type, type, sizeof(sa.salg_type));
os_strlcpy((char *) sa.salg_name, name, sizeof(sa.salg_name));
if (bind(s, (struct sockaddr *) &sa, sizeof(sa)) < 0) {
LogErr("%s: Failed to bind AF_ALG socket(%s,%s): %s ",__func__, (char *) sa.salg_type, (char *) sa.salg_name, strerror(errno));
close(s);
return -1;
}
return s;
}
static struct linux_af_alg_skcipher *linux_af_alg_skcipher(const char *alg, const u8 *key, size_t key_len)
{
struct linux_af_alg_skcipher *skcipher;
skcipher = os_zalloc(sizeof(*skcipher));
if (!skcipher)
goto fail;
skcipher->t = -1;
skcipher->s = linux_af_alg_socket(TYPE_NAME, alg);
if (skcipher->s < 0)
goto fail;
if (setsockopt(skcipher->s, SOL_ALG, ALG_SET_KEY, key, key_len) < 0) {
LogErr("%s: setsockopt(ALG_SET_KEY) failed: %s ",
__func__, strerror(errno));
goto fail;
}
skcipher->t = accept(skcipher->s, NULL, NULL);
if (skcipher->t < 0) {
LogErr("%s: accept on AF_ALG socket failed: %s ",
__func__, strerror(errno));
goto fail;
}
return skcipher;
fail:
linux_af_alg_skcipher_deinit(skcipher);
return NULL;
}
static int aes_128_cbc_oper(char *alg_name, const u8 *key,size_t key_len, int enc, const u8 *iv, u8 *data, size_t data_len)
{
struct linux_af_alg_skcipher *skcipher;
char buf[100];
struct iovec io[1];
struct msghdr msg;
struct cmsghdr *hdr;
ssize_t ret;
u32 *op;
struct af_alg_iv *alg_iv;
size_t iv_len = AES_BLOCK_SIZE;
skcipher = linux_af_alg_skcipher(alg_name, key, key_len);//alg_name = "__cbc-aes-asr-ce"
if (!skcipher)
return -1;
io[0].iov_base = (void *) data;
io[0].iov_len = data_len;
os_memset(&msg, 0, sizeof(msg));
os_memset(buf, 0, sizeof(buf));
msg.msg_control = buf;
msg.msg_controllen = CMSG_SPACE(sizeof(u32)) +
CMSG_SPACE(sizeof(*alg_iv) + iv_len);
msg.msg_iov = io;
msg.msg_iovlen = 1;
hdr = CMSG_FIRSTHDR(&msg);
hdr->cmsg_level = SOL_ALG;
hdr->cmsg_type = ALG_SET_OP;
hdr->cmsg_len = CMSG_LEN(sizeof(u32));
op = (u32 *) CMSG_DATA(hdr);
*op = enc ? ALG_OP_ENCRYPT : ALG_OP_DECRYPT;
hdr = CMSG_NXTHDR(&msg, hdr);
hdr->cmsg_level = SOL_ALG;
hdr->cmsg_type = ALG_SET_IV;
hdr->cmsg_len = CMSG_SPACE(sizeof(*alg_iv) + iv_len);
alg_iv = (struct af_alg_iv *) CMSG_DATA(hdr);
if(NULL != iv){
alg_iv->ivlen = iv_len;
os_memcpy(alg_iv->iv, iv, iv_len);
}else
{
alg_iv->ivlen = 0;
}
ret = sendmsg(skcipher->t, &msg, 0);
if (ret < 0) {
LogErr("%s: sendmsg failed: %s ",
__func__, strerror(errno));
linux_af_alg_skcipher_deinit(skcipher);
return -1;
}
ret = recvmsg(skcipher->t, &msg, 0);
if (ret < 0) {
LogErr("%s: recvmsg failed: %s ",
__func__, strerror(errno));
linux_af_alg_skcipher_deinit(skcipher);
return -1;
}
if ((size_t) ret < data_len) {
LogErr(
"%s: recvmsg not return full data (%d/%d) ",
__func__, (int) ret, (int) data_len);
linux_af_alg_skcipher_deinit(skcipher);
return -1;
}
//s_to_binary(data,data_len);
linux_af_alg_skcipher_deinit(skcipher);
return 0;
}
2.2、Kernelspace對底層密碼算法的訪問
Kernel程序對底層算法的調用采用函數直接調用的方式。流程為:kernel程序--->算法中間層--->算法實現層
. 算法中間層
就是暴露給kernel其它模塊的API函數。
如下是一個kernel中調用底層算法的一個示例(因skcipher為例):
static int test_skcipher(void)
{
struct crypto_skcipher *tfm = NULL;
struct skcipher_request *req = NULL;
u8 *data = NULL;
const size_t datasize = 512; /* data size in bytes */
struct scatterlist sg;
DECLARE_CRYPTO_WAIT(wait);
u8 iv[16]; /* AES-256-XTS takes a 16-byte IV */
u8 key[64]; /* AES-256-XTS takes a 64-byte key */
int err;
/*
* Allocate a tfm (a transformation object) and set the key.
*
* In real-world use, a tfm and key are typically used for many
* encryption/decryption operations. But in this example, we'll just do a
* single encryption operation with it (which is not very efficient).
*/
tfm = crypto_alloc_skcipher("xts(aes)", 0, 0);
if (IS_ERR(tfm)) {
pr_err("Error allocating xts(aes) handle: %ld ", PTR_ERR(tfm));
return PTR_ERR(tfm);
}
get_random_bytes(key, sizeof(key));
err = crypto_skcipher_setkey(tfm, key, sizeof(key));
if (err) {
pr_err("Error setting key: %d ", err);
goto out;
}
/* Allocate a request object */
req = skcipher_request_alloc(tfm, GFP_KERNEL);
if (!req) {
err = -ENOMEM;
goto out;
}
/* Prepare the input data */
data = kmalloc(datasize, GFP_KERNEL);
if (!data) {
err = -ENOMEM;
goto out;
}
get_random_bytes(data, datasize);
/* Initialize the IV */
get_random_bytes(iv, sizeof(iv));
/*
* Encrypt the data in-place.
*
* For simplicity, in this example we wait for the request to complete
* before proceeding, even if the underlying implementation is asynchronous.
*
* To decrypt instead of encrypt, just change crypto_skcipher_encrypt() to
* crypto_skcipher_decrypt().
*/
sg_init_one(&sg, data, datasize);
skcipher_request_set_callback(req, CRYPTO_TFM_REQ_MAY_BACKLOG |
CRYPTO_TFM_REQ_MAY_SLEEP,
crypto_req_done, &wait);
skcipher_request_set_crypt(req, &sg, &sg, datasize, iv);
err = crypto_wait_req(crypto_skcipher_encrypt(req), &wait);
if (err) {
pr_err("Error encrypting data: %d ", err);
goto out;
}
pr_debug("Encryption was successful ");
out:
crypto_free_skcipher(tfm);
skcipher_request_free(req);
kfree(data);
return err;
}
2.3、增加一個算法實現
增加一個"算法的實現" 只需要:
-
定義一個該算法的結構體變量并初始化,其實就是實現其中的成員函數
-
將該算法實現注冊到系統中。
結構體的定義并初始化:
static struct skcipher_alg aes_algs[] = {
{
.base.cra_name = "__ecb(aes)",
.base.cra_driver_name = "__ecb-aes-neonbs",
.base.cra_priority = 250,
.base.cra_blocksize = AES_BLOCK_SIZE,
.base.cra_ctxsize = sizeof(struct aesbs_ctx),
.base.cra_module = THIS_MODULE,
.base.cra_flags = CRYPTO_ALG_INTERNAL,
.min_keysize = AES_MIN_KEY_SIZE,
.max_keysize = AES_MAX_KEY_SIZE,
.walksize = 8 * AES_BLOCK_SIZE,
.setkey = aesbs_setkey,
.encrypt = ecb_encrypt,
.decrypt = ecb_decrypt,
},
{
.base.cra_name = "__cbc(aes)",
.base.cra_driver_name = "__cbc-aes-neonbs",
.base.cra_priority = 250,
.base.cra_blocksize = AES_BLOCK_SIZE,
.base.cra_ctxsize = sizeof(struct aesbs_cbc_ctx),
.base.cra_module = THIS_MODULE,
.base.cra_flags = CRYPTO_ALG_INTERNAL,
.min_keysize = AES_MIN_KEY_SIZE,
.max_keysize = AES_MAX_KEY_SIZE,
.walksize = 8 * AES_BLOCK_SIZE,
.ivsize = AES_BLOCK_SIZE,
.setkey = aesbs_cbc_setkey,
.encrypt = cbc_encrypt,
.decrypt = cbc_decrypt,
}
};
成員函數的實現,例如:
static int ecb_encrypt(struct skcipher_request *req)
{
return __ecb_crypt(req, aesbs_ecb_encrypt);
}
將該算法實現注冊到系統中:
小小總結一下, 如果您要增加一個算法實現,那么您就是需要實現定義如下結構體,并調用static int __init aes_init(void)
{
...
err = crypto_register_skciphers(aes_algs, ARRAY_SIZE(aes_algs));
...
}
module_init(aes_init);
crypto_register_xxx()
注冊到kernel系統中:- skcipher_alg
- akcipher_alg
- ahash_alg
- rng_alg
- aead_alg
3、kernel中實現的算法實現
思考:
對稱密碼底層是怎樣實現的?純軟?硬件?Neon指令?CE指令?
非對稱密碼底層是怎樣實現的?
Hash、rng、aead 又都是怎樣實現的?
實現算法的方式:
-
(1)在armv8/armv9的芯片中,有ARM-CE指令可以進行aes/hash/md5計算,
-
(2)在armv8/armv9的芯片中,也有ARM-NEON指令也可以進行aes/hash/md5計算
-
(3)arm的security IP中,有cryptocell之類的加密芯片
-
(4)另外SOC廠商也可能集成自己設計的crypto engine加解密芯片
毫無疑問,在效率這塊肯定是:(3)(4) > (1) > (2) > (5).另外從"實現算法的方式" 來看,如果是rng、aead、rsa之類的算法,那么就不能用ARM-CE這種方式,只有編程語言實現、Neon指令實現、crypto engine(含arm security IP)這幾種方式了。
kernel怎么玩的?:
-
針對 crypto engine(含arm security IP) 這種,先當SOC硬件不支持,跳過此場景。
-
針對rng、aead、rsa,那么kernel有一套純軟的實現 (似乎沒有看到arm neon指令的實現)
-
針對aes、hash,有arm-ce的實現、arm neon指令的實現、純軟的實現,三者三選一(通過宏開關,只能選1)
crypto engine的實現:如果自定義了crypto engine的實現,那么要看你具體的設計,是設計成“取代原有算法實現”,還是設計成“新增算法實現”。如果是前者,那么對于aes/hash,則變成了四選一的了(crypto engine實現、arm-ce的實現、arm neon指令的實現、純軟)。如果是后者,這和原有實現不沖突。
有關aes/hash底層實現三選一的開關:
(1) 開啟下面兩個宏,使用ARM Neon指令的實現 CONFIG_CRYPTO_AES_ARM64_CE_BLK
CONFIG_CRYPTO_AES_ARM64_NEON_BLK
(2) 在(1) 的基礎之上,再開啟如下宏,使用ARM CE指令的實現 USE_V8_CRYPTO_EXTENSIONS
(3) 以上三個宏都不開啟的情況下,使用默認的純軟實現
4、crypto engine的實現
(以ARM Security IP的cryptocell 712為例)
在Linux Kernel中開啟 CONFIG_CRYPTO_DEV_CCREE
宏控即可起用該實現, 代碼路徑如下:
以為aes-cbc為例,其實現的名字 和 Kernel中默認是算法實現的名字是一致的,即使這種實現方式是取代原有算法實現
{
.name = "cbc(aes)",
.driver_name = "cbc-aes-ccree",
.blocksize = AES_BLOCK_SIZE,
.template_skcipher = {
.setkey = cc_cipher_setkey,
.encrypt = cc_cipher_encrypt,
.decrypt = cc_cipher_decrypt,
.min_keysize = AES_MIN_KEY_SIZE,
.max_keysize = AES_MAX_KEY_SIZE,
.ivsize = AES_BLOCK_SIZE,
},
.cipher_mode = DRV_CIPHER_CBC,
.flow_mode = S_DIN_to_AES,
.min_hw_rev = CC_HW_REV_630,
.std_body = CC_STD_NIST,
}
4、代碼導讀
在網絡層、算法中間層、算法實現層有著豐富的結構體類型?那么怎么去閱讀代碼?怎弄清各個層面之間的邏輯呢?事實上我們只要理清這些結構體之間的關系,將其抽象成模型,就會變得更加容易理解了。
如下是以Userspace調用底層的對稱密碼函數為例總結的一張數據結構圖:
sock通信進入網絡層后(algifskcipher.c),構建skcipherrequest結構體,通過該結構體,就能尋址到底層的算法實現,繼而完成算法實現的調用。這些總結一下就是:
-
skcipher_request //網絡層構建的結構體
-
cryptoskcipher // kernel中間層構建的結構體,如果是kernel層調用底層算法,那么就從構建cryptocipher結構體開始。
-
skcipher_alg //算法實現層的結構體,描述著具體的算法實現,有實現廠商自己添加。
上述復雜的結構體流程,進一步抽象,就變成如下這個樣子:
既然如此,那么我們還可以舉一反三一下:
審核編輯 :李倩
-
算法
+關注
關注
23文章
4625瀏覽量
93142 -
Linux
+關注
關注
87文章
11332瀏覽量
210023 -
代碼
+關注
關注
30文章
4814瀏覽量
68849
原文標題:一文了解Linux Kernel中密碼學算法的設計與應用
文章出處:【微信號:LinuxDev,微信公眾號:Linux閱碼場】歡迎添加關注!文章轉載請注明出處。
發布評論請先 登錄
相關推薦
評論