本文介绍一种方法使得通过RSA_private_decrypt函数可以使用CSP中的私钥来进行解密和通过RSA_sign函数可以使用CSP中的私钥来进行签名。
在openssl中RSA结构既可以表示公钥也可以表示私钥,它有一个类型为RSA_METHOD的域meth,该域包含一组实现RSA运算的函数指针。为了可以调用CSP,我们只需要实现一个特殊的RSA私钥,它包含有自己的RSA_METHOD结构。由于很多CSP中通常不都能获取明文的私钥,所以,我们实现的RSA私钥结构中只能包含有公钥信息,即n和e,然后根据公钥寻找到匹配的私钥句柄来进行相关的操作。具体来说,我们实现了一个函数:RSA *RSAToCAPIPrivKey(RSA *rsa)。该函数的参数是个RSA公钥,返回的是一个能够调用CSP私钥的RSA私钥。
具体的实现如下:
1、从个人证书库中寻找存在私钥且和给定公钥相匹配的证书,如果不存在,则返回NULL
2、用RSA_new()创建一个新的RSA结构,把公钥的n和e复制过去
3、设置新的RSA结构的flags为RSA_FLAG_SIGN_VER,并为之设置一个可以调用CSP的RSA_METHOD结构
4、返回该RSA结构。
实现的关键是RSA_METHOD结构。
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_mod_exp)(BIGNUM *r0,const BIGNUM *I,RSA *rsa,BN_CTX *ctx);
int (*bn_mod_exp)(BIGNUM *r, const BIGNUM *a, const BIGNUM *p,
const BIGNUM *m, BN_CTX *ctx,BN_MONT_CTX *m_ctx);
int (*init)(RSA *rsa);
int (*finish)(RSA *rsa);
int flags;
char *app_data;
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);
};
typedef struct rsa_meth_st RSA_METHOD;
name--是RSA_METHOD的名称
rsa_pub_enc--是RSA公钥加密的实现,RSA_public_encrypt就是由它实现的。我们不需要实现它。
rsa_pub_dec--是RSA公钥解密的实现,RSA_public_decrypt就是由它实现的。我们也不需要实现它。如果RSA结构的flags域没有设置了RSA_FLAG_SIGN_VER或者没有实现rsa_verify,则RSA_verify也会使用它来验证签名。
rsa_priv_enc--是RSA私钥加密的实现,RSA_private_encrypt就是由它实现的。我不知道怎么实现它。如果RSA结构的flags域没有设置了RSA_FLAG_SIGN_VER或者没有实现rsa_sign,则RSA_sign也会使用它来进行签名。
rsa_priv_dec--是RSA私钥解密的实现,RSA_private_decrypt就是由它实现的。这是我们要实现的。
rsa_mod_exp和bn_mod_exp是用来辅佐实现RSA算法的某些大整数运算的实现,具体我不清楚。我们也不需要实现它。
init--初始化函数,RSA_new会调用它。我们也不需要实现它。
finish--清理函数,RSA_free会调用它。我们也不需要实现它。
flags--某些标志
app_data--某些相关数据。我们也不需要它。
rsa_sign--如果RSA结构的flags域设置了RSA_FLAG_SIGN_VER(也就是rsa->flags & RSA_FLAG_SIGN_VER为真)且rsa_sign不为NULL的时候,RSA_sign会使用它来进行签名。这是我们要实现的。
rsa_verify--如果RSA结构的flags域设置了RSA_FLAG_SIGN_VER(也就是rsa->flags & RSA_FLAG_SIGN_VER为真)且rsa_verify不为NULL的时候,RSA_verify会使用它来验证签名。我们也不需要实现它。
rsa_keygen--产生RSA密钥对的实现。如果不为NULL,RSA_generate_key_ex会使用它,否则使用内置的RSA密钥对的实现。我们也不需要实现它。
rsa_priv_dec的实现如下:
1、从个人证书库中寻找存在私钥且和给定公钥相匹配的证书,并获取其CERT_KEY_PROV_INFO_PROP_ID属性的值
2、根据获取到的CSP信息,获得私钥句柄
3、把要解密的数据的格式从big-endian转换为little-endian。openssl使用big-endian格式,而CryptoAPI通常要求little-endian格式。
4、调用CryptDecrypt解密即可。
rsa_sign的实现如下:
1、从个人证书库中寻找存在私钥且和给定公钥相匹配的证书,并获取其CERT_KEY_PROV_INFO_PROP_ID属性的值
2、根据获取到的CSP信息,获得容器句柄
3、使用容器句柄创建一个对应的hash对象,并设定hash值。
4、使用CryptSignHash签名
5、把签名得到的数据从little-endian转换为big-endian。
该程序使用openssl-0.9.8在windows2000和XP下测试通过。
存在的问题:
1、只能在windows2000、XP下使用,不能在98、Me中使用,原因是使用了CryptAcquireContextW函数。其实很容易把得到的CERT_KEY_PROV_INFO_PROP_ID属性的值的各宽字符串转换为多字节字符串,使用CryptAcquireContextA函数就可以在在98和Me使用了。
2、获取私钥句柄只是搜索个人证书库,可能会有些CSP的私钥无法使用。
3、没有使用openssl的错误报告函数给出详细的错误信息
4、解密的时候存在很多限制,这些限制和CryptDecrypt的一样。有些CSP根本不支持CryptDecrypt使用RSA解密,有的只有Algid为AT_KEYEXCHANGE才能进行解密。
OpenVPN已经实现了可以看它的这个函数SSL_CTX_use_CryptoAPI_certificate
- #include "openssl/rsa.h"
- #include "openssl/obj_mac.h"
- #include <windows.h>
- #include <string.h>
- static int capi_rsa_priv_dec(int flen,const unsigned char *from,unsigned char *to,RSA *rsa,int padding);
- static int capi_rsa_sign(int type,const unsigned char *m, unsigned int m_length,unsigned char *sigret, unsigned int *siglen, const RSA *rsa);
- static RSA *CAPI_RSA_new();
- static PCRYPT_KEY_PROV_INFO GetProvInfo(const RSA *rsa);
- static RSA_METHOD capirsameth=
- {
- "Windows Crypto API RSA Method", /* name */
- 0, /* rsa_pub_enc */
- 0, /* rsa_pub_dec */
- 0, /* rsa_priv_enc */
- capi_rsa_priv_dec, /* rsa_priv_dec */
- 0, /* rsa_mod_exp */
- 0, /* bn_mod_exp */
- 0, /* init */
- 0, /* finish */
- RSA_FLAG_SIGN_VER, /* flags */
- 0, /* app_data */
- capi_rsa_sign, /* rsa_sign */
- 0, /* rsa_verify */
- 0, /* rsa_keygen */
- };
- int capi_rsa_priv_dec(int flen,const unsigned char *from,unsigned char *to,RSA *rsa,int padding)
- {
- int number;
- PCRYPT_KEY_PROV_INFO keyinfo;
- HCRYPTPROV hProv;
- BOOL b;
- DWORD dwKeySpec;
- HCRYPTKEY hUserKey;
- BYTE *data;
- DWORD datalen;
- int i;
- if(flen<0||from==0||to==0||rsa==0||rsa->n==0||rsa->e==0)
- {
- return -1;
- }
- number=RSA_size(rsa);
- if(flen!=number)
- {
- return -1;
- }
- switch(padding)
- {
- case RSA_PKCS1_PADDING:
- break;
- default:
- return -1;
- }
- keyinfo=GetProvInfo(rsa);
- if(keyinfo==0)
- {
- return -1;
- }
- b=CryptAcquireContextW(&hProv,keyinfo->pwszContainerName,keyinfo->pwszProvName,keyinfo->dwProvType,keyinfo->dwFlags);
- dwKeySpec=keyinfo->dwKeySpec;
- free(keyinfo);
- if(b==0)
- {
- return -1;
- }
- b=CryptGetUserKey(hProv,dwKeySpec,&hUserKey);
- if(b==0)
- {
- CryptReleaseContext(hProv,0);
- return -1;
- }
- datalen=flen;
- data=(BYTE *)malloc(datalen);
- if(data==0)
- {
- CryptDestroyKey(hUserKey);
- CryptReleaseContext(hProv,0);
- return -1;
- }
- for(i=0;i<flen;i++)
- {
- data[i]=from[flen-i-1];
- }
- b=CryptDecrypt(hUserKey,0,TRUE,0,data,&datalen);
- CryptDestroyKey(hUserKey);
- CryptReleaseContext(hProv,0);
- if(b==0)
- {
- free(data);
- return -1;
- }
- memcpy(to,data,datalen);
- memset(data,0,datalen);
- free(data);
- return (int)datalen;
- }
- int capi_rsa_sign(int type,const unsigned char *m, unsigned int m_length,unsigned char *sigret, unsigned int *siglen, const RSA *rsa)
- {
- int number;
- PCRYPT_KEY_PROV_INFO keyinfo;
- HCRYPTPROV hProv;
- BOOL b;
- DWORD dwKeySpec;
- ALG_ID hashid;
- HCRYPTHASH hHash;
- unsigned int i;
- unsigned char temp;
- if(m_length<0||m==0||sigret==0||siglen==0||rsa==0||rsa->n==0||rsa->e==0)
- {
- return 0;
- }
- number=RSA_size(rsa);
- if((int)m_length>number)
- {
- return 0;
- }
- *siglen=number;
- switch(type)
- {
- case NID_md5_sha1:
- hashid=CALG_SSL3_SHAMD5;
- if(m_length!=36)
- {
- return 0;
- }
- break;
- case NID_md5:
- hashid=CALG_MD5;
- if(m_length!=16)
- {
- return 0;
- }
- break;
- case NID_md2:
- hashid=CALG_MD2;
- if(m_length!=16)
- {
- return 0;
- }
- break;
- case NID_sha1:
- hashid=CALG_SHA1;
- if(m_length!=20)
- {
- return 0;
- }
- break;
- default :
- return 0;
- }
- keyinfo=GetProvInfo(rsa);
- if(keyinfo==0)
- {
- return 0;
- }
- b=CryptAcquireContextW(&hProv,keyinfo->pwszContainerName,keyinfo->pwszProvName,keyinfo->dwProvType,keyinfo->dwFlags);
- dwKeySpec=keyinfo->dwKeySpec;
- free(keyinfo);
- if(b==0)
- {
- return 0;
- }
- b=CryptCreateHash(hProv,hashid,0,0,&hHash);
- if(b==0)
- {
- CryptReleaseContext(hProv,0);
- return 0;
- }
- b=CryptSetHashParam(hHash,HP_HASHVAL,m,0);
- if(b==0)
- {
- CryptDestroyHash(hHash);
- CryptReleaseContext(hProv,0);
- return 0;
- }
- b=CryptSignHash(hHash,dwKeySpec,0,0,sigret,(DWORD *)siglen);
- CryptDestroyHash(hHash);
- CryptReleaseContext(hProv,0);
- if(b==0)
- {
- return 0;
- }
- for(i=0;i<(*siglen)/2;i++)
- {
- temp=sigret[i];
- sigret[i]=sigret[*siglen-i-1];
- sigret[*siglen-i-1]=temp;
- }
- return 1;
- }
- RSA *CAPI_RSA_new()
- {
- RSA *rsa;
- rsa=RSA_new();
- if(rsa==0)
- {
- return 0;
- }
- RSA_set_method(rsa,&capirsameth);
- rsa->flags=RSA_FLAG_SIGN_VER;
- return rsa;
- }
- RSA *RSAToCAPIPrivKey(RSA *rsa)
- {
- RSA *ret;
- PCRYPT_KEY_PROV_INFO keyinfo;
- HCRYPTPROV hProv;
- BOOL b;
- if(rsa==0||rsa->e==0||rsa->n==0)
- {
- return 0;
- }
- keyinfo=GetProvInfo(rsa);
- if(keyinfo==0)
- {
- return 0;
- }
- b=CryptAcquireContextW(&hProv,keyinfo->pwszContainerName,keyinfo->pwszProvName,keyinfo->dwProvType,keyinfo->dwFlags);
- free(keyinfo);
- if(b==0)
- {
- return 0;
- }
- CryptReleaseContext(hProv,0);
- ret=CAPI_RSA_new();
- if(ret==0)
- {
- return 0;
- }
- ret->e=BN_dup(rsa->e);
- if(ret->e==0)
- {
- RSA_free(ret);
- return 0;
- }
- ret->n=BN_dup(rsa->n);
- if(ret->n==0)
- {
- RSA_free(ret);
- return 0;
- }
- return ret;
- }
- PCRYPT_KEY_PROV_INFO GetProvInfo(const RSA *rsa)
- {
- unsigned char *rsapubkeybuf=0;
- int rsapubkeybuflen;
- BOOL b;
- PCCERT_CONTEXT ctx1;
- PCCERT_CONTEXT ctx2;
- HCERTSTORE hStore;
- PCRYPT_KEY_PROV_INFO data;
- DWORD datalen;
- rsapubkeybuflen=i2d_RSAPublicKey(rsa,&rsapubkeybuf);
- if(rsapubkeybuflen<=0)
- {
- return 0;
- }
- hStore=CertOpenSystemStore(0,"MY");
- if(hStore==0)
- {
- OPENSSL_free(rsapubkeybuf);
- return 0;
- }
- ctx2=0;
- while((ctx1=CertEnumCertificatesInStore(hStore,ctx2)))
- {
- b=CertGetCertificateContextProperty(ctx1,CERT_KEY_PROV_INFO_PROP_ID, 0, &datalen);
- if(b)
- {
- data=(PCRYPT_KEY_PROV_INFO)malloc(datalen);
- if(data==0)
- {
- OPENSSL_free(rsapubkeybuf);
- CertFreeCertificateContext(ctx1);
- CertCloseStore(hStore,0);
- return 0;
- }
- b=CertGetCertificateContextProperty(ctx1,CERT_KEY_PROV_INFO_PROP_ID, data, &datalen);
- if(b)
- {
- if(ctx1->pCertInfo->SubjectPublicKeyInfo.PublicKey.cUnusedBits==0&
- (int)ctx1->pCertInfo->SubjectPublicKeyInfo.PublicKey.cbData==rsapubkeybuflen &
- memcmp(ctx1->pCertInfo->SubjectPublicKeyInfo.PublicKey.pbData,rsapubkeybuf,rsapubkeybuflen)==0)
- {
- OPENSSL_free(rsapubkeybuf);
- CertFreeCertificateContext(ctx1);
- CertCloseStore(hStore,0);
- return data;
- }
- else
- {
- free(data);
- }
- }
- else
- {
- free(data);
- }
- }
- ctx2=ctx1;
- }
- OPENSSL_free(rsapubkeybuf);
- CertCloseStore(hStore,0);
- return 0;
- }