【问题标题】:Verify a RSA public key in OpenSSL?在 OpenSSL 中验证 RSA 公钥?
【发布时间】:2015-06-09 21:34:53
【问题描述】:

我有一个只有 RSA 密钥的公共部分的 EVP_PKEY。我从 DER 编码中的 SubjectPublicKeyInfo 结构中提取了公共部分。这就是我现在拥有的:

unsigned char publicKey[] = {0x30, 0x5a, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, ...}
size_t publicKeyLength = 92;
unsigned char* publicKeyCopy = new unsigned char[publicKeyLength];
memcpy(publicKeyCopy, publicKey, publicKeyLength);

RSA *rsa;
rsa = d2i_RSA_PUBKEY(NULL, (unsigned char const **) &pubKey, pubKeyLen);
EVP_PKEY *pkey = EVP_PKEY_new();
EVP_PKEY_assign_RSA(pkey, rsa);

我知道您可以使用 RSA_check_key to verify a RSA private key,但文档说“它不适用于仅填充了模数和公共指数元素的 RSA 公钥”。

那么,是否可以在没有私有部分的情况下验证密钥?因为如您所见,我只有 EVP_PKEY 的公共部分。我想知道,这甚至可能吗?你会在 EVP_PKEY 的公共部分验证什么?

你可以看到这个问题的答案Programmatically verify a X509 certificate and private key match,但是完整的密钥已经过验证(私有和公共部分)。

小心 本题贴出的原代码有BUG。这是因为内部d2i_RSA_PUBKEY 使用d2i_PUBKEYd2i_PUBKEY 使用d2i_X509_PUBKEY(在x_pubkey.c 中)。如果您阅读documentation for d2i_X509,您将看到下一个“警告:必须使用临时变量。一个常见的错误是尝试直接使用缓冲区...”。 因此,更正后的代码必须使用publicKeyCopy 的临时副本,使用后您可以安全地删除publicKeyCopy

【问题讨论】:

  • 您发布的内容看起来像(开始)一个 512 位 RSA 公钥。当您说“验证”时,您的意思仅仅是它格式正确吗?还是您的意思是将其建立为特定公共/私人对的“公共”密钥。后者涉及私钥加密的已知值(通常是签名)。澄清您正在验证的具体内容是什么? “公钥”不是答案。 “关于……的公钥”会很有帮助。
  • 感谢您的回复。我想知道公钥是否格式正确,将使用此密钥的客户端只知道这是一个公钥,他将使用它来加密一些数据。这是 DER 格式的密钥lapo.it/asn1js/…

标签: c++ c openssl


【解决方案1】:

注意本题贴的原代码有BUG...

我只是对此发表评论,并告诉你如何处理它。

unsigned char publicKey[] = {0x30, 0x5a, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, ...}
size_t publicKeyLength = sizeof(publicKey);

unsigned char* t = publicKey;
rsa = d2i_RSA_PUBKEY(NULL, &t, pubKeyLen);

在内部,临时指针t 递增,因此它被浪费了。如果一切都按预期工作,它将指向缓冲区之后的某个位置。函数执行后你应该找到(size_t)t - (size_t)publicKey == publicKeyLength

因为你使用了临时指针,原来的指针publicKey还是不错的。如果内存中有连续的key,可以使用t解析下一个key。

无需复制数据。


我认为第二种选择是使用内存BIOd2i_RSA_PUBKEY_bio。比如:

BIO* bio = BIO_new_mem_buf(publicKey, (int)publicKeyLength);
ASSERT(bio != NULL);

RSA* rsa = d2i_RSA_PUBKEY_bio(bio, NULL);
ASSERT(rsa != NULL);

/* ... */

RSA_free(rsa);
BIO_free(bio);

get1 会增加引用计数,因此您需要同时在 EVP_PKEY*RSA* 上调用 free

【讨论】:

    【解决方案2】:

    在@jww 的帮助下回答https://*.com/a/29885771/2692914。我想出了这个解决方案,希望没问题:

    bool isValidPublicKeyOnly(EVP_PKEY *pkey) {
        //EVP_PKEY_get_type from https://*.com/a/29885771/2692914
        int type = EVP_PKEY_get_type(pkey); //checks nullptr
        if (type != EVP_PKEY_RSA && type != EVP_PKEY_RSA2) {
            //not RSA
            return false;
        }
    
        RSA *rsa = EVP_PKEY_get1_RSA(pkey);
        if (!rsa) {
            return false;
        }
    
        bool isValid = isValidRSAPublicKeyOnly(rsa);
        RSA_free(rsa);
        return isValid;
    }
    
    bool isValidRSAPublicKeyOnly(RSA *rsa) {
        //from rsa_ameth.c do_rsa_print : has a private key
        //from rsa_chk.c RSA_check_key : doesn't have n (modulus) and e (public exponent)
        if (!rsa || rsa->d || !rsa->n || !rsa->e) {
            return false;
        }
        //from http://rt.openssl.org/Ticket/Display.html?user=guest&pass=guest&id=1454
        //doesnt have a valid public exponent
        return BN_is_odd(rsa->e) && !BN_is_one(rsa->e);
    }
    

    【讨论】:

    • 世界上有什么能让你想到一个 c++ 答案适用于一个 c 问题?它不会。它们是两种不同的语言(有充分的理由)。想要/需要 c 解决方案的人这样做是有充分理由的。
    • 我认为将此代码更改为 C 代码很简单,实际上您可以编辑答案。但是,为什么我不应该使用 C++?请给点建议好吗?
    • 然后用 C 重写它。特别是你的条件(即bool Crypto::isValidRSAPublicKeyOnly(RSA *rsa)
    【解决方案3】:

    我有一个类似的问题,我认为展示我对这个问题的解决方案可能是谨慎的。与 lmiguelmh 的解决方案不同,这个解决方案可以在 C 中使用。

    int checkRsaPublic(RSA *rsa, int debug) {
        if (!rsa) {
            printf("ERROR: RSA key not defined!\n");
            return 0;
        }
        //key
        const BIGNUM *n;
        const BIGNUM *e;
        const BIGNUM *d;
    
        //factors
        const BIGNUM *p;
        const BIGNUM *q;
    
        //crt_params
        const BIGNUM *dmp1;
        const BIGNUM *dmq1;
        const BIGNUM *iqmp;
    
        RSA_get0_key(rsa, &n, &e, &d);
        RSA_get0_factors(rsa, &p, &q);
        RSA_get0_crt_params(rsa, &dmp1, &dmq1, &iqmp);
    
        if (debug) {
            if (n) {
                printf("n is %s\n", BN_bn2hex(n));
            }
            if (e) {
                printf("e is %s\n", BN_bn2hex(e));
            }
            if (d) {
                printf("d is %s\n", BN_bn2hex(d));
            }
            if (p) {
                printf("p is %s\n", BN_bn2hex(p));
            }
            if (q) {
                printf("q is %s\n", BN_bn2hex(q));
            }
            if (dmp1) {
                printf("dmp1 is %s\n", BN_bn2hex(dmp1));
            }
            if (dmq1) {
                printf("dmq1 is %s\n", BN_bn2hex(dmq1));
            }
            if (iqmp) {
                printf("iqmp is %s\n", BN_bn2hex(iqmp));
            }
        }
    
        //RSA_check_key : doesn't have n (modulus) and e (public exponent)
        if (d || !n || !e) {
            printf("ERROR: RSA public key not well defined!\n");
            return 0;
        }
    
        if (BN_is_odd(e) && !BN_is_one(e)) {
            return 1;
        }
    
        printf("ERROR: Invalid public exponent.");
        return 0;
    }
    

    【讨论】: