概述
在Linux用户空间,如果要进行加解密运算,通常的方法是安装类似OpenSSL的软件。但是这些加解密运算可以卸载使用内核空间的加密服务,尤其是内核适配了硬件引擎驱动,能有效降低CPU运算负载。
应用层访问内核加密服务大致有如下四种方式:
下图是以OpenSSL为例,展示了用户空间到内核空间的加密服务调用路径:
Linux加密子系统分层框架接下来我们在RISCV开发板上测试四种方法,所有的程序和库需要使用交叉编译。
AF_ALG
AF_ALG是Linux内核提供的一种特殊套接字接口,实现用户空间程序与内核加密API进行通信,为用户态提供相应的加解密服务。
应用层在使用内核加密服务之前,必须配置内核开启相应用户接口选项,如下使能哈希和对称加密的用户API配置:
CONFIG_CRYPTO_USER=yCONFIG_CRYPTO_USER_API_HASH=yCONFIG_CRYPTO_USER_API_SKCIPHER=y
编写sha256计算示例代码如下:
#include<stdio.h>#include<stdlib.h>s#include<string.h>#include<unistd.h>#include<sys/socket.h>#include<linux/if_alg.h>#include<linux/socket.h>intmain(void){int tfmfd, opfd;// 绑定算法类型和名称structsockaddr_algsa = { .salg_family = AF_ALG, .salg_type = "hash", // 算法类型:哈希 .salg_name = "sha256"// 具体算法:SHA256 };constchar *msg = "Hello World";size_t msg_len = strlen(msg);unsignedchar dgst[32];ssize_t len;// 创建AF_ALG套接字 tfmfd = socket(AF_ALG, SOCK_SEQPACKET, 0);if (tfmfd < 0) { perror("socket");exit(EXIT_FAILURE); }// 绑定算法类型和名称if (bind(tfmfd, (struct sockaddr *)&sa, sizeof(sa)) < 0) { perror("bind"); close(tfmfd);exit(EXIT_FAILURE); }// 获取操作句柄 opfd = accept(tfmfd, NULL, 0);if (opfd < 0) { perror("accept"); close(tfmfd);exit(EXIT_FAILURE); }// 发送数据到内核进行哈希计算if (send(opfd, msg, msg_len, 0) != msg_len) { perror("send"); close(opfd); close(tfmfd);exit(EXIT_FAILURE); }// 获取哈希结果 len = read(opfd, dgst, sizeof(dgst));if (len != sizeof(dgst)) { perror("read"); close(opfd); close(tfmfd);exit(EXIT_FAILURE); }// 打印结果printf("Input: %s\nSHA256: ", msg);for (int i = 0; i < sizeof(dgst); i++) {printf("%02x", dgst[i]); }printf("\n");// 清理资源 close(opfd); close(tfmfd);return0;}
使用交叉工具链编译,并在开发板上执行,输出如下:
$ ./af_alg_sha256 Input: Hello WorldSHA256: a591a6d40bf420404a011733cfb7b190d62c65bf0bcda32b57b277d9ad9f146e
默认情况下,会使用内核软件实现的方式计算哈希。如果加载了引擎驱动,就会调用硬件进行计算,但需要注意在注册该算法SHA256时,不能开启此标志CRYPTO_ALG_INTERNAL,即允许应用层调用该算法。硬件引擎驱动安装方法如下(后续其他方式想使用硬件引擎,都需要先安装驱动模块):
$ insmod crypto_engine.ko$ insmod demo_crypto.ko
同样AES-CTR-128算法的计算示例如下,需要注意明文数据长度要16字节块对齐。
#include<stdio.h>#include<stdlib.h>#include<string.h>#include<unistd.h>#include<sys/socket.h>#include<linux/if_alg.h>#include<linux/socket.h>#ifndef SOL_ALG#define SOL_ALG 279#endifintmain(void){int tfmfd, opfd;// 绑定算法类型和名称structsockaddr_algsa = { .salg_family = AF_ALG, .salg_type = "skcipher", // 算法类型:加密 .salg_name = "ctr(aes)"// 具体算法:AES-CTR };constchar *plaintext = "Hello World!!!!!";size_t plaintext_len = strlen(plaintext);constunsignedchar key[16] = {0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77,0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff };constunsignedchar iv[16] = {0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77,0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff };structmsghdrmsg = {};structcmsghdr *cmsg;char cbuf[CMSG_SPACE(4) + CMSG_SPACE(20)] = {0};structaf_alg_iv *af_alg_iv;structioveciov;unsignedchar buf[16];// 创建AF_ALG套接字 tfmfd = socket(AF_ALG, SOCK_SEQPACKET, 0);if (tfmfd < 0) { perror("socket");exit(EXIT_FAILURE); }// 绑定算法类型和名称if (bind(tfmfd, (struct sockaddr *)&sa, sizeof(sa)) < 0) { perror("bind"); close(tfmfd);exit(EXIT_FAILURE); }// 配置密钥if (setsockopt(tfmfd, SOL_ALG, ALG_SET_KEY, key, sizeof(key)) < 0) { perror("setsockopt"); close(tfmfd);exit(EXIT_FAILURE); }// 获取操作句柄 opfd = accept(tfmfd, NULL, 0);if (opfd < 0) { perror("accept"); close(tfmfd);exit(EXIT_FAILURE); }// 配置操作和IV,并发送数据到内核进行加密计算 msg.msg_control = cbuf; msg.msg_controllen = sizeof(cbuf);// 操作 cmsg = CMSG_FIRSTHDR(&msg); cmsg->cmsg_level = SOL_ALG; cmsg->cmsg_type = ALG_SET_OP; cmsg->cmsg_len = CMSG_LEN(4); *(unsignedint *)CMSG_DATA(cmsg) = ALG_OP_ENCRYPT;// IV cmsg = CMSG_NXTHDR(&msg, cmsg); cmsg->cmsg_level = SOL_ALG; cmsg->cmsg_type = ALG_SET_IV; cmsg->cmsg_len = CMSG_LEN(20); af_alg_iv = (struct af_alg_iv *)CMSG_DATA(cmsg);memcpy(af_alg_iv->iv, iv, 16); af_alg_iv->ivlen = 16;// 数据 iov.iov_base = (void *)plaintext; iov.iov_len = plaintext_len; msg.msg_iov = &iov; msg.msg_iovlen = 1;// 发送数据到内核进行计算if (sendmsg(opfd, &msg, 0) < 0) { perror("sendmsg"); close(opfd); close(tfmfd);exit(EXIT_FAILURE); }// 获取加密结果if (read(opfd, buf, sizeof(buf)) < 0) { perror("read"); close(opfd); close(tfmfd);exit(EXIT_FAILURE); }// 打印结果printf("Input: %s\nAES-CTR-128 Output: ", plaintext);for (int i = 0; i < sizeof(buf); i++) {printf("%02x", buf[i]); }printf("\n");// 清理资源 close(opfd); close(tfmfd);return0;}
同样使用交叉工具链编译,并在开发板上执行,输出如下:
$ ./af_alg_aes-ctr-128 Input: Hello World!!!!!AES-CTR-128 Output: 2a9315d244d08e5e167267bd82613a93
而对于非对称算法,比如rsa算法在Linux主分支中未支持通过af_alg方式调用,但是在kernel-patchs目录提供了补丁,可以自行添加到内核中。
libkcapi
库libkcapi 允许用户空间访问Linux内核crypto API。该库利用Netlink接口并封装出易于使用的API,使开发者无需关注底层Netlink接口的处理细节。该库本身不实现任何加密算法,所有消费者请求均发送至内核进行处理。内核加密API的返回结果通过相关API回传给消费者。
下载并编译,流程如下:
$ git clone https://github.com/smuellerDD/libkcapi $ cd libkcapi$ autoreconf -i$ ./configure --prefix=/path/to/libkcapi/install --enable-kcapi-test CC=/path/to/riscv32-linux-gcc --host=riscv$ make all && make install
- --enable-kcapi-test:编译测试程序
编译成功后库和应用程序在libkcapi/install目录下,拷贝到开发板上。
测试sha256哈希计算命令如下:
$ ./kcapi -x 3 -c "sha256" -p 48656c6c6f20576f726c64a591a6d40bf420404a011733cfb7b190d62c65bf0bcda32b57b277d9ad9f146e
测试aes-ctr加密命令如下:
$ ./kcapi -x 1 -e -c "ctr(aes)" -k 00112233445566778899aabbccddeeff -i 00112233445566778899aabbccddeeff -p 48656c6c6f20576f726c6421212121212a9315d244d08e5e167267bd82613a93
如果运行时报如下错误libkcapi - Error: Netlink error: cannot open netlink socket,注意Linux配置需要开启CONFIG_CRYPTO_USER选项,并重新编译内核。
由于AF_ALG不支持非对称算法,如果自行安装了补丁,也可以使用libkcapi测试非对称算法,注意需要使能—enable-lib-asym选项,并重新编译。测试rsa私钥加密命令如下:
$ ./kcapi -x 4 -o 0 -c "rsa" -r 3082010902820100DB101AC2A3F1DCFF136BED44DFF0026D13C788DA706B54F1E827DCC30F996AFAC667FF1D1E3C1DC1B55F6CC0B2073A6D41E42599ACFCD20F02D3D154061A5177BDB6BFEAA75C06A95D698445D7F505BA47F01BD72B24ECCB9B1B108D81A0BEB18C33E436B843EB192A818DDE810A9948B6F6BCCD49343A8F2694E328821A7C8F599F45E85D1A4576045605A1D01B8C776DAF53FA71E267E09AFE03A985D2C9AABA2ABCF4A008F51398135DF0D933342A61C38955F0AE1A9C22EE19058D32FEEC9C84BAB7F96C3A4F07FC45EB12E57BFD55E62969D1C2E8B97859F67910C64EEB6A5EB99AC7C45B63DAA33F5E927A815ED6B0E2628F7426C20CD39A1747E68EAB0203010001 -p 54859b342c49ea2ab29776b4ae3e383c7e641fcca27ff6becf49bc48d36c8f0a0ec173bd7b5579360ea18788b92c90a6535ee9efc4e24dddf7a669823f56a47bfb62e0aeb8d304b3ac5a152ae3199b039a0b41da64ec0a69fcf21092f3c1bf847ffd2caec8b5f64170c547038af8ff6f3fd26f09b422f330bea985cb9c8df98feb3291a225848ff5dcc7069c2de5112c09098709a9f6337390f160f265dd30a566ce627bd0f82d3d198277e30a5f752f8eb1e5e891351b3b33b76692d1f28e6fe5750cad36fb4ed06661bd49fef41aa22b49fe034c74478d9a66b249464d77ea33
cryptodev-linux
cryptodev-linux 是一个/dev/crypto设备驱动,也允许用户空间调用内核的加密API。下载和编译流程如下:
# 下载源码git clone https://github.com/cryptodev-linux/cryptodev-linux# 配置交叉编译器export CROSS_COMPILE=/path/to/riscv32-linux-export CC=${CROSS_COMPILE}gccexport LD=${CROSS_COMPILE}ld# 配置架构export ARCH=riscv# 配置内核源码的编译目录export KERNEL_DIR=/path/to/linux-build# 编译$ make# 编译测试程序$ make tests
这里注意要配置内核源码的编译目录KERNEL_DIR。
编译成功会在根目录下生成cryptodev.ko。cryptode-linux也提供了一些示例,位于examples目录,这里编译哈希和加密代码,测试一下功能正确性:
$ cd examples$ {CROSS_COMPILE}gcc sha.c -o sha -I../$ {CROSS_COMPILE}gcc aes.c -o aes -I../
将以上编译的输出文件cryptodev.ko、sha和aes拷贝到设备上,执行查看哈希和加解密功能是否正常:
$ insmod cryptodev.ko$ ./shaGot sha1 with driver sha1-genericNote: This is not an accelerated cipher$ ./aesGot cbc(aes) with driver cbc(aes-generic)Note: This is not an accelerated cipherGot cbc(aes) with driver cbc(aes-generic)Note: This is not an accelerated cipherAES Test passed
可以看出功能pass,但是并未使用加速引擎。如果我们安装了硬件驱动引擎模块,就会调用硬件引擎计算。
$ insmod crypto_engine.ko$ insmod demo_crypto.ko$ ./aesGot cbc(aes) with driver cbc(aes)-acmeGot cbc(aes) with driver cbc(aes)-acmeAES Test passed
openssl engine
OpenSSL支持动态引擎,可以通过af_alg和cryptodev等方式卸载加解密请求到内核。
下载OpenSSL并进行编译,命令如下:
$ wget https://github.com/openssl/openssl/releases/download/openssl-3.4.0/openssl-3.4.0.tar.gz$ tar -xvf openssl-3.4.0.tar.gz && cd openssl-3.4.0$ ./Configure --prefix=$(pwd)/install --cross-compile-prefix=/path/to/riscv32-linux- linux32-riscv32$ make -j12 && make install
- --cross-compile-prefix:指定交叉编译器
编译成功后命令程序和动态库文件位于install目录,拷贝到目标开发板上:
$ cp openssl-3.4.0/install/lib/libcrypto.so.3 /usr/lib$ cp openssl-3.4.0/install/lib/libssl.so.3 /usr/lib$ cp openssl-3.4.0/install/bin/openssl /usr/bin
OpenSSL默认编译是支持动态引擎加载,因此执行如下命令,可以看到响应:
$ openssl engine -c(dynamic) Dynamic engine loading support
af_alg方式
首先看下af_alg方式卸载到内核,OpenSSL默认编译自带af_alg,生成的引擎so文件位于openssl-3.4.0/install/l ib64/engines-3/afalg.so。同样拷贝到目标开发板上:
$ cp openssl-3.4.0/install/lib64/engines-3/afalg.so /usr/lib
注意需要配置OPENSSL_ENGINES环境变量,否则找不到afalg.so动态库:
$ export OPENSSL_ENGINES=/usr/lib
查看引擎是否安装成功,并列举支持的算法能力,当前只支持AES-CBC算法:
$ openssl engine -c -t afalg(afalg) AFALG engine support [AES-128-CBC, AES-192-CBC, AES-256-CBC] [ available ]
测试一下加解密,查看引擎是否正常工作:
# 纯应用层软件加密$ openssl enc -e -aes-128-cbc -K 00112233445566778899aabbccddeeff -iv 00112233445566778899aabbccddeeff -in hello.txt -out ciphertext# 启用afalg引擎加密$ openssl enc -e -aes-128-cbc -K 00112233445566778899aabbccddeeff -iv 00112233445566778899aabbccddeeff -in hello.txt -out ciphertext_afalg -engine afalgEngine "afalg"set.
我们也可以手动动态加载AF_ALG,命令如下:
$ openssl engine -t -c -pre SO_PATH:/usr/lib/afalg.so -pre ID:afalg -pre LOAD
cryptodev方式
接着看下cryptodev方式,默认OpenSSL不编译,因此需要重新编译并使能enable-devcryptoeng,另外注意需要提供cryptodev-linux的头文件目录:
$ ./Configure --prefix=$(pwd)/install --cross-compile-prefix=/path/to/riscv32-linux- linux32-riscv32 -I/path/to/cryptodev-linux enable-devcryptoeng$ make -j12 && make install
生成的引擎so文件位于openssl-3.4.0/install/l ib64/engines-3/devcrypto.so。同样拷贝到目标开发板上:
cp openssl-3.4.0/install/lib64/engines-3/devcrypto.so /usr/lib
由于openssl编译出的引擎依赖cryptodev库,因此需要先安装前一章cryptodev-linux编译出的cryptodev.ko:
$ insmod cryptodev.ko
否则直接使用devcyrpto方式,会报错:
90AF7595:error:1300006D:engine routines:dynamic_load:init failed:crypto/engine/eng_dyn.c:510:90AF7595:error:13000074:engine routines:ENGINE_by_id:no such engine:crypto/engine/eng_list.c:475:id=devcrypto
同样查看引擎是否安装成功,并列举支持的算法能力,可以看出支持AES-CBC/CTR和SHA256算法,比af_alg方式多:
$ openssl engine -c -t devcrypto(devcrypto) /dev/crypto engine [AES-128-CBC, AES-192-CBC, AES-256-CBC, AES-128-CTR, AES-192-CTR, AES-256-CTR, AES-128-ECB, AES-192-ECB, AES-256-ECB, SHA256] [ available ]
测试一下SHA256算法,查看引擎是否正常工作:
# 纯应用层软件计算$ openssl dgst -sha256 hello.txtSHA2-256(hello.txt)= a948904f2f0f479b8f8197694b30184b0d2ed1c1cd2a1ec0fb85d299a192a447# 启用devcrypto引擎计算$ openssl dgst -engine devcrypto -sha256 hello.txt Engine "devcrypto"set.SHA2-256(hello.txt)= a948904f2f0f479b8f8197694b30184b0d2ed1c1cd2a1ec0fb85d299a192a447
再测试一下AES加解密:
# 纯应用层软件加密$ openssl enc -e -aes-128-cbc -K 00112233445566778899aabbccddeeff -iv 00112233445566778899aabbccddeeff -in hello.txt -out ciphertext# 启用devcrypto引擎加密$ openssl enc -e -aes-128-cbc -K 00112233445566778899aabbccddeeff -iv 00112233445566778899aabbccddeeff -in hello.txt -out ciphertext_devcrypto -engine devcryptoEngine "devcrypto"set.
最后再测试一下非对称算法RSA的签名和验签:
# 生成密钥$ openssl genrsa -out prikey.pem 2048# 提取公钥$ openssl rsa -in prikey.pem -pubout -out pubkey.pem# PEM转化为DER格式$ openssl rsa -pubin -in pubkey.pem -out pubkey.der -outform DER# 随机生成32字节哈希$ dd if=/dev/urandom of=hash bs=1 count=32# 纯应用层软件签名$ openssl pkeyutl -sign -inhash -inkey prikey.pem -out sig -pkeyopt digest:sha256 -pkeyopt rsa_padding_mode:pss# 纯应用层软件验签$ openssl pkeyutl -verify -inhash -sigfile sig -pubin -inkey pubkey.pem -pkeyopt digest:sha256 -pkeyopt rsa_padding_mode:pssSignature Verified Successfully# 启用devcrypto引擎签名$ openssl pkeyutl -sign -inhash -inkey prikey.pem -out sig -pkeyopt digest:sha256 -pkeyopt rsa_padding_mode:pss -engine devcryptoEngine "devcrypto"set.# 启用devcrypto引擎验签$ openssl pkeyutl -verify -inhash -sigfile sig -pubin -inkey pubkey.pem -pkeyopt digest:sha256 -pkeyopt rsa_padding_mode:pss -engine devcryptoEngine "devcrypto"set.Signature Verified Successfully
参考