Linux内核RSA签名验证crypto_akcipher_verify()输出

时间:2018-04-05 00:58:11

标签: c linux-kernel cryptography rsa

我正在开发一个内核模块,我正在执行RSA签名验证。我的模块针对4.4内核,因此我决定使用较低级别的akcipher API。我一直在使用当前public_key_verify_signature *tfm = crypto_alloc_akcipher("rsa", 0, 0);作为指南。我的方法是:

  1. 分配一个crypto_akcipher结构:req = akcipher_request_alloc(*tfm, GFP_KERNEL);
  2. 分配akcipher_request结构:err = crypto_akcipher_set_pub_key(*tfm, data, len);
  3. 设置请求的公钥:akcipher_request_set_crypt(req, &src, &dst, sig->s_size, MAX_OUT);
  4. 将收到的签名放入散点列表,并将其设置为akcipher_request的参数:crypto_akcipher_verify(req)
  5. 最后拨打crypto_akcipher_verify来计算预期的摘要
  6. 将预期摘要与收到的摘要进行比较以验证签名
  7. 我目前正处于这样的地步,我认为我正在使用API​​,但public_key_verify_signature的输出不符合新Expected Digest: e52bed356dcbf8e4b3c1458ac3e4cb49e77512e6 Computated outbuf: 01ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff003015300906052b0e03021a05000408e52bed356dcbf8e4 示例中使用的方式。这让我感到困惑,因为它似乎输出了正确摘要的一部分。

    例如,当收到正确签名的请求时,我会得到以下结果:

    outbuf

    计算的outbuf的最后8个字节是预期的20字节摘要的前8个字节。但0x01的其余部分似乎是垃圾。 (虽然每次都是一致的,但在最后8个字节之前总是0xffs后面跟着很多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 %d\n", 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: %d\n", -EKEYREJECTED); printk(KERN_INFO "[!] Sig len: %d Computed len: %d\n", sig->digest_size, req->dst_len); kfree(inbuf); kfree(outbuf); return -EKEYREJECTED; } 的代码块:

    final MediaPlayer Sound1 = MediaPlayer.create(this, R.raw.somesound);
    final Button play_Sound1 = (Button) this.findViewById(R.id.play_Sound1);
    
    play_Sound1.setOnClickListener(new View.OnClickListener() {
    
    
        @Override
        public void onClick(View v){
                Sound1.start();
        }
    
    });
    

    任何帮助或指向正确的方向将不胜感激。对不起,如果这篇文章不是非常简洁。

2 个答案:

答案 0 :(得分:0)

您已执行原始或教科书RSA解密。您正在查看的是散列+填充,称为PKCS#1 v1.5。这也在PKCS#1 v2.0以后定义为向后兼容性(即使它是确定性的,它仍然被认为是安全的。)

所以看看PKCS#1 v2.2, 8.2.2第3步,其中解释说你必须自己构建相同的结构然后进行比较。请注意,9.2包含一些有用的快捷方式,用于在笔记中创建结构。

最后:对于签名生成/验证,SHA-1不再被认为是安全的。尽快升级!

答案 1 :(得分:0)

正如@Maarten上面所说。我所看到的是PKCS1 v1.5编码。从RFC开始,填充看起来像:

EM = 0x00 || 0x01 || PS || 0x00 || T.

PS在哪里:

“PS由emLen组成 - tLen - 具有十六进制值0xff的3个八位字节.PS的长度至少为8个八位字节。”

SHA1末端的DER编码“T”是(我将按照上面的建议切换到SHA256):

SHA-1:   (0x)30 21 30 09 06 05 2b 0e 03 02 1a 05 00 04 14 || H.

在较新的内核中解析此填充的最佳方法是在调用"pkcs1pad(rsa,SHA256)"时使用crypto_alloc_akcipher类型。然后rsa_verify将为您解析填充。不幸的是,因为我试图在几个内核版本中移植它,我不得不看一个较旧的方法和referenced the old rsa_verify routine

最后,我的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://tools.ietf.org/html/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 %d\n", 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 failed\n");
        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: %d\n", -EKEYREJECTED);
        kfree(inbuf);
        kfree(outbuf);
        return -EKEYREJECTED;
    }

    printk(KERN_INFO "[+] RSA signature verification passed\n");
    kfree(inbuf);
    kfree(outbuf);
    return 0;
}

如果有人想要引用完整的代码,那么can be found here