2021SC@SDUSC 17.1 RSA介绍
RSA公钥算法被广泛使用。 它的密钥包括公钥和私钥。于数字签名、身份认证和密钥交换。 RSA密钥长度一般为1024位或更高。RSA密钥信息主要包括[1]:
n : 模数 e : 公钥指数 d : 私钥指数 p : 最初的大素数 q : 最初的大素数 dmp1 : e*dmp1 = 1 (mod (p-1)) dmq1 : e*dmq1 = 1 (mod (q-1)) iqmp : q*iqmp = 1 (mod p )
公钥为n和e;私钥为n和d。 在实际应用中,公钥加密通常用于协商密钥;私钥加密通常用于签名。
17.2 openssl的RSA实现
Openssl的RSA实现源码在crypto/rsa目录下。它实现了RSA PKCS1标准。主要源代码如下:
1) rsa.h 定义RSA以及数据结构RSA_METHOD,定义了RSA各种函数。 2) rsa_asn1.c 实现了RSA密钥的DER编码和解码,包括公钥和私钥。 3) rsa_chk.c RSA密钥检查。 4) rsa_eay.c Openssl实现的一种RSA_METHOD,作为一种默认RSA计算实现方法。 本文件未实现rsa_sign、rsa_verify和rsa_keygen回调函数。 5)rsa_err.c RSA错误处理。 6)rsa_gen.c RSA如果RSA_METHOD中的rsa_keygen如果回调函数不是空的,则调用它, 否则,调用其内部实现。 7)rsa_lib.c 主要实现了RSA运算的四个函数(公钥/私钥,加密/解密), 都调用了RSA_METHOD相应的回调函数。 8)rsa_none.c 实现了一种填充和去填充。 9)rsa_null.c 一种空的实现RSA_METHOD。 10) rsa_oaep.c 实现了oaep填充和填充。 11)rsa_pk1. 实现了pkcs填充和去填充。 12)rsa_sign.c 实现了RSA签名和验签。 13)rsa_ssl.c 实现了ssl填充。 14)rsa_x931.c 实现了填充和去填充。
17.3 RSA签名和验证过程
RSA签名流程如下:
- 摘要用户数据; 2)构造X509_SIG结构并DER编码包括摘要算法和摘要结果。 3)填充2)结果,填充RSA密钥长度字节数。 比如1024位RSA密钥必须填满128字节。具体填充方法由用户指定。 4)用于3)结果RSA私钥加密。
RSA_eay_private_encrypt3)和4)函数实现过程。
RSA验签过程是上述过程的逆过程,如下:
- 对数据用RSA公钥解密,在签名过程中得到2)结果。
- 去除1)填充结果。
- 摘要算法和摘要结果从2)结果中得到。
- 摘要算法将原始数据按3)中的摘要算法计算。 5)比较4)与签名过程中1)的结果。
RSA_eay_public_decrypt实现1)和2)过程。
17.4 数据结构
RSA定义主要数据结构crypto/rsa/rsa.h中: 17.4.1 RSA_METHOD
struct rsa_meth_st {
const char *name;
int (*rsa_pub_enc)(int flen, const unsigned char *from, unsigned char *to, RSA *rsa,int padding);
int (*rsa_pub_dec)(int flen, const unsigned char *from, unsigned char *to, RSA *rsa,int padding);
int (*rsa_priv_enc)(int flen, const unsigned char *from, unsigned char *to, RSA *rsa,int padding);
int (*rsa_priv_dec)(int flen, const unsigned char *from, unsigned char *to, RSA *rsa,int padding);
/* 其他函数 */
int (*rsa_sign)(int type, const unsigned char *m, unsigned int m_length,
unsigned char *sigret, unsigned int *siglen, const RSA *rsa);
int (*rsa_verify)(int dtype,const unsigned char *m, unsigned int m_length,
unsigned char *sigbuf, unsigned int siglen, const RSA *rsa);
int (*rsa_keygen)(RSA *rsa, int bits, BIGNUM *e, BN_GENCB *cb);
};
主要项说明: name : RSA_METHOD名称; rsa_pub_enc : 公钥加密函数,padding为其填充方式,输入数据不能太长,否则无法填充; rsa_pub_dec : 公钥解密函数,padding为其去除填充的方式,输入数据长度为RSA密钥长度的字节数; rsa_priv_enc : 私钥加密函数,padding为其填充方式,输入数据长度不能太长,否则无法填充; rsa_priv_dec : 私钥解密函数,padding为其去除填充的方式,输入数据长度为RSA密钥长度的字节数; rsa_sign : 签名函数; rsa_verify : 验签函数; rsa_keygen : RSA密钥对生成函数。
用户可实现自己的RSA_METHOD来替换openssl提供的默认方法。
17.4.2 RSA
RSA数据结构中包含了公/私钥信息(如果仅有 n 和 e,则表明是公钥),定义如下:
struct rsa_st {
/* 其他 */
const RSA_METHOD *meth;
ENGINE *engine;
BIGNUM *n;
BIGNUM *e;
BIGNUM *d;
BIGNUM *p;
BIGNUM *q;
BIGNUM *dmp1;
BIGNUM *dmq1;
BIGNUM *iqmp;
CRYPTO_EX_DATA ex_data;
int references;
/* 其他数据项 */
};
各项意义: meth :RSA_METHOD结构,指明了本RSA密钥的各种运算函数地址; engine:硬件引擎; n,e,d,p,q,dmp1,dmq1,iqmp:RSA密钥的各个值; ex_data:扩展数据结构,用于存放用户数据; references:RSA结构引用数。
17.5 主要函数
-
RSA_check_key
检查RSA密钥。
2)RSA_new
生成一个RSA密钥结构,并采用默认的rsa_pkcs1_eay_meth RSA_METHOD方法。
3)RSA_free
释放RSA结构。
-
RSA *RSA_generate_key( int bits, unsigned long e_value, void (*callback)(int,int,void *), void *cb_arg )
生成RSA密钥. bits : 是模数比特数, e_value : 是公钥指数e, callback : 回调函数由用户实现,用于干预密钥生成过程中的一些运算,可为空。
5)RSA_get_default_method
获取默认的 RSA_METHOD,为 rsa_pkcs1_eay_meth。
6)RSA_get_ex_data 获取扩展数据。
7)RSA_get_method 获取RSA结构的 RSA_METHOD。
8)RSA_padding_add_none RSA_padding_add_PKCS1_OAEP RSA_padding_add_PKCS1_type_1(私钥加密的填充) RSA_padding_add_PKCS1_type_2(公钥加密的填充) RSA_padding_add_SSLv23
各种填充方式函数。
9)RSA_padding_check_none RSA_padding_check_PKCS1_OAEP RSA_padding_check_PKCS1_type_1 RSA_padding_check_PKCS1_type_2 RSA_padding_check_SSLv23 RSA_PKCS1_SSLeay 各种去除填充函数。
10)int RSA_print(BIO *bp, const RSA *x, int off)
将RSA信息输出到 BIO 中,off 为输出信息在BIO中的偏移量,
比如是屏幕BIO,则表示打印信息的位置离左边屏幕边缘的距离。
11)int DSA_print_fp(FILE *fp, const DSA *x, int off) 将RSA信息输出到FILE中,off为输出偏移量。
12)RSA_public_decrypt
RSA公钥解密。
13)RSA_public_encrypt
RSA公钥加密。
14)RSA_set_default_method/ RSA_set_method
设置RSA结构中的method,当用户实现了一个RSA_METHOD时,
调用此函数来设置,使RSA运算采用用户的方法。
15)RSA_set_ex_data
设置扩展数据。
16)RSA_sign
RSA签名。
17)RSA_sign_ASN1_OCTET_STRING
另外一种RSA签名,不涉及摘要算法,它将输入数据作为 ASN1_OCTET_STRING 进行 DER 编码,
然后直接调用 RSA_private_encrypt 进行计算。
18)RSA_size
获取RSA密钥长度字节数。
19)RSA_up_ref
给RSA密钥增加一个引用。
20)RSA_verify
RSA验证。
21)RSA_verify_ASN1_OCTET_STRING
另一种 RSA 验证,不涉及摘要算法,与 RSA_sign_ASN1_OCTET_STRING 对应。
22)RSAPrivateKey_asn1_meth
获取 RSA 私钥的 ASN1_METHOD,包括 i2d、d2i、new 和 free 函数地址。
23)RSAPrivateKey_dup
复制RSA私钥。
24)RSAPublicKey_dup
复制RSA公钥。
TYPE *d2i_TYPE(TYPE **a, unsigned char **ppin, long length);
TYPE *d2i_TYPE_bio(BIO *bp, TYPE **a);
TYPE *d2i_TYPE_fp(FILE *fp, TYPE **a);
int i2d_TYPE(TYPE *a, unsigned char **ppout);
int i2d_TYPE_fp(FILE *fp, TYPE *a);
int i2d_TYPE_bio(BIO *bp, TYPE *a);
Type : d2i_RSAPrivateKey, d2i_RSAPrivateKey_bio, d2i_RSAPrivateKey_fp, d2i_RSAPublicKey, d2i_RSAPublicKey_bio, d2i_RSAPublicKey_fp, d2i_RSA_OAEP_PARAMS, d2i_RSA_PSS_PARAMS, d2i_RSA_PUBKEY, d2i_RSA_PUBKEY_bio, d2i_RSA_PUBKEY_fp
i2d_RSAPrivateKey, i2d_RSAPrivateKey_bio, i2d_RSAPrivateKey_fp,
i2d_RSAPublicKey, i2d_RSAPublicKey_bio, i2d_RSAPublicKey_fp,
i2d_RSA_OAEP_PARAMS,
i2d_RSA_PSS_PARAMS,
i2d_RSA_PUBKEY, i2d_RSA_PUBKEY_bio, i2d_RSA_PUBKEY_fp
17.6 编程示例 17.6.1 密钥生成
Simple Public Key Encryption with RSA and OpenSSL
#include <openssl/rsa.h>
int main()
{
RSA *r;
int bits=512,ret;
unsigned long e=RSA_3;
BIGNUM *bne;
r=RSA_generate_key(bits,e,NULL,NULL); //不推荐使用
RSA_print_fp(stdout,r,11);
RSA_free(r);
bne=BN_new();
ret=BN_set_word(bne,e);
r=RSA_new();
ret=RSA_generate_key_ex(r,bits,bne,NULL);
if(ret!=1) {
printf("RSA_generate_key_ex err!\n");
return -1;
}
printf("\n-------------\n");
RSA_print_fp(stdout,r,11);
RSA_free(r);
return 0;
}
17.6.2 RSA加解密运算
#include <openssl/rsa.h>
#include <openssl/sha.h>
int main()
{
RSA *r;
int bits=1024, ret,len, flen, padding,i;
unsigned long e = RSA_3;
BIGNUM *bne;
unsigned char *key,*p;
BIO *b;
unsigned char from[500],to[500],out[500];
bne=BN_new();
ret=BN_set_word(bne,e);
r=RSA_new();
ret=RSA_generate_key_ex(r,bits,bne,NULL);
if(ret!=1) {
printf("RSA_generate_key_ex err!\n"); return -1;
}
/* 私钥 i2d */
b=BIO_new(BIO_s_mem());
ret=i2d_RSAPrivateKey_bio(b,r);
key=malloc(1024);
len=BIO_read(b,key,1024);
BIO_free(b);
b=BIO_new_file("rsa.key","w");
ret=i2d_RSAPrivateKey_bio(b,r);
BIO_free(b);
/* 私钥 d2i */
/* 公钥 i2d */
/* 公钥 d2i */
/* 私钥加密 */
flen=RSA_size(r);
printf("please select private enc padding : \n");
printf("1.RSA_PKCS1_PADDING\n");
printf("3.RSA_NO_PADDING\n");
printf("5.RSA_X931_PADDING\n");
scanf("%d",&padding);
if(padding==RSA_PKCS1_PADDING)
flen-=11;
else if(padding==RSA_X931_PADDING)
flen-=2;
else if(padding==RSA_NO_PADDING)
flen=flen;
else{
printf("rsa not surport !\n"); return -1;
}
for(i=0;i<flen;i++)
memset(&from[i],i,1);
len=RSA_private_encrypt(flen,from,to,r,padding);
if(len<=0){
printf("RSA_private_encrypt err!\n");
return -1;
}
len=RSA_public_decrypt(len,to,out,r,padding);
if(len<=0){
printf("RSA_public_decrypt err!\n");
return -1;
}
if(memcmp(from,out,flen)) {
printf("err!\n");
return -1;
}
/* */
printf("please select public enc padding : \n");
printf("1.RSA_PKCS1_PADDING\n");
printf("2.RSA_SSLV23_PADDING\n");
printf("3.RSA_NO_PADDING\n");
printf("4.RSA_PKCS1_OAEP_PADDING\n");
scanf("%d",&padding);
flen=RSA_size(r);
if(padding==RSA_PKCS1_PADDING)
flen-=11;
else if(padding==RSA_SSLV23_PADDING)
flen-=11;
else if(padding==RSA_X931_PADDING)
flen-=2;
else if(padding==RSA_NO_PADDING)
flen=flen;
else if(padding==RSA_PKCS1_OAEP_PADDING)
flen=flen-2 * SHA_DIGEST_LENGTH-2 ;
else {
printf("rsa not surport !\n"); return -1;
}
for(i=0;i<flen;i++)
memset(&from[i],i+1,1);
len=RSA_public_encrypt(flen,from,to,r,padding);
if(len<=0){
printf("RSA_public_encrypt err!\n");
return -1;
}
len=RSA_private_decrypt(len,to,out,r,padding);
if(len<=0) {
printf("RSA_private_decrypt err!\n");
return -1;
}
if(memcmp(from,out,flen)) {
printf("err!\n");
return -1;
}
printf("test ok!\n"); RSA_free(r); return 0;
}
17.6.3 签名与验证
#include <string.h>
#include <openssl/objects.h>
#include <openssl/rsa.h>
int main()
{
int ret;
RSA *r;
int i, bits=1024, signlen, datalen, alg, nid;
unsigned long e = RSA_3;
BIGNUM *bne;
unsigned char data[100],signret[200];
bne=BN_new();
ret=BN_set_word(bne,e);
r=RSA_new();
ret=RSA_generate_key_ex(r,bits,bne,NULL);
if(ret!=1) {
printf("RSA_generate_key_ex err!\n");
return -1;
}
for(i = 0;i < 100;i ++){
memset(&data[i], i+1, 1);
}
printf("please select digest alg: \n");
printf("1.NID_md5\n");
printf("2.NID_sha\n");
printf("3.NID_sha1\n");
printf("4.NID_md5_sha1\n");
scanf("%d",&alg);
if(alg == 1) {
datalen = 55;
nid=NID_md5;
}
else if(alg == 2) {
datalen = 55;
nid=NID_sha;
}
else if(alg == 3) {
datalen = 55;
nid=NID_sha1;
}
else if(alg == 4) {
datalen = 36;
nid=NID_md5_sha1;
}
ret=RSA_sign(nid,data,datalen,signret,&signlen,r);
if(ret != 1) {
printf("RSA_sign err!\n");
RSA_free(r);
return -1;
}
ret=RSA_verify(nid,data,datalen,signret,signlen,r);
if(ret!=1) {
printf("RSA_verify err!\n");
RSA_free(r);
return -1;
}
RSA_free(r);
printf("test ok!\n");
return 0;
}
注意:本示例并不是真正的数据签名示例,因为没有做摘要计算。
ret=RSA_sign(nid,data,datalen,signret,&signlen,r)
将需要运算的数据放入X509_ALGOR数据结构并将其DER编码,
编码结果做RSA_PKCS1_PADDING 再进行私钥加密。
被签名数据应该是摘要之后的数据,
而本例没有先做摘要,接将数据拿去做运算.
因此 datalen 不能太长,保证 RSA_PKCS1_PADDING 私钥加密运算时输入数据的长度限制。
ret = RSA_verify(nid,data,datalen,signret,signlen,r)用来验证签名。