我目前正在开发一个内核模块,我正在执行RSA签名验证。我的模块针对4.4内核,因此我决定使用较低级别的akcipher
API。我一直在使用public_key_verify_signature
的当前实现作为指南。我的方法是:
- 分配一个加密_akcipher结构:
*tfm = crypto_alloc_akcipher("rsa", 0, 0);
- 分配akcipher_request结构:
req = akcipher_request_alloc(*tfm, GFP_KERNEL);
- 设置请求的公共密钥:
err = crypto_akcipher_set_pub_key(*tfm, data, len);
- 将接收的签名放入散点表中,并将其设置为AKCIPHER_REQUEST的参数:
akcipher_request_set_crypt(req, &src, &dst, sig->s_size, MAX_OUT);
- 最终呼叫
crypto_akcipher_verify(req)
,该电话应该计算预期的摘要 - 将预期的摘要与接收的摘要进行比较以验证签名
我目前正处于我认为我正确使用API的时刻,但是crypto_akcipher_verify
的输出不符合较新的public_key_verify_signature
示例中使用的方式。这使我感到困惑,因为它似乎输出了正确的消化的一部分。
例如,当收到正确签名的请求时,我会得到以下结果:
Expected Digest:
e52bed356dcbf8e4b3c1458ac3e4cb49e77512e6
Computated outbuf:
01ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff003015300906052b0e03021a05000408e52bed356dcbf8e4
计算的outbuf
的最后8个字节是预期20个字节摘要的前8个字节。但是outbuf
的其余部分似乎是垃圾。(尽管每次都是一致的,但始终是0x01
,其次是大量0xffs
,最后是最后8个字节之前的003015300906052b0e03021a05000408
(。这是负责拨打crypto_akcipher_verify(req)
的代码的一部分:
// Init completion
init_completion(&(res.completion));
// Put the data into our request structure
memcpy(inbuf, sig->s, sig->s_size);
sg_init_one(&src, inbuf, sig->s_size);
sg_init_one(&dst, outbuf, MAX_OUT);
akcipher_request_set_crypt(req, &src, &dst, sig->s_size, MAX_OUT);
// Set the completion routine callback
// results from the verify routine will be stored in &res
akcipher_request_set_callback(req, CRYPTO_TFM_REQ_MAY_BACKLOG |
CRYPTO_TFM_REQ_MAY_SLEEP, op_complete, &res);
// Compute the expected digest
err = wait_async_op(&res, crypto_akcipher_verify(req));
if(err) {
printk(KERN_INFO "[!] Digest computation failed %dn", err);
kfree(inbuf);
kfree(outbuf);
return err;
}
printk(KERN_INFO "nComputation:n");
hexdump(outbuf, req->dst_len);
/* Do the actual verification step. */
if (req->dst_len != sig->digest_size ||
memcmp(sig->digest, outbuf, sig->digest_size) != 0) {
printk(KERN_INFO "[!] Signature verification failed - Key Rejected: %dn", -EKEYREJECTED);
printk(KERN_INFO "[!] Sig len: %d Computed len: %dn", sig->digest_size, req->dst_len);
kfree(inbuf);
kfree(outbuf);
return -EKEYREJECTED;
}
任何帮助或指向正确方向的人将不胜感激。抱歉,如果这篇文章不是很简洁。
如上所述。我看到的是PKCS1 V1.5编码。从RFC中,填充看起来像:
EM = 0x00 || 0x01 || PS || 0x00 || T.
ps是:
'ps由emlen -tlen -3带有十六进制值0xff的八位字节组成。PS的长度至少为8个八位。
和der编码" t&quot"在SHA1的末尾是(如上所述,我将切换到SHA256(:
SHA-1: (0x)30 21 30 09 06 05 2b 0e 03 02 1a 05 00 04 14 || H.
在调用crypto_alloc_akcipher
时,在新内核中解析此填充的最佳方法是使用"pkcs1pad(rsa,SHA256)"
。那么rsa_verify
将为您解析填充物。不幸的是,由于我试图在几个内核版本上移植它,因此我必须查看旧方法并引用了旧的rsa_verify
例程。
最后,我的sha256 emsa pkcs#1 v1.5解析代码看起来像:
static const u8 RSA_digest_info_SHA256[] = {
0x30, 0x31, 0x30, 0x0d, 0x06, 0x09,
0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x01,
0x05, 0x00, 0x04, 0x20
};
typedef struct RSA_ASN1_template {
const u8 * data;
size_t size;
} RSA_ASN1_template;
RSA_ASN1_template sha256_template;
// Derived from https://github.com/torvalds/linux/blob/db6c43bd2132dc2dd63d73a6d1ed601cffd0ae06/crypto/asymmetric_keys/rsa.c#L101
// and https://www.rfc-editor.org/rfc/rfc8017#section-9.2
// thanks to Maarten Bodewes for answering the question on Stackoverflow
// https://stackoverflow.com/questions/49662595/linux-kernel-rsa-signature-verification-crypto-akcipher-verify-output
static char *pkcs_1_v1_5_decode_emsa(unsigned char * EM,
unsigned long EMlen,
const u8 * asn1_template,
size_t asn1_size,
size_t hash_size) {
unsigned int t_offset, ps_end, ps_start, i;
if (EMlen < 2 + 1 + asn1_size + hash_size)
return NULL;
/* Decode the EMSA-PKCS1-v1_5
* note: leading zeros are stripped by the RSA implementation in older kernels
* so EM = 0x00 || 0x01 || PS || 0x00 || T
* will become EM = 0x01 || PS || 0x00 || T.
*/
#if LINUX_VERSION_CODE < KERNEL_VERSION(4,8,0)
ps_start = 1;
if (EM[0] != 0x01) {
printk(" = -EBADMSG [EM[0] == %02u]", EM[0]);
return NULL;
}
#else
ps_start = 2;
if (EM[0] != 0x00 || EM[1] != 0x01) {
printk(" = -EBADMSG [EM[0] == %02u] [EM[1] == %02u]", EM[0], EM[1]);
return NULL;
}
#endif
// Calculate offsets
t_offset = EMlen - (asn1_size + hash_size);
ps_end = t_offset - 1;
// Check if there's a 0x00 seperator between PS and T
if (EM[ps_end] != 0x00) {
printk(" = -EBADMSG [EM[T-1] == %02u]", EM[ps_end]);
return NULL;
}
// Check the PS 0xff padding
for (i = ps_start; i < ps_end; i++) {
if (EM[i] != 0xff) {
printk(" = -EBADMSG [EM[PS%x] == %02u]", i - 2, EM[i]);
return NULL;
}
}
// Compare the DER encoding T of the DigestInfo value
if (crypto_memneq(asn1_template, EM + t_offset, asn1_size) != 0) {
printk(" = -EBADMSG [EM[T] ASN.1 mismatch]");
return NULL;
}
return EM + t_offset + asn1_size;
}
和调用它的验证功能:
// Verify a recieved signature
int verify_sig_rsa(akcipher_request * req, pkey_signature * sig) {
int err;
void *inbuf, *outbuf, *result = NULL;
op_result res;
struct scatterlist src, dst;
crypto_akcipher *tfm = crypto_akcipher_reqtfm(req);
int MAX_OUT = crypto_akcipher_maxsize(tfm);
inbuf = kzalloc(PAGE_SIZE, GFP_KERNEL);
err = -ENOMEM;
if(!inbuf) {
return err;
}
outbuf = kzalloc(MAX_OUT, GFP_KERNEL);
if(!outbuf) {
kfree(inbuf);
return err;
}
// Init completion
init_completion(&(res.completion));
// Put the data into our request structure
memcpy(inbuf, sig->s, sig->s_size);
sg_init_one(&src, inbuf, sig->s_size);
sg_init_one(&dst, outbuf, MAX_OUT);
akcipher_request_set_crypt(req, &src, &dst, sig->s_size, MAX_OUT);
// Set the completion routine callback
// results from the verify routine will be stored in &res
akcipher_request_set_callback(req, CRYPTO_TFM_REQ_MAY_BACKLOG |
CRYPTO_TFM_REQ_MAY_SLEEP, op_complete, &res);
// Compute the expected digest
err = wait_async_op(&res, crypto_akcipher_verify(req));
if(err) {
printk(KERN_INFO "[!] Digest computation failed %dn", err);
kfree(inbuf);
kfree(outbuf);
kfree(result);
return err;
}
// Decode the PKCS#1 v1.5 encoding
sha256_template.data = RSA_digest_info_SHA256;
sha256_template.size = ARRAY_SIZE(RSA_digest_info_SHA256);
result = pkcs_1_v1_5_decode_emsa(outbuf, req->dst_len,
sha256_template.data, sha256_template.size, 32);
err = -EINVAL;
if(!result) {
printk(KERN_INFO "[!] EMSA PKCS#1 v1.5 decode failedn");
kfree(inbuf);
kfree(outbuf);
return err;
}
printk(KERN_INFO "nComputation:n");
hexdump(result, 20);
/* Do the actual verification step. */
if (crypto_memneq(sig->digest, result, sig->digest_size) != 0) {
printk(KERN_INFO "[!] Signature verification failed - Key Rejected: %dn", -EKEYREJECTED);
kfree(inbuf);
kfree(outbuf);
return -EKEYREJECTED;
}
printk(KERN_INFO "[+] RSA signature verification passedn");
kfree(inbuf);
kfree(outbuf);
return 0;
}
如果有人想引用完整的代码,可以在此处找到。
您已经执行了RAW或教科书RSA解密。您正在查看的是Hash 一个称为PKCS#1 V1.5的填充物。这也在PKCS#1 V2.0中定义了向后兼容(即使它是确定性的(。
(。因此,请查看PKCS#1 v2.2,8.2.2步骤3,其中解释说您必须自己构建相同的结构,然后进行比较。请注意,9.2包含一些有用的快捷方式,用于在注释中创建结构。
最后:SHA-1不再被认为是安全的签名生成/验证。尽快升级!