一、概述
在OpenSSL中,有多种签名验签算法,包括:RSA (Rivest-Shamir-Adleman)、DSA (Digital Signature Algorithm)、ECDSA (Elliptic Curve Digital Signature Algorithm)、EdDSA (Edwards-curve Digital Signature Algorithm)、SM2算法等。
本文主讲RSA签名验签算法的PSS填充模式。
本文对签名验签的机制只做简单介绍,若对签名验签机制存疑,可参考我的其他博客。
1. RSA签名流程
在OpenSSL中进行签名,需要一个私钥(通常是一个PEM或DER格式的密钥文件)。以下是一个简化的签名过程:
加载私钥:使用OpenSSL的API(如PEM_read_RSAPrivateKey或d2i_PrivateKey)从密钥文件中加载私钥。
计算数据的哈希值:使用OpenSSL的哈希函数(如SHA256)计算要签名的数据的哈希值。
签名哈希值:使用私钥对哈希值进行签名。
输出签名:将签名结果以适当的格式(如DER或PEM)输出。
2. RSA验签流程
在OpenSSL中进行验签,需要公钥和签名数据。以下是一个简化的验签过程:
加载公钥:使用OpenSSL的API(如PEM_read_RSAPublicKey或d2i_PUBKEY)从公钥文件中加载公钥。
计算数据的哈希值:与签名过程相同,使用相同的哈希函数计算要验签的数据的哈希值。
验签哈希值:使用公钥和签名数据对哈希值进行验签。
检查结果:根据验签函数的返回值判断签名是否有效。
3. RSA签名验签算法填充模式
常见的填充模式包括PKCS#1 v1.5填充、PKCS#1 OAEP填充和PSS填充。以下是它们的大致原理和特点:
PKCS#1 v1.5填充:
原理: 在进行RSA签名时,首先需要对消息进行填充以满足RSA算法的要求。PKCS#1 v1.5填充是一种较早期的填充模式,它在消息前面添加了一定数量的随机字节,以增加填充的随机性,然后再进行RSA加密。
特点:PKCS#1 v1.5填充是一种较为简单的填充模式,广泛应用于RSA签名和加密中。然而,它存在一些安全性方面的问题,如存在一些攻击方法可以利用其漏洞进行RSA签名伪造或密文恢复。
PKCS#1 OAEP填充:
原理:OAEP(Optimal Asymmetric Encryption Padding)是一种改进的填充模式,旨在提供更好的安全性。它引入了一个哈希函数和掩盖函数,将消息进行填充,以增加填充的随机性,并提供一定程度的抗攻击能力。
特点:PKCS#1 OAEP填充相对于PKCS#1 v1.5填充来说,提供了更好的安全性,能够抵御一些针对PKCS#1 v1.5填充的攻击。因此,在更高安全要求的场景下,推荐使用OAEP填充。
PSS填充:
原理:PSS(Probabilistic Signature Scheme)是一种基于随机化的签名填充方案,旨在提供更好的安全性和抗攻击能力。它引入了随机盐和哈希函数,将消息进行填充,并采用一定的概率分布来生成填充的结果。
特点:PSS填充提供了更好的安全性和抗攻击能力,相对于PKCS#1 v1.5填充和OAEP填充来说,更为推荐。它提供了更好的防护机制,能够抵御一些针对RSA签名填充的攻击,如长度扩展攻击等。
总的来说,PKCS#1 v1.5填充是最简单的填充模式,但安全性较低,OAEP填充提供了更好的安全性,而PSS填充则是最推荐的填充模式,提供了更高的安全性和抗攻击能力。
4. 注意事项
在进行签名验签时,要确保:
- 使用的哈希算法和签名算法,与公私钥生成时使用的算法相同;
- 签名和验签使用的算法填充模式相同。
二、调用函数介绍
本文主要使用openssl中,EVP高级函数来实现签名验签。
1. EVP高级函数简介
在OpenSSL中,可用于签名验签的接口很多,官方的封装上比较杂,本人在开发过程中倾向于全使用EVP打头的函数,整洁统一安全。
关于EVP高级函数,可以参考我的博客Linux C语言调用OpenSSL:EVP 接口概述
2. hash算法
RSA签名验签过程中,会对原文计算hash,涉及一些计算hash的openssl接口,这部分内容可以参考我的另一篇博客:
Linux C语言调用OpenSSL: 摘要算法(SHA-2和SHA-3)
3. EVP_PKEY_new
动态地分配并初始化一个 EVP_PKEY 结构体。这个结构体通常与特定的密钥类型(如 RSA、DSA、EC 等)关联,并且包含密钥的实际数据。
#include <openssl/evp.h>
EVP_PKEY *EVP_PKEY_new(void);
返回值:
如果成功,返回一个指向新分配的 EVP_PKEY 结构体的指针。如果失败,返回 NULL。
注意:
(1)通常不会直接使用 EVP_PKEY_new 来生成密钥。应该使用特定于密钥类型的函数(如 RSA_generate_key_ex、EC_KEY_generate_key 等)来生成密钥,并将生成的密钥设置为 EVP_PKEY 结构体的内容。
(2)当使用完 EVP_PKEY 结构体后,应该使用 EVP_PKEY_free 函数来释放它,以避免内存泄漏。
(3)如果在创建 EVP_PKEY 结构体后没有设置其密钥类型或内容,那么它就是一个“空”的或“未初始化”的 EVP_PKEY 结构体,并且可能无法用于任何有意义的操作。
4. EVP_PKEY_set1_RSA
用于将RSA密钥(RSA *类型)设置为EVP密钥(EVP_PKEY *类型)的RSA部分。
#include <openssl/evp.h>
#include <openssl/rsa.h>
int EVP_PKEY_set1_RSA(EVP_PKEY *pkey, struct rsa_st *key)
参数:
*EVP_PKEY pkey:这是一个指向EVP_PKEY结构的指针,该结构表示一个公