签名本质上就是: AAA = base64_encode(私钥加密(SHA1(消息体msg)))
验签本质上就是: BBB = 对方的公钥解密(base64_decode(AAA)) 和 CCC = SHA1(消息体MSG)
if (BBB == CCC) {
验签成功
}else{
验签失败
}
其中的SHA1算法, 可以根据需要替换为MD5, SHA256, SHA512都行, base64也不是必须的, 这里主要是为了把二进制转为字符串,方便用HTTP协议传输到服务器进行验签。用对方的公钥能解密数据,就证明这个数据确实是这小子的私钥加密的, 可以确认发送数据的人是阿毛还是阿狗。如果是网络数据传输,防止被偷窥,那就是用对方的公钥加密数据, 收到数据的人用自己的私钥解密, 和签名验签不一样。
国密的签名和验签函数有如下三个:
1、ECDSA_sign 和 ECDSA_verify
2、ECDSA_do_sign 和 ECDSA_do_verify
3、sm2_do_sign 和 sm2_do_verify
/** Computes ECDSA signature of a given hash value using the supplied private key (note: sig must point to ECDSA_size(eckey) bytes of memory).
* \param type 此参数可以被忽略 this parameter is ignored
* \param dgst 指向要签名的HASH值 pointer to the hash value to sign
* \param dgstlen HASH值的长度 length of the hash value
* \param sig 创建签名的DER加密内存块 memory for the DER encoded created signature
* \param siglen 指向签名的长度 pointer to the length of the returned signature
* \param eckey 包含私钥的EC_KEY对象 EC_KEY object containing a private EC key
* \ 返回1代表成功 return 1 on success and 0 otherwise
*/
int ECDSA_sign(int type, const unsigned char *dgst, int dgstlen, unsigned char *sig, unsigned int *siglen, EC_KEY *eckey);
/** Verifies that the given signature is valid ECDSA signature of the supplied hash value using the specified public key.
* \param type 此参数可以被忽略 this parameter is ignored
* \param dgst 指向HASH值的指针 pointer to the hash value
* \param dgstlen HASH值的长度 length of the hash value
* \param sig 指向DER编码后的签名 pointer to the DER encoded signature
* \param siglen 编码的签名长度 length of the DER encoded signature
* \param eckey 包含公钥的EC_KEY对象 EC_KEY object containing a public EC key
* \ 返回1代表签名有效 return 1 if the signature is valid, 0 if the signature is invalid and -1 on error
*/
int ECDSA_verify(int type, const unsigned char *dgst, int dgstlen, const unsigned char *sig, int siglen, EC_KEY *eckey);
/** 使用私钥计算给定的HASH值的ECDSA签名结构体 Computes the ECDSA signature of the given hash value using the supplied private key and returns the created signature.
* \param dgst 指向HASH值的指针 pointer to the hash value
* \param dgst_len HASH值的长度 length of the hash value
* \param eckey 包含私钥的EC_KEY对象 EC_KEY object containing a private EC key
* \ 返回ECDSA_SIG结构体的指针,返回NULL代表出错 return pointer to a ECDSA_SIG structure or NULL if an error occurred
*/
ECDSA_SIG *ECDSA_do_sign(const unsigned char *dgst, int dgst_len, EC_KEY *eckey);
/** Verifies that the supplied signature is a valid ECDSA signature of the supplied hash value using the supplied public key.
* \param dgst 指向HASH值的指针 pointer to the hash value
* \param dgst_len HASH值的长度 length of the hash value
* \param sig ECDSA_SIG 结构体指针 structure
* \param eckey 包含公钥的EC_KEY结构体 EC_KEY object containing a public EC key
* \ 签名成功返回1,失败返回-1 return 1 if the signature is valid, 0 if the signature is invalid and -1 on error
*/
int ECDSA_do_verify(const unsigned char *dgst, int dgst_len, const ECDSA_SIG *sig, EC_KEY *eckey);
/*
* SM2 签名操作. 计算Z值和签名H(Z) Computes Z and then signs H(Z || msg) using SM2
*/
私钥 摘要算法 ID值 ID的长度 签名的消息 消息长度
ECDSA_SIG *sm2_do_sign(const EC_KEY *key, const EVP_MD *digest, const uint8_t *id, const size_t id_len, const uint8_t *msg, size_t msg_len);
摘要算法 签名函数返回的结构体 ID值 ID的长度 签名的消息 消息长度
int sm2_do_verify(const EC_KEY *key, const EVP_MD *digest, const ECDSA_SIG *signature, const uint8_t *id, const size_t id_len, const uint8_t *msg, size_t msg_len);
下面是测试的demo例子,我编译的是国密的静态库,需要连接libssl_static.lib libcrypto_static.lib ws2_32.lib, 证书可以用 gmssl.exe 生成
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <openssl/asn1t.h>
#include <openssl/x509.h>
#include <openssl/rsa.h>
#include <openssl/dsa.h>
#include <openssl/err.h>
#include <openssl/x509.h>
#include <openssl/x509v3.h>
#include <openssl/pem.h>
#include <openssl/pkcs12.h>
#include <openssl/ui.h>
#include <openssl/safestack.h>
#include <openssl/bn.h>
#include <openssl/ssl.h>
#include "main.h"
//#pragma comment(lib,"legacy_stdio_definitions.lib")
//#define stdin (__acrt_iob_func(0))
//#define stdout (__acrt_iob_func(1))
//#define stderr (__acrt_iob_func(2))
//#define stdin (&__iob_func()[0])
//#define stdout (&__iob_func()[1])
//#define stderr (&__iob_func()[2])
//参数为私钥文件名,返回私钥
EVP_PKEY* load_privateKey(const char* file) {
BIO* in;
EVP_PKEY* key;
in = BIO_new_file(file, "r");
if (!in) return NULL;
key = PEM_read_bio_PrivateKey(in, NULL, 0, NULL);
BIO_free(in);
return key;
}
//参数为公钥文件名,返回公钥
EVP_PKEY* load_publicKey(const char* file) {
BIO* in;
EVP_PKEY* key;
in = BIO_new_file(file, "r");
if (!in) return NULL;
key = PEM_read_bio_PUBKEY(in, NULL, 0, NULL);
BIO_free(in);
return key;
}
//参数为证书文件名,返回证书中的公钥
EVP_PKEY* load_cert(const char* file)
{
STACK_OF(X509_INFO)* allcerts = NULL;
BIO* certs = NULL;
EVP_PKEY* pkey = NULL;
int i;
certs = BIO_new_file(file, "r");//bio_open_default(file, 'r', FORMAT_PEM);
if (certs == NULL) return NULL;
allcerts = PEM_X509_INFO_read_bio(certs, NULL, NULL, NULL);
for (i=0; i<sk_X509_INFO_num(allcerts); i++) {
X509_INFO* xinfo = sk_X509_INFO_value(allcerts, i);
if (xinfo->x509 != NULL) {
X509* xx = xinfo->x509;
pkey = X509_get_pubkey(xx);
break;
}
}
sk_X509_INFO_pop_free(allcerts, X509_INFO_free);
BIO_free(certs);
return pkey;
}
#define HASHED_DATA(p) (((unsigned char*)p)+15)
//计算 SHA-1 的签名HASH值
unsigned char* HashForSign(unsigned char *dst, unsigned int dst_size, unsigned char *src, unsigned int src_size)
{
unsigned char *buf = (unsigned char *)dst;
unsigned char sign_data[] =
{
0x30, 0x21, 0x30, 0x09, 0x06, 0x05, 0x2B, 0x0E,
0x03, 0x02, 0x1A, 0x05, 0x00, 0x04, 0x14,
};
//校验参数
if (dst == NULL || src == NULL || src_size == 0 || dst_size < 35)
{
return NULL;
}
//
memcpy(buf, sign_data, sizeof(sign_data));
// Hash
SHA1(src, src_size, HASHED_DATA(buf));
return buf;
}
int main(int argc, char** argv)
{
//msg为原始消息, 这里随便写
unsigned char* msg = (unsigned char*)"aaaaaaaaaaaaaaaaaaaaaaa";
unsigned char sha[64] = {0};
//用sha1算法计算msg的HASH值,
unsigned char* sha1 = HashForSign(sha, sizeof(sha), msg, strlen((const char*)msg));
int res1, res2;
unsigned char sig[512] = {0};
unsigned int siglen = sizeof(sig);
///加载签名证书的私钥文件
EVP_PKEY* evpPrvKey = load_privateKey("./ClientSign.key");
EC_KEY* ecPrvKey = EVP_PKEY_get1_EC_KEY(evpPrvKey);
//加载签名证书,从证书中获取公钥,也可以直接加载公钥文件
EVP_PKEY* evpPubKey = load_cert("./ClientSign.crt");
EC_KEY* ecPubKey = EVP_PKEY_get1_EC_KEY(evpPubKey);
//进行签名,返回1代表成功
res1 = ECDSA_sign(0, sha1, sizeof(sha), sig, &siglen, ecPrvKey);
//如果要让服务器验签则把sig用base64加密后传给服务器即可。
//进行验签,返回1代表成功
res2 = ECDSA_verify(0, sha1, sizeof(sha), sig, siglen, ecPubKey);
printf("-------res1=%d, res2=%d--------\n", res1, res2);
return 0;
}