有效创建数字签名的正确方法是什么?我可以使用DSA_sign_setup()吗?

时间:2018-06-07 21:28:25

标签: c++ openssl digital-signature dsa

我正在开发一个性能至关重要的应用程序。

在这个应用程序中,我需要使用相同的私钥/公钥分别签署(并且当然验证)许多消息(即数千个)。我正在使用OpenSSL库。

使用DSA功能的天真方法(见下文)需要几十秒才能签名,这不太好。我尝试使用DSA_sign_setup()函数来加快速度,但我无法弄清楚使用它的正确方法。

我也尝试过ECDSA,但是在获得正确配置方面我很迷失。

如果我真的关心效率,这样做的正确方法是什么?

#include <openssl/dsa.h>
#include <openssl/engine.h>
#include <stdio.h>
#include <openssl/evp.h>

int N=3000;

int main()
{
    DSA *set=DSA_new();
    int a;
    a=DSA_generate_parameters_ex(set,1024,NULL,1,NULL,NULL,NULL);
    printf("%d\n",a);
    a=DSA_generate_key(set);
    printf("%d\n",a);
    unsigned char msg[]="I am watching you!I am watching you!";
    unsigned char sign[256];
    unsigned int size;
    for(int i=0;i<N;i++)
        a=DSA_sign(1,msg,32,sign,&size,set);
    printf("%d %d\n",a,size);
}

3 个答案:

答案 0 :(得分:2)

我决定删除这个答案,因为它会影响OpenSSL团队使他们的软件安全的努力。

如果您查看编辑但我发布的代码仍然可见,但不使用它,不安全。如果您这样做,则冒着暴露您的私钥的风险。

请不要说你没有被警告过。事实上,如果您在自己的代码中使用DSA_sign_setup()会将其视为警告,因为您不应该这样做。 Romen的上述答案有更多细节。谢谢。

答案 1 :(得分:2)

以上面提出的方式使用DSA_sign_setup()实际上是完全不安全的,幸运的是OpenSSL开发人员使DSA结构变得不透明,以便开发人员不能试图强行推进。

DSA_sign_setup()生成一个新的随机现时(每个签名都是一个短暂的密钥)。它应该从不在相同的长期密钥下重用。 <强>从不

理论上,对于相同的消息重用同一个nonce,理论上仍然可以相对安全,但只要私有密钥和nonce的相同组合被重用于两个不同的消息,您只需揭示攻击者需要检索的所有信息。秘密密钥(参见Sony fail0verflow,这主要是由于在使用ECDSA重用nonce时犯了同样的错误)。

不幸的是,DSA很慢,特别是现在需要更长的密钥:为了加速您的应用程序,您可以尝试使用ECDSA(例如,使用曲线NISTP256,仍然没有nonce重用)或Ed25519(确定性nonce)。

使用EVP_DigestSign API

的概念证明

更新:这里是如何使用OpenSSL以编程方式生成签名的概念证明。 首选方法是使用EVP_DigestSign API,因为它抽象出使用了哪种非对称密钥。

以下示例扩展了this OpenSSL wiki page中的PoC:我测试了它使用DSA或NIST P-256私钥,OpenSSL 1.0.2,1.1.0和1.1.1-pre6。

#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <openssl/pem.h>
#include <openssl/err.h>
#include <openssl/evp.h>

#define KEYFILE "private_key.pem"
#define N 3000
#define BUFFSIZE 80

EVP_PKEY *read_secret_key_from_file(const char * fname)
{
    EVP_PKEY *key = NULL;
    FILE *fp = fopen(fname, "r");
    if(!fp) {
        perror(fname); return NULL;
    }
    key = PEM_read_PrivateKey(fp, NULL, NULL, NULL);
    fclose(fp);
    return key;
}

int do_sign(EVP_PKEY *key, const unsigned char *msg, const size_t mlen,
            unsigned char **sig, size_t *slen)
{
    EVP_MD_CTX *mdctx = NULL;
    int ret = 0;

    /* Create the Message Digest Context */
    if(!(mdctx = EVP_MD_CTX_create())) goto err;

    /* Initialise the DigestSign operation - SHA-256 has been selected
     * as the message digest function in this example */
    if(1 != EVP_DigestSignInit(mdctx, NULL, EVP_sha256(), NULL, key))
        goto err;

    /* Call update with the message */
    if(1 != EVP_DigestSignUpdate(mdctx, msg, mlen)) goto err;

    /* Finalise the DigestSign operation */
    /* First call EVP_DigestSignFinal with a NULL sig parameter to
     * obtain the length of the signature. Length is returned in slen */
    if(1 != EVP_DigestSignFinal(mdctx, NULL, slen)) goto err;
    /* Allocate memory for the signature based on size in slen */
    if(!(*sig = OPENSSL_malloc(*slen))) goto err;
    /* Obtain the signature */
    if(1 != EVP_DigestSignFinal(mdctx, *sig, slen)) goto err;

    /* Success */
    ret = 1;

err:
    if(ret != 1)
    {
        /* Do some error handling */
    }

    /* Clean up */
    if(*sig && !ret) OPENSSL_free(*sig);
    if(mdctx) EVP_MD_CTX_destroy(mdctx);

    return ret;
}

int main()
{
    int ret = EXIT_FAILURE;
    const char *str = "I am watching you!I am watching you!";
    unsigned char *sig = NULL;
    size_t slen = 0;
    unsigned char msg[BUFFSIZE];
    size_t mlen = 0;

    EVP_PKEY *key = read_secret_key_from_file(KEYFILE);
    if(!key) goto err;

    for(int i=0;i<N;i++) {
        if ( snprintf((char *)msg, BUFFSIZE, "%s %d", str, i+1) < 0 )
            goto err;
        mlen = strlen((const char*)msg);
        if (!do_sign(key, msg, mlen, &sig, &slen)) goto err;
        OPENSSL_free(sig); sig = NULL;
        printf("\"%s\" -> siglen=%lu\n", msg, slen);
    }

    printf("DONE\n");
    ret = EXIT_SUCCESS;

err:
    if (ret != EXIT_SUCCESS) {
        ERR_print_errors_fp(stderr);
        fprintf(stderr, "Something broke!\n");
    }

    if (key)
        EVP_PKEY_free(key);

    exit(ret);
}

生成密钥

# Generate a new NIST P-256 private key
openssl ecparam -genkey -name prime256v1 -noout -out private_key.pem

的性能/随机性

我在我的(英特尔Skylake)机器和Raspberry Pi 3上运行了您的原始示例和我的代码。在这两种情况下,您的原始示例不需要几十秒。 鉴于您显然在OpenSSL 1.0.2中使用不安全 DSA_sign_setup()方法(内部消耗新的随机性,除了一些有些昂贵的模块算法)之外,我看到了巨大的性能提升,我怀疑你可能实际上有一个PRNG问题,它正在减慢新随机nonce的产生,并且比模块化算术运算产生更大的影响。 如果是这种情况,您可能肯定会受益于使用Ed25519,因为在这种情况下,随机数是确定性的而不是随机的(它是使用安全散列函数生成并将私钥和消息组合在一起)。 不幸的是,这意味着你需要等到OpenSSL 1.1.1发布(希望在今年夏天)。

在Ed25519上

要使用Ed25519(本机将从OpenSSL 1.1.1开始支持),需要修改上述示例,如在OpenSSL 1.1.1中,不支持Ed25519ph而不是使用Init /您需要调用一次性Update界面的Final / EVP_DigestSign()流API(请参阅documentation)。

完全免责声明下一段是我的libsuola研究项目的无耻插件,因为我可以从其他用户的实际应用程序测试中获益

或者,如果您不能等待,我是一个名为ENGINE的OpenSSL libsuola的开发人员,它在OpenSSL 1.0.2,1.1.0中增加了对Ed25519的支持(以及使用替代方案的1.1.1)实现)。它仍然是实验性的,但它使用第三方实现(libsodium,HACL *,donna)作为加密部分,到目前为止,我的测试(用于研究目的)还没有发现突出的错误。

OP原始示例与我的

的基准比较

为了解决一些评论,我编译并执行了OP的原始示例,一个修改了一些错误和内存泄漏的略微修改的版本,以及我如何使用EVP_DigestSign API的示例,所有已编译针对OpenSSL 1.1.0h(使用默认配置参数从发行版tarball编译为自定义前缀的共享库)。

完整的详细信息可以在this gist找到,其中包括我基准测试的确切版本,Makefile包含有关如何编译示例以及如何运行基准测试的所有详细信息,以及有关我的机器的详细信息(简要说明)它是四核i5-6500 @ 3.20GHz,并且从软件和UEFI禁用频率缩放/ Turbo提升。)

make_output.txt可以看出:

Running ./op_example
time ./op_example >/dev/null
0.32user 0.00system 0:00.32elapsed 100%CPU (0avgtext+0avgdata 3452maxresident)k
0inputs+0outputs (0major+153minor)pagefaults 0swaps

Running ./dsa_example
time ./dsa_example >/dev/null
0.42user 0.00system 0:00.42elapsed 100%CPU (0avgtext+0avgdata 3404maxresident)k
0inputs+0outputs (0major+153minor)pagefaults 0swaps

Running ./evp_example
time ./evp_example >/dev/null
0.12user 0.00system 0:00.12elapsed 99%CPU (0avgtext+0avgdata 3764maxresident)k
0inputs+0outputs (0major+157minor)pagefaults 0swaps

这表明通过EVP_DigestSign API在NIST P-256上使用ECDSA比原始OP的示例快2.66倍,比修正版本快3.5倍。

作为一个后期补充说明,本答案中的代码还计算输入明文的SHA256摘要,而OP的原始代码和&#34;固定&#34;版本跳过它。 因此,上面报道的比率表明的加速更为显着!

TL; DR :在OpenSSL中有效使用数字签名的正确方法是通过EVP_DigestSign API:尝试以上述方式使用DSA_sign_setup()无效OpenSSL 1.1.0和1.1.1,错误(如完全破坏DSA的安全性并揭示私钥)≤1.0.2。我完全同意DSA API documentation具有误导性,应予以修正;遗憾的是,函数DSA_sign_setup()无法完全删除,因为次要版本必须保持二进制兼容性,因此符号需要保留在那里,即使是即将发布的1.1.1版本(但在下一个主要版本中删除它是一个很好的候选者)。

答案 2 :(得分:0)

如果消息很大,通常会对它们进行安全散列并对散列进行签名。这要快得多。当然,您需要传输消息,散列和签名,并且检查过程必须包括重新散列和检查相等性以及数字签名验证。

相关问题