欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

openssl 生成证书及生成过程

程序员文章站 2022-06-29 21:56:03
...

1. 基本原理

公司一个项目要进行交易数据传输,因为这个项目银行那边也是刚刚开始启动,所有的支持只有一个传输字段的说明文档,好吧,总的有人做事不是嘛,于是接口开发正式展开,第一步的难点就是加密解密,我选择使用OpenSSL.

OpenSSL初接触的人恐怕最难的在于先理解各种概念

公钥/私钥/签名/验证签名/加密/解密/非对称加密

我们一般的加密是用一个密码加密文件,然后解密也用同样的密码.这很好理解,这个是对称加密.而有些加密时,加密用的一个密码,而解密用另外一组密码,这个叫非对称加密,意思就是加密解密的密码不一样.初次接触的人恐怕无论如何都理解不了.其实这是数学上的一个素数积求因子的原理的应用,如果你一定要搞懂,百度有大把大把的资料可以看,其结果就是用这一组**中的一个来加密数据,可以用另一个解开.是的没错,公钥和私钥都可以用来加密数据,相反用另一个解开,公钥加密数据,然后私钥解密的情况被称为加密解密,私钥加密数据,公钥解密一般被称为签名和验证签名.

因为公钥加密的数据只有它相对应的私钥可以解开,所以你可以把公钥给人和人,让他加密他想要传送给你的数据,这个数据只有到了有私钥的你这里,才可以解开成有用的数据,其他人就是得到了,也看懂内容.同理,如果你用你的私钥对数据进行签名,那这个数据就只有配对的公钥可以解开,有这个私钥的只有你,所以如果配对的公钥解开了数据,就说明这数据是你发的,相反,则不是.这个被称为签名.

实际应用中,一般都是和对方交换公钥,然后你要发给对方的数据,用他的公钥加密,他得到后用他的私钥解密,他要发给你的数据,用你的公钥加密,你得到后用你的私钥解密,这样最大程度保证了安全性.

RSA/DSA/SHA/MD5

非对称加密的算法有很多,比较著名的有RSA/DSA ,不同的是RSA可以用于加/解密,也可以用于签名验签,DSA则只能用于签名.至于SHA则是一种和md5相同的算法,它不是用于加密解密或者签名的,它被称为摘要算法.就是通过一种算法,依据数据内容生成一种固定长度的摘要,这串摘要值与原数据存在对应关系,就是原数据会生成这个摘要,但是,这个摘要是不能还原成原数据的,嗯....,正常情况下是这样的,这个算法起的作用就是,如果你把原数据修改一点点,那么生成的摘要都会不同,传输过程中把原数据给你再给你一个摘要,你把得到的原数据同样做一次摘要算法,与给你的摘要相比较就可以知道这个数据有没有在传输过程中被修改了.

实际应用过程中,因为需要加密的数据可能会很大,进行加密费时费力,所以一般都会把原数据先进行摘要,然后对这个摘要值进行加密,将原数据的明文和加密后的摘要值一起传给你.这样你解开加密后的摘要值,再和你得到的数据进行的摘要值对应一下就可以知道数据有没有被修改了,而且,因为私钥只有你有,只有你能解密摘要值,所以别人就算把原数据做了修改,然后生成一个假的摘要给你也是不行的,你这边用**也根本解不开.

CA/PEM/DER/X509/PKCS

一般的公钥不会用明文传输给别人的,正常情况下都会生成一个文件,这个文件就是公钥文件,然后这个文件可以交给其他人用于加密,但是传输过程中如果有人恶意破坏,将你的公钥换成了他的公钥,然后得到公钥的一方加密数据,不是他就可以用他自己的**解密看到数据了吗,为了解决这个问题,需要一个公证方来做这个事,任何人都可以找它来确认公钥是谁发的.这就是CA,CA确认公钥的原理也很简单,它将它自己的公钥发布给所有人,然后一个想要发布自己公钥的人可以将自己的公钥和一些身份信息发给CA,CA用自己的**进行加密,这里也可以称为签名.然后这个包含了你的公钥和你的信息的文件就可以称为证书文件了.这样一来所有得到一些公钥文件的人,通过CA的公钥解密了文件,如果正常解密那么机密后里面的信息一定是真的,因为加密方只可能是CA,其他人没它的**啊.这样你解开公钥文件,看看里面的信息就知道这个是不是那个你需要用来加密的公钥了.

实际应用中,一般人都不会找CA去签名,因为那是收钱的,所以可以自己做一个自签名的证书文件,就是自己生成一对**,然后再用自己生成的另外一对**对这对**进行签名,这个只用于真正需要签名证书的人,普通的加密解密数据,直接用公钥和私钥来做就可以了.

**文件的格式用OpenSSL生成的就只有PEM和DER两种格式,PEM的是将**用base64编码表示出来的,直接打开你能看到一串的英文字母,DER格式是二进制的**文件,直接打开,你可以看到........你什么也看不懂!.X509是通用的证书文件格式定义.pkcs的一系列标准是指定的存放**的文件标准,你只要知道PEM DER X509 PKCS这几种格式是可以互相转化的.


openssl 生成证书及生成过程

2. 生成证书 

一:生成CA证书
目前不使用第三方权威机构的CA来认证,自己充当CA的角色。  
网上下载一个openssl软件
1.创建私钥 :
C:\OpenSSL\bin>openssl genrsa -out ca/ca-key.pem 1024  
2.创建证书请求 :
C:\OpenSSL\bin>openssl req -new -out ca/ca-req.csr -key ca/ca-key.pem  
-----
Country Name (2 letter code) [AU]:cn
State or Province Name (full name) [Some-State]:zhejiang
Locality Name (eg, city) []:hangzhou
Organization Name (eg, company) [Internet Widgits Pty Ltd]:skyvision
Organizational Unit Name (eg, section) []:test
Common Name (eg, YOUR name) []:root
Email Address []:sky
3.自签署证书 :
C:\OpenSSL\bin>openssl x509 -req -in ca/ca-req.csr -out ca/ca-cert.pem -signkey ca/ca-key.pem -days 3650
4.将证书导出成浏览器支持的.p12格式 :
C:\OpenSSL\bin>openssl pkcs12 -export -clcerts -in ca/ca-cert.pem -inkey ca/ca-key.pem -out ca/ca.p12  
密码:changeit       
二.生成server证书。  
1.创建私钥 :
C:\OpenSSL\bin>openssl genrsa -out server/server-key.pem 1024  
2.创建证书请求 :
C:\OpenSSL\bin>openssl req -new -out server/server-req.csr -key server/server-key.pem  
-----
Country Name (2 letter code) [AU]:cn
State or Province Name (full name) [Some-State]:zhejiang
Locality Name (eg, city) []:hangzhou
Organization Name (eg, company) [Internet Widgits Pty Ltd]:skyvision
Organizational Unit Name (eg, section) []:test
Common Name (eg, YOUR name) []:192.168.1.246   注释:一定要写服务器所在的ip地址
Email Address []:sky
3.自签署证书 :
C:\OpenSSL\bin>openssl x509 -req -in server/server-req.csr -out server/server-cert.pem -signkey server/server-key.pem -CA ca/ca-cert.pem -CAkey ca/ca-key.pem -CAcreateserial -days 3650  
4.将证书导出成浏览器支持的.p12格式 :
C:\OpenSSL\bin>openssl pkcs12 -export -clcerts -in server/server-cert.pem -inkey server/server-key.pem -out server/server.p12  

三.生成client证书。  
1.创建私钥 :
C:\OpenSSL\bin>openssl genrsa -out client/client-key.pem 1024  
2.创建证书请求 :
C:\OpenSSL\bin>openssl req -new -out client/client-req.csr -key client/client-key.pem
-----
Country Name (2 letter code) [AU]:cn
State or Province Name (full name) [Some-State]:zhejiang
Locality Name (eg, city) []:hangzhou
Organization Name (eg, company) [Internet Widgits Pty Ltd]:skyvision
Organizational Unit Name (eg, section) []:test
Common Name (eg, YOUR name) []:sky
Email Address []:sky      注释:就是登入中心的用户(本来用户名应该是Common Name,但是中山*的不知道为什么使用的Email Address,其他版本没有测试)
Please enter the following 'extra' attributes
to be sent with your certificate request
A challenge password []:123456
An optional company name []:tsing  
3.自签署证书 :
C:\OpenSSL\bin>openssl x509 -req -in client/client-req.csr -out client/client-cert.pem -signkey client/client-key.pem -CA ca/ca-cert.pem -CAkey ca/ca-key.pem -CAcreateserial -days 3650  
4.将证书导出成浏览器支持的.p12格式 :
C:\OpenSSL\bin>openssl pkcs12 -export -clcerts -in client/client-cert.pem -inkey client/client-key.pem -out client/client.p12  

请一定严格根据里面的步骤来,待实验成功后,修改你自己想要修改的内容。我就是一开始没有安装该填写的来,结果生成的证书就无法配对成功。

3 证书格式

3.1 证书标准

X.509 - 这是一种证书标准,主要定义了证书中应该包含哪些内容.其详情可以参考RFC5280,SSL使用的就是这种证书标准.

同样的X.509证书,可能有不同的编码格式,目前有以下两种编码格式.

PEM - Privacy Enhanced Mail,打开看文本格式,以"-----BEGIN..."开头, "-----END..."结尾,内容是BASE64编码.
查看PEM格式证书的信息:openssl x509 -in certificate.pem -text -noout
Apache和*NIX服务器偏向于使用这种编码格式.

DER - Distinguished Encoding Rules,打开看是二进制格式,不可读.
查看DER格式证书的信息:openssl x509 -in certificate.der -inform der -text -noout
Java和Windows服务器偏向于使用这种编码格式.

3.2 相关的文件扩展名:

这是比较误导人的地方,虽然我们已经知道有PEM和DER这两种编码格式,但文件扩展名并不一定就叫"PEM"或者"DER",常见的扩展名除了PEM和DER还有以下这些,它们除了编码格式可能不同之外,内容也有差别,但大多数都能相互转换编码格式.

CRT - CRT应该是certificate的三个字母,其实还是证书的意思,常见于*NIX系统,有可能是PEM编码,也有可能是DER编码,大多数应该是PEM编码,相信你已经知道怎么辨别.

CER - 还是certificate,还是证书,常见于Windows系统,同样的,可能是PEM编码,也可能是DER编码,大多数应该是DER编码.

KEY - 通常用来存放一个公钥或者私钥,并非X.509证书,编码同样的,可能是PEM,也可能是DER.
查看KEY的办法:openssl rsa -in mykey.key -text -noout
如果是DER格式的话,同理应该这样了:openssl rsa -in mykey.key -text -noout -inform der

CSR - Certificate Signing Request,即证书签名请求,这个并不是证书,而是向权威证书颁发机构获得签名证书的申请,其核心内容是一个公钥(当然还附带了一些别的信息),在生 成这个申请的时候,同时也会生成一个私钥,私钥要自己保管好.做过iOS APP的朋友都应该知道是怎么向苹果申请开发者证书的吧.
查看的办法:openssl req -noout -text -in my.csr (如果是DER格式的话照旧加上-inform der,这里不写了)

PFX/P12 - predecessor of PKCS#12,对*nix服务器来说,一般CRT和KEY是分开存放在不同文件中的,但Windows的IIS则将它们存在一个PFX文件中,(因此这 个文件包含了证书及私钥)这样会不会不安全?应该不会,PFX通常会有一个"提取密码",你想把里面的东西读取出来的话,它就要求你提供提取密码,PFX 使用的时DER编码,如何把PFX转换为PEM编码?
openssl pkcs12 -in for-iis.pfx -out for-iis.pem -nodes
这个时候会提示你输入提取代码. for-iis.pem就是可读的文本.
生成pfx的命令类似这样:openssl pkcs12 -export -in certificate.crt -inkey privateKey.key -out certificate.pfx -certfile CACert.crt

其中CACert.crt是CA(权威证书颁发机构)的根证书,有的话也通过-certfile参数一起带进去.这么看来,PFX其实是个证书**库.

JKS - 即Java Key Storage,这是Java的专利,跟OpenSSL关系不大,利用Java的一个叫"keytool"的工具,可以将PFX转为JKS,当然了,keytool也能直接生成JKS,不过在此就不多表了.

3.3 证书编码的转换

PEM转为DER openssl x509 -in cert.crt -outform der -out cert.der

DER转为PEM openssl x509 -in cert.crt -inform der -outform pem -out cert.pem

(提示:要转换KEY文件也类似,只不过把x509换成rsa,要转CSR的话,把x509换成req...)

3.4 获得证书

向权威证书颁发机构申请证书

用这命令生成一个csr: openssl req -newkey rsa:2048 -new -nodes -keyout my.key -out my.csr
把csr交给权威证书颁发机构,权威证书颁发机构对此进行签名,完成.保留好csr,当权威证书颁发机构颁发的证书过期的时候,你还可以用同样的csr来申请新的证书,key保持不变.

或者生成自签名的证书
openssl req -newkey rsa:2048 -new -nodes -x509 -days 3650 -keyout key.pem -out cert.pem
在生成证书的过程中会要你填一堆的东西,其实真正要填的只有Common Name,通常填写你服务器的域名,如"yourcompany.com",或者你服务器的IP地址,其它都可以留空的.
生产环境中还是不要使用自签的证书,否则浏览器会不认,或者如果你是企业应用的话能够强制让用户的浏览器接受你的自签证书也行.向权威机构要证书通常是要钱的,但现在也有免费的,仅仅需要一个简单的域名验证即可.有兴趣的话查查"沃通数字证书".

 

4. C++实战例子
 

////////////////////////////////////////////////////////////////
void EXIT_IF_TRUE(bool x)
{
    if (x) {
        exit(2);
    }
}
// 服务端代码
int Test_openssl_server_v2()
{
    OM_NetworkInit();
 
    SSL_CTX     *ctx;
    SSL         *ssl;
    X509        *client_cert;
 
    char szBuffer[1024];
    int nLen;
 
    struct sockaddr_in addr;
    int len;
    int nListenFd, nAcceptFd;
 
    // 初始化
    SSLeay_add_ssl_algorithms();
    OpenSSL_add_all_algorithms();
    SSL_load_error_strings();
    ERR_load_BIO_strings();
 
    // 我们使用SSL V3,V2
    EXIT_IF_TRUE((ctx = SSL_CTX_new(SSLv23_method())) == NULL);
 
    // 要求校验对方证书
    SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER, NULL);
 
    // 加载CA的证书
    EXIT_IF_TRUE(!SSL_CTX_load_verify_locations(ctx, "cacert.cer", NULL));
 
    // 加载自己的证书
    EXIT_IF_TRUE(SSL_CTX_use_certificate_file(ctx, "server.cer", SSL_FILETYPE_PEM) <= 0);
 
    // 加载自己的私钥  
    EXIT_IF_TRUE(SSL_CTX_use_PrivateKey_file(ctx, "server.key", SSL_FILETYPE_PEM) <= 0);
 
    // 判定私钥是否正确  
    EXIT_IF_TRUE(!SSL_CTX_check_private_key(ctx));
 
    // 创建并等待连接
    nListenFd = socket(PF_INET, SOCK_STREAM, 0);
    struct sockaddr_in my_addr;
    memset(&my_addr, 0, sizeof(my_addr));
    my_addr.sin_family = PF_INET;
    my_addr.sin_port = htons(8812);
    my_addr.sin_addr.s_addr = inet_addr("127.0.0.1");//  INADDR_ANY;
    if (bind(nListenFd, (struct sockaddr *)&my_addr, sizeof(struct sockaddr)) == -1) {
        int a = 2;
        int b = a;
    }
    
    //
    listen(nListenFd, 10);
 
    memset(&addr, 0, sizeof(addr));
    len = sizeof(addr);
    nAcceptFd = accept(nListenFd, (struct sockaddr *)&addr, (int *)&len);
    CPosaLog::Printf(CPosaLog::L_DEBUG, "Accept a connect from [%s:%d]\n",
        inet_ntoa(addr.sin_addr), ntohs(addr.sin_port));
 
    // 将连接付给SSL  
    EXIT_IF_TRUE((ssl = SSL_new(ctx)) == NULL);
    SSL_set_fd(ssl, nAcceptFd);
    int n1 = SSL_accept(ssl);
    if (n1 == -1) {
        const char* p1 = SSL_state_string_long(ssl);
        int a = 2;
        int b = a;
    }
    // 进行操作  
    memset(szBuffer, 0, sizeof(szBuffer));
    nLen = SSL_read(ssl, szBuffer, sizeof(szBuffer));
    CPosaLog::Printf(CPosaLog::L_DEBUG, "Get Len %d %s ok\n", nLen, szBuffer);
 
    strcat(szBuffer, " this is from server");
    SSL_write(ssl, szBuffer, strlen(szBuffer));
 
    // 释放资源  
    SSL_free(ssl);
    SSL_CTX_free(ctx);
    closesocket(nAcceptFd);
 
    return 0;
}
// 客户端代码
int Test_openssl_client_v2()
{
    OM_NetworkInit();
 
    SSL_METHOD  *meth;
    SSL_CTX     *ctx;
    SSL         *ssl;
 
    int nFd;
    int nLen;
    char szBuffer[1024];
 
    // 初始化
    SSLeay_add_ssl_algorithms();
    OpenSSL_add_all_algorithms();
    SSL_load_error_strings();
    ERR_load_BIO_strings();
 
    // 我们使用SSL V3,V2
    EXIT_IF_TRUE((ctx = SSL_CTX_new(SSLv23_method())) == NULL);
 
    // 要求校验对方证书
    SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER, NULL);
 
    // 加载CA的证书
    EXIT_IF_TRUE(!SSL_CTX_load_verify_locations(ctx, "cacert.cer", NULL));
 
    // 加载自己的证书
    EXIT_IF_TRUE(SSL_CTX_use_certificate_file(ctx, "client.cer", SSL_FILETYPE_PEM) <= 0);
 
    // 加载自己的私钥
    EXIT_IF_TRUE(SSL_CTX_use_PrivateKey_file(ctx, "client.key", SSL_FILETYPE_PEM) <= 0);
 
    // 判定私钥是否正确
    EXIT_IF_TRUE(!SSL_CTX_check_private_key(ctx));
 
    // new
    // 创建连接
    nFd = socket(PF_INET, SOCK_STREAM, 0);
    struct sockaddr_in dest;
    memset(&dest, 0, sizeof(dest));
    dest.sin_family = AF_INET;
    dest.sin_port = htons(8812);
    dest.sin_addr.s_addr = inet_addr("127.0.0.1");
    if (connect(nFd, (struct sockaddr *) &dest, sizeof(dest)) != 0) {
        perror("Connect ");
        exit(errno);
    }
 
    // 将连接付给SSL
    EXIT_IF_TRUE((ssl = SSL_new(ctx)) == NULL);
    SSL_set_fd(ssl, nFd);
    int n1 = SSL_connect(ssl);
    if (n1 == -1) {
        int n2 = SSL_get_error(ssl, n1);
 
        const char* p1 = SSL_state_string(ssl);
    }
 
    // 进行操作
    sprintf(szBuffer, "this is from client %d", getpid());
    int nWriten = SSL_write(ssl, szBuffer, strlen(szBuffer));
 
    // 释放资源
    memset(szBuffer, 0, sizeof(szBuffer));
    nLen = SSL_read(ssl, szBuffer, sizeof(szBuffer));
    CPosaLog::Printf(CPosaLog::L_DEBUG, "Get Len %d %s ok\n", nLen, szBuffer);
 
    SSL_free(ssl);
    SSL_CTX_free(ctx);
    closesocket(nFd);
 
    return 0;
}