. . . . 是个内核模块里面功能的一部分,用来通过file结构体(来自fs部分)读取文件并计算哈希,在内核5.15上编译通过
#include <crypto/algapi.h>
#include <crypto/hash.h>
#include <crypto/sha2.h> // 注意这个引用,因为用的是SHA256所以要引用它,如果是sha1就得引用sha1.h,不同的算法文件不一样,需要自己去crypto下确认下
#include <linux/fs.h>
#include <linux/string.h>
struct sdesc {
struct shash_desc shash;
char ctx[];
};
// 我这个模块循环执行的,所以环境alloc一次就保持了,不会释放也不会多次申请,提高性能,按需更改
struct crypto_shash *alg = NULL;
struct sdesc *sdesc = NULL;
char *rbuf = NULL;
static int calc_sha256_hash(struct file *file, unsigned char *digest) {
int ret = 0;
int size = 0;
loff_t i_size = 0;
loff_t offset = 0;
int rbuf_len = 0;
// The target we're checking
struct dentry *dentry = file->f_path.dentry;
struct inode *inode = d_backing_inode(dentry);
// Find out how big the file is
// 下面这行被注释的代码来自于内核security下ima组件的代码,很简练的用法,但我没试过,我测试通过的是没注释部分的代码
// i_size = i_size_read(file_inode(file));
i_size = i_size_read(inode);
printk(KERN_INFO "File Size: %lld\n", i_size);
// Allocate a buffer for reading the file contents
if (rbuf == NULL) {
rbuf = kzalloc(PAGE_SIZE, GFP_KERNEL);
if (!rbuf) {
printk(KERN_ERR "failed to kzalloc to read buffer\n");
return -ENOMEM;
}
}
// 注意下面的初始化操作,要是不是我这种环境持久化的代码,每个出错返回流程中需要把前面申请的内存释放掉,避免泄漏。我这里不需要就没有,释放的代码在最下面
// Hash Algo init
if (alg == NULL) {
// 这个位置的算法名字可以指定其他,见下一部分的列表
alg = crypto_alloc_shash("sha256", 0, 0);
if (IS_ERR(alg)) {
printk(KERN_ERR "can't alloc alg sha256\n");
return PTR_ERR(alg);
}
}
// Calc CTX init
if (sdesc == NULL) {
size = sizeof(struct shash_desc) + crypto_shash_descsize(alg);
sdesc = kmalloc(size, GFP_KERNEL);
if (!sdesc) {
printk(KERN_ERR "can't alloc sdesc\n");
return -ENOMEM;
}
sdesc->shash.tfm = alg;
// 部分资料里有写这个,但是5.15没有,我就注释了
// sdesc->shash.flag = crypto_shash_get_flags(alg);
}
// Init the hash
ret = crypto_shash_init(&sdesc->shash);
if (ret) {
printk(KERN_ERR "failed to crypto_shash_init\n");
return ret;
}
// Calc Hash, in page-sized chunks.
while (offset < i_size) {
// be care of "&offset", offset will self increment
rbuf_len = kernel_read(file, rbuf, PAGE_SIZE, &offset);
if (rbuf_len < 0) {
ret = rbuf_len;
break;
}
if (rbuf_len == 0) {
break;
}
ret = crypto_shash_update(&sdesc->shash, rbuf, rbuf_len);
if (ret) {
break;
}
}
// Finalise the SHA256 result.
if (!ret) {
ret = crypto_shash_final(&sdesc->shash, digest);
}
// 清除环境的代码
// kfree(sdesc);
// sdesc = NULL;
// crypto_free_shash(alg);
// alg = NULL;
// kfree(rbuf);
// rbuf = NULL;
// 我的环境是持久化的,每次处理要清空读文件的缓存空间,避免文件内容泄漏
memset(rbuf, 0, PAGE_SIZE);
return ret;
}
. . . . 哈希算法可以换其他的,5.15内核里可用的算法如下:
const char *const hash_algo_name[HASH_ALGO__LAST] = {
[HASH_ALGO_MD4] = "md4",
[HASH_ALGO_MD5] = "md5",
[HASH_ALGO_SHA1] = "sha1",
[HASH_ALGO_RIPE_MD_160] = "rmd160",
[HASH_ALGO_SHA256] = "sha256",
[HASH_ALGO_SHA384] = "sha384",
[HASH_ALGO_SHA512] = "sha512",
[HASH_ALGO_SHA224] = "sha224",
[HASH_ALGO_RIPE_MD_128] = "rmd128",
[HASH_ALGO_RIPE_MD_256] = "rmd256",
[HASH_ALGO_RIPE_MD_320] = "rmd320",
[HASH_ALGO_WP_256] = "wp256",
[HASH_ALGO_WP_384] = "wp384",
[HASH_ALGO_WP_512] = "wp512",
[HASH_ALGO_TGR_128] = "tgr128",
[HASH_ALGO_TGR_160] = "tgr160",
[HASH_ALGO_TGR_192] = "tgr192",
[HASH_ALGO_SM3_256] = "sm3",
[HASH_ALGO_STREEBOG_256] = "streebog256",
[HASH_ALGO_STREEBOG_512] = "streebog512",
};
EXPORT_SYMBOL_GPL(hash_algo_name);
. . . .RSA验签功能的实现如下:
#include <linux/string.h>
// RSA
#include <crypto/akcipher.h>
#include <linux/verification.h>
#include <crypto/pkcs7.h>
#include <crypto/public_key.h>
#include <crypto/hash.h>
#include <linux/scatterlist.h>
// 私钥生成方式: openssl genrsa -out priv.pem
// 公钥生成方式: openssl rsa -in priv.pem -RSAPublicKey_out -outform der -out pub.der
// 务必注意-RSAPublicKey_out和-outform der参数,内核只能识别这个格式的密钥
// 注意,这个公钥顺序是不对的,因为这个是通过 hexdump pub.der 方式获取的,导致原本0xab, 0xcd, 0xef, 0xgh的顺序变成了0xcd, 0xab, 0xgh, 0xef,每两个字节的数据顺序相反了
// 如果想要获取正确顺序的密钥,请使用 hexdump -C pub.der 方式,只不过多余的垃圾数据会多些,需要手工处理
// 因此下面才会有pubkey_translate函数转换顺序
unsigned char pubkey[] = {
0x81, 0x30, 0x02, 0x89, 0x81, 0x81, 0xca, 0x00, 0x81, 0x5f, 0x61, 0x7f, 0x79, 0xb1, 0x8d, 0x58, 0x41, 0x4a, 0x74, 0x29, 0x72, 0x09, 0xd2, 0x8b, 0x47, 0x55, 0x51, 0xa4, 0xdf, 0x6d, 0x2b, 0xdb, 0x51, 0xa2, 0x54, 0xe6, 0xa3, 0x36, 0x02, 0x11, 0x49, 0xd1, 0x79, 0x54, 0x28, 0x56, 0x13, 0xee, 0xd3, 0x87, 0x8a, 0xda, 0x3b, 0xec, 0xed, 0xe0, 0x79, 0xaa, 0x9a, 0x06, 0xfb, 0x6b, 0xb8, 0x2d, 0x0f, 0x99, 0x7a, 0xb5, 0xe2, 0xab, 0x8d, 0x5c, 0x91, 0x0b, 0x41, 0x57, 0x60, 0x8b, 0x5c, 0x05, 0xaa, 0x9b, 0xd6, 0xd5, 0xe9, 0x55, 0x08, 0xcc, 0x56, 0xb0, 0xcf, 0xd8, 0x39, 0x42, 0x11, 0x4d, 0xb9, 0x07, 0x6c, 0x11, 0x26, 0x97, 0x63, 0x38, 0x79, 0x5f, 0x7b, 0x7f, 0x7e, 0x63, 0x73, 0x96, 0xe5, 0x9b, 0x2d, 0x49, 0xfa, 0xcd, 0xd9, 0x48, 0x7d, 0xdb, 0x80, 0x56, 0xdc, 0xa4, 0xb2, 0xcd, 0x9c, 0xf1, 0x1f, 0x58, 0x1a, 0xea, 0x02, 0x3f, 0x01, 0x03, 0x01, 0x00};
// RSA会话与运算请求的数据指针,同样的是持久性/性能优化设计,按需更改
struct crypto_akcipher *algo_verify = NULL;
struct akcipher_request *req = NULL;
// 为了把 hexdump 的错误顺序转换回来的函数,使用了-C参数就不需要转换了
static void pubkey_translate(unsigned char *pubkey, unsigned int pubkey_len) {
int i;
unsigned char code_tmp;
if (pubkey_len % 2) {
printk(KERN_ERR "Invalid pubkey length\n");
return;
}
for (i = 0; i < pubkey_len; i += 2) {
code_tmp = pubkey[i];
pubkey[i] = pubkey[i + 1];
pubkey[i + 1] = code_tmp;
}
}
static int rsa_verify(unsigned char *digest, unsigned int digest_len,
unsigned char *signature, unsigned int signature_len) {
int ret = 0;
// clock that used to wait for completion
DECLARE_CRYPTO_WAIT(wait);
struct scatterlist src_tab[2];
// Load the RSA algo
if (algo_verify == NULL) {
// Init the RSA algo
// 这里面的算法名字可以换掉,具体能换哪些,可以在内核源码中的crypto/testmgr.c文件中
// 搜索alg_test_akcipher,然后寻找算法测试的定义结构体,挨个查看.alg变量的值,如下文样例
algo_verify = crypto_alloc_akcipher("pkcs1pad(rsa,sha256)", 0, 0);
if (IS_ERR(algo_verify)) {
printk(KERN_ERR "Failed to load RSA-SHA256 algo.\n");
return PTR_ERR(algo_verify);
}
// Set the public key
// 错误的密钥顺序需要先行转换
pubkey_translate(pubkey, sizeof(pubkey));
ret = crypto_akcipher_set_pub_key(algo_verify, pubkey, sizeof(pubkey));
if (ret) {
printk(KERN_ERR "Failed to set RSA key.\n");
// Oh shit, set pubkey failed, we must do it again
crypto_free_akcipher(algo_verify);
algo_verify = NULL;
return ret;
}
}
// Init the request
if (req == NULL) {
req = akcipher_request_alloc(algo_verify, GFP_KERNEL);
if (!req) {
printk(KERN_ERR "Failed to allocate RSA request.\n");
return -ENOMEM;
}
}
// Setup the data to be verified
sg_init_table(src_tab, 2);
sg_set_buf(&src_tab[0], signature, signature_len);
sg_set_buf(&src_tab[1], digest, digest_len);
// Setup the request
akcipher_request_set_crypt(req, src_tab, NULL, signature_len, digest_len);
// Set the callback when the request is done
akcipher_request_set_callback(req, CRYPTO_TFM_REQ_MAY_BACKLOG,
crypto_req_done, &wait);
// Verify the signature and wait for completion
ret = crypto_wait_req(crypto_akcipher_verify(req), &wait);
// 环境清理的代码,按需使用
// free the request
// akcipher_request_free(req);
// req = NULL;
// free the algo
// crypto_free_akcipher(algo_verify);
// algo_verify = NULL;
return ret;
}
int test_demo(void)
{
int x;
// 签名值的获取方法: openssl dgst -hex -sha256 -sign priv.key file
unsigned char sig[] = {0x98, 0xe0, 0x9f, 0xce, 0xd1, 0xcf, 0xe6, 0x0d, 0xf7, 0xe4, 0x68, 0x35, 0x76, 0x9b, 0x54, 0x28, 0x5f, 0x48, 0x58, 0x4a, 0x52, 0xcd, 0x74, 0x55, 0x38, 0xd3, 0x40, 0xc6, 0x65, 0x2c, 0x58, 0xe9, 0x63, 0x5a, 0x4a, 0x98, 0xb0, 0x3e, 0x12, 0x64, 0x83, 0x1a, 0x30, 0x10, 0xc1, 0x6b, 0x1e, 0x72, 0xfd, 0x46, 0xe5, 0x14, 0xbf, 0x7e, 0xa8, 0x46, 0x33, 0x2b, 0x6b, 0xcc, 0x72, 0x23, 0x30, 0x59, 0x9c, 0x3a, 0xf6, 0x09, 0x45, 0x50, 0x7f, 0xf2, 0x0e, 0x8f, 0x91, 0x2d, 0x79, 0x6a, 0xe6, 0x29, 0x0a, 0x03, 0xd3, 0x00, 0x6b, 0x91, 0x4f, 0x22, 0xd6, 0xe6, 0x9d, 0x24, 0x56, 0x14, 0x8e, 0x94, 0x0c, 0x23, 0x33, 0xd0, 0x7e, 0xea, 0xf4, 0x61, 0x32, 0xaf, 0x16, 0x75, 0x43, 0x10, 0x39, 0x07, 0x8a, 0x23, 0x7d, 0x6d, 0x36, 0xf5, 0x01, 0x3e, 0x8e, 0x07, 0x97, 0x43, 0x06, 0xa4, 0x5e, 0x57};
// 哈希值的获取方法: openssl dgst -sha256 file
unsigned char digest[] = {
0x60, 0xec, 0xc4, 0x14, 0xd3, 0x03, 0x41, 0x4a, 0xac, 0xf9, 0x67, 0x4c, 0x49, 0x1e, 0x5a, 0x72, 0x35, 0x3a, 0x00, 0xec, 0x5a, 0x77, 0xf2, 0xcd, 0xcf, 0x6b, 0x08, 0x3d, 0xd5, 0xb9, 0x12, 0x26};
x = rsa_verify(digest, 32, sig, 128);
printk(KERN_INFO "Verify result: %d\n", x);
return 0;
}
. . . . 可换的算法名字搜索特征样例(目标位置是kernel_src/crypto/testmgr.c):
.alg = "pkcs1pad(rsa,sha256)",
.test = alg_test_akcipher,
.fips_allowed = 1,
.suite = {
. . . . To Do:RSA签名的实现。。。。。。