Linux内核crypto框架Hash/RSA验签算法使用样例

. . . . 是个内核模块里面功能的一部分,用来通过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签名的实现。。。。。。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值