有效创建数字签名的正确方法是什么?我可以使用DSA_sign_setup()吗?
What is the proper way to efficiently create digital signatures? Can I use DSA_sign_setup()?
我正在开发一个性能至关重要的应用程序。
在这个应用程序中,我有很多消息(即数千条)需要使用相同的私钥/公钥分别签名(当然和验证)。我正在使用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("%dn",a);
a=DSA_generate_key(set);
printf("%dn",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 %dn",a,size);
}
以上面提出的方式使用DSA_sign_setup()
实际上是完全不安全的,幸运的是,OpenSSL开发人员使DSA结构不透明,因此开发人员无法尝试强行
。DSA_sign_setup()
生成一个新的随机随机数(这是每个签名的临时密钥)。它永远不应该在同一个长期密钥下重复使用。从来没有。
从理论上讲,您仍然可以相对安全地为同一消息重用相同的随机数,但是一旦私钥和随机数的相同组合被重用于两个不同的消息,您就会泄露攻击者检索密钥所需的所有信息(参见Sony fail0verflow,这基本上是由于在ECDSA中重用随机数的相同错误)。
不幸的是,DSA很慢,特别是现在需要更长的密钥:为了加快应用程序的速度,您可以尝试使用ECDSA(例如,使用曲线NISTP256,仍然没有随机数重用)或Ed25519(确定性随机数)。
使用EVP_DigestSign
API 进行概念验证
更新:这是如何使用 OpenSSL 以编程方式生成签名的概念证明。 首选方法是使用EVP_DigestSign
API,因为它抽象出正在使用哪种非对称密钥。
以下示例扩展了此 OpenSSL wiki 页面中的 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=%lun", msg, slen);
}
printf("DONEn");
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)机器和树莓派 3 上运行了您的原始示例和代码。在这两种情况下,您的原始示例都不需要几十秒。 鉴于显然您在 OpenSSL 1.0.2 中使用不安全的DSA_sign_setup()
方法(除了一些有点昂贵的模块化算术之外,它还在内部消耗新的随机性)时看到了巨大的性能改进,我怀疑您实际上可能对 PRNG 有问题,它正在减慢新的随机随机数的生成速度,并且比模块化算术运算的影响更大。 如果是这种情况,您肯定会从使用 Ed25519 中受益,因为在这种情况下,随机数是确定性的而不是随机的(它是使用安全哈希函数并结合私钥和消息生成的)。 不幸的是,这意味着您需要等到OpenSSL 1.1.1发布(希望在今年夏天)。
在 Ed25519 上
要使用 Ed25519(从 OpenSSL 1.1.1开始本机支持),需要修改上述示例,因为在 OpenSSL 1.1.1 中不支持 Ed25519ph,而不是使用Init
/Update
/Final
流 API,您需要调用一次性EVP_DigestSign()
接口(请参阅文档)。
完全免责声明:下一段是我的libsuola研究项目的无耻插件,因为我绝对可以从其他用户的实际应用程序测试中受益。
或者,如果您迫不及待,我是一个名为libsuola
的 OpenSSLENGINE
的开发人员,该在 OpenSSL 1.0.2、1.1.0(以及使用替代实现的 1.1.1)中添加了对 Ed25519 的支持。它仍处于实验阶段,但它使用第三方实现(libsodium,HACL*,donna)用于加密部分,到目前为止,我的测试(用于研究目的)尚未发现突出的错误。
OP原样与矿的对标比较
为了解决一些评论,我编译并执行了OP的原始示例,一个略微修改的版本修复了一些错误和内存泄漏,以及如何使用EVP_DigestSign
API的示例,所有这些都针对OpenSSL 1.1.0h编译(编译为共享库,使用默认配置参数从发布压缩包中自定义前缀)。
完整的细节可以在这个要点中找到,其中包括我基准测试的确切版本,Makefile包含有关如何编译示例以及如何运行基准测试的所有详细信息,以及有关我的机器的详细信息(简而言之,它是一个四核i5-6500 @ 3.20GHz,并且频率缩放/Turbo boost从软件和UEFI中被禁用)。
从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 的原始代码和"固定"版本跳过它。 因此,上面报告的比率所证明的加速比更加显着!
TL;DR:在OpenSSL中有效使用数字签名的正确方法是通过EVP_DigestSign
API:尝试以上述方式使用DSA_sign_setup()
在OpenSSL 1.1.0和1.1.1中无效,并且在≤1.0.2中是错误的(如完全破坏DSA的安全性并泄露私钥)。我完全同意DSA API文档具有误导性,应该修复;不幸的是,函数DSA_sign_setup()
无法完全删除,因为次要版本必须保留二进制兼容性,因此即使对于即将发布的 1.1.1 版本,该符号也需要保留在那里(但在下一个主要版本中是一个很好的候选者)。
我决定删除这个答案,因为它损害了OpenSSL团队确保软件安全的努力。
如果您查看编辑,我发布的代码仍然可见,但不使用它,它是不安全的。 如果这样做,则可能会暴露您的私钥。
请不要说你没有被警告。 事实上,如果您在自己的代码中使用DSA_sign_setup()
,请将其视为警告,因为您不应该这样做。 Romen上面的回答对此有更多细节。 谢谢。
如果消息很大,通常对它们进行安全哈希并对哈希进行签名。这要快得多。当然,您需要传输消息、哈希和签名,并且检查过程必须包括重新哈希和检查相等性以及数字签名验证。
- 验证openssl c++中的签名,这是由JAVA DSA签名的?
- Coursera DSA 算法工具箱第 4 周第 2 个问题 - 分区纪念品
- 禁用/覆盖 -werror=sign-compare
- _InterlockedCompareExchange文档中"The sign is ignored"的含义
- 十进制数字的区域设置感知编辑控件子类化(格式[sign][xxx..][decimal separator][yy.])
- 使用openssl生成DSA密钥对
- DSA公钥大于私钥
- Openssl DSA sign
- c++中的Sign Extension是编译器选项,还是编译器相关的还是目标相关的?
- visual Sign Hash with windows Cryptography functions C++
- [-Wpointer-sign]在eclipse中禁用此检查