SSL单向/双向认证详解

1、单向认证和双向认证

单向认证 SSL 协议的具体过程

①客户端的浏览器向服务器传送客户端 SSL 协议的版本号,加密算法的种类,产生的随机数,以及其他服务器和客户端之间通讯所需要的各种信息。

②服务器向客户端传送 SSL 协议的版本号,加密算法的种类,随机数以及其他相关信息,同时服务器还将向客户端传送自己的证书。

③客户利用服务器传过来的信息验证服务器的合法性,服务器的合法性包括:证书是否过期,发行服务器证书的 CA 是否可靠,发行者证书的公钥能否正确解开服务器证书的“发行者的数字签名”,服务器证书上的域名是否和服务器的实际域名相匹配。如果合法性验证没有通过, 通讯将断开;如果合法性验证通过,将继续进行第四步。

④用户端随机产生一个用于后面通讯的“对称密码”,然后用服务器的公钥(服务器的公钥从步骤②中的服务器的证书中获得)对其加密,然后将加密后的“预主密码”传给服务器。

⑤如果服务器要求客户的身份认证(在握手过程中为可选),用户可以建立一个随机数然后对其进行数据签名,将这个含有签名的随机数和客户自己的证书以及加密过的“预主密码”一起传给服务器。

⑥ 如果服务器要求客户的身份认证,服务器必须检验客户证书和签名随机数的合法性,具体的合法性验证过程包括:客户的证书使用日期是否有效,为客户提供证书的 CA 是否可靠,发行CA 的公钥能否正确解开客户证书的发行 CA 的数字签名,检查客户的证书是否在证书废止列表(CRL)中。检验如果没有通过,通讯立刻中断;如果验证通过,服务器将用自己的私钥解开加密的“预主密码 ”,然后执行一系列步骤来产生主通讯密码(客户端也将通过同样的方法产生相同的主通讯密码)。

⑦服务器和客户端用相同的主密码即“通话密码”,一个对称密钥用于 SSL 协议的安全数据通讯的加解密通讯。同时在 SSL 通讯过程中还要完成数据通讯的完整性,防止数据通讯中的任何变化。

⑧客户端向服务器端发出信息,指明后面的数据通讯将使用的步骤⑦中的主密码为对称密钥,同时通知服务器客户端的握手过程结束。

⑨服务器向客户端发出信息,指明后面的数据通讯将使用的步骤⑦中的主密码为对称密钥,同时通知客户端服务器端的握手过程结束。

⑩SSL 的握手部分结束,SSL 安全通道的数据通讯开始,客户和服务器开始使用相同的对称密钥进行数据通讯,同时进行通讯完整性的检验。

 

双向认证 SSL 协议的具体过程

① 浏览器发送一个连接请求给安全服务器。

② 服务器将自己的证书,以及同证书相关的信息发送给客户浏览器。

③ 客户浏览器检查服务器送过来的证书是否是由自己信赖的 CA 中心所签发的。如果是,就继续执行协议;如果不是,客户浏览器就给客户一个警告消息:警告客户这个证书不是可以信赖的,询问客户是否需要继续。

④ 接着客户浏览器比较证书里的消息,例如域名和公钥,与服务器刚刚发送的相关消息是否一致,如果是一致的,客户浏览器认可这个服务器的合法身份。

⑤ 服务器要求客户发送客户自己的证书。收到后,服务器验证客户的证书,如果没有通过验证,拒绝连接;如果通过验证,服务器获得用户的公钥。

⑥ 客户浏览器告诉服务器自己所能够支持的通讯对称密码方案。

⑦ 服务器从客户发送过来的密码方案中,选择一种加密程度最高的密码方案,用客户的公钥加过密后通知浏览器。

⑧ 浏览器针对这个密码方案,选择一个通话密钥,接着用服务器的公钥加过密后发送给服务器。

⑨ 服务器接收到浏览器送过来的消息,用自己的私钥解密,获得通话密钥。

⑩ 服务器、浏览器接下来的通讯都是用对称密码方案,对称密钥是加过密的。


上面所述的是双向认证 SSL 协议的具体通讯过程,这种情况要求服务器和用户双方都有证书。单向认证 SSL 协议不需要客户拥有 CA 证书,具体的过程相对于上面的步骤,只需将服务器端验证客户证书的过程去掉,以及在协商对称密码方案,对称通话密钥时,服务器发送给客户的是没有加过密的 (这并不影响 SSL 过程的安全性)密码方案。这样,双方具体的通讯内容,就是加过密的数据,如果有第三方攻击,获得的只是加密的数据,第三方要获得有用的信息,就需要对加密 的数据进行解密,这时候的安全就依赖于密码方案的安全。而幸运的是,目前所用的密码方案,只要通讯密钥长度足够的长,就足够的安全。这也是我们强调要求使 用 128 位加密通讯的原因。

 

2、证书扩展名

在开发和测试阶段,使用的是自签的证书。在找自签CA证书操作流程的时候,会有很多的文件扩展名,如pem,der,csr.cer.crt.p12,pfx,jks等,刚开始接触的话很容易混淆,因此先来看下证书的扩展名。

DER: .DER = DER扩展用于二进制DER编码证书。这些文件也可能承载CER或CRT扩展。
PEM:使⽤Base64 ASCII进⾏编码的纯⽂本格式,是以“ - BEGIN …”前缀的ASCII(Base64)数据。
KEY:.KEY 扩展名用于公钥和私钥,常见使用于私钥。也可以被编码为二进制DER或ASCII PEM。
CSR:证书签名请求。CSR文件是申请SSL证书时所需要的一个数据文件。
CRT:CRT扩展用于证书。 证书可以被编码为二进制DER或ASCII PEM。 CER和CRT扩展几乎是同义词。 最常见的于Unix 或类Unix系统。通俗来讲,.CRT文件常在Linux系统使用,包含公钥和主体信息。
CER:.CRT的替代形式,您可以在微软系统环境下将.CRT转换为.CER(.both DER编码的.CER,或base64 [PEM]编码的.cer)。通俗来讲,就是.CER扩展文件是DER编码,并且.CER文件常在Windows系统使用。
P12:P12证书全称是PKCS#12。是一种交换数字证书的加密标准,用来描述个人身份信息。p12证书包含了私钥、公钥并且有口令保护,在证书泄露后还有最后一道保障——证书口令,不知道正确的证书口令无法提取秘钥(文件的扩展名能够为pfx或p12)​
PFX:PFX也是由PKCS#12标准定义,包含了公钥和私钥的二进制格式的证书形式,以pfx做为证书文件后缀名(文件的扩展名能够为pfx或p12)
JKS:JKS是JAVA的keytools证书工具支持的证书私钥格式

 

3、实战

openssl准备环境

 

使用openssl version查看openssl版本,如果没有安转openssl,可以执行 yum install openssl 安装

 

nginx环境准备

我们使用nginx来进行https的双向认证,首先我们需要安装nginx并附带SSL模块,在linux下安装nginx,首先需要安装 gcc-c++编译器。然后安装nginx依赖的pcre和zlib包。最后安装nginx即可。

1.先安装gcc-c++编译器

yum install gcc-c++
yum install -y openssl openssl-devel


2.再安装pcre包

yum install -y pcre pcre-devel


3.再安装zlib包

yum install -y zlib zlib-deve


4.nginx安装

#在/usr/local/下创建文件nginx文件

mkdir /usr/local/nginx

 

#在网上下nginx包上传至Linux(https://nginx.org/download/),也可以直接下载

wget https://nginx.org/download/nginx-1.19.9.tar.gz

 

#解压并进入nginx目录

tar -zxvf nginx-1.19.9.tar.gz

cd nginx-1.19.9


#使用nginx默认配置

./configure --prefix=/usr/local/nginx --with-http_ssl_module --with-pcre

 

#编译安装

make

make install

 

#查找安装路径

whereis nginx


#
进入sbin目录,可以看到有一个可执行文件nginx,直接./nginx执行就OK了。

./nginx

 

#查看是否启动成功

ps -ef | grep nginx

 

 

 

#然后在网页上访问自己的IP就可以了默认端口为80(出现如下欢迎界面就成功了!)

 

 

 

 

 

自签CA证书

1、生成根证书

#创建证书存放的⽬录

mkdir ssl 

 

#进⼊证书存放⽬录

cd ssl 

 

#生成私钥

openssl genrsa -out ca.key 2048 #⽣成根证书私钥


#查看一下ca.key 的内容,可以看到

 

 

 

 

 

 

 

#可以将ca.key转为pem文件

openssl rsa -in ca.key -out ca-key.pem

再查看一下ca-key.pem文件,内容和ca.key是相同的

 

#生成根证书

openssl req -new -x509 -days 3650 -key ca.key -out ca.crt

-days设置证书有效时间,这里我们设置10年
生成证书时要输入一些个体信息
证书生成后,可以查看ca.crt的内容

 

 

 

#也可以将ca.crt转为pem文件

openssl x509 -in ca.crt -out ca-crt.pem

再查看一下ca-crt.pem文件,内容和ca.crt是相同的

 

#编码类型转换

PEM编码转为DER编码

 

openssl x509 -in ca-crt.pem -outform der -out ca-crt.der

 

DER转为PEM

openssl x509 -in ca-crt.der -inform der -outform pem -out ca-crt.pem

(提示:要转换KEY文件也类似)


der二进制编码的文件不能直接查看,可以使用命令查看.der文件

openssl x509 -in ca-crt.der -inform der -text -noout

 

2、生成服务端证书

为了便于理解和操作,这里统一使用pem编码,并统一生成pem格式扩展文件

#生成服务端私钥

# 生成pem扩展名的私钥
openssl genrsa -out server-key.pem 2048
# 2048表示生成的私钥为2048位,一般使用2048位相对比较安全

 

#将.pem扩展名私钥转为.key

openssl rsa -in server-key.pem -out server.key

再查看server.key 和server.pem 私钥内容是相同的

 

#生成服务端证书签名请求文件

#注意这⾥的common name必须是需要访问的域名,其他的内容可以和根证书填写的⼀样
openssl req -new -key server-key.pem -out server-csr.pem

 

 

 

#可以将server-csr.pem转为.csr扩展名

openssl req -in server-csr.pem -out server.csr 或直接修改扩展名 .pem为.csr

 

#根据签发请求⽣成服务端证书

openssl x509 -req -sha256 -in server-csr.pem -CA ca.crt -CAkey ca.key -CAcreateserial -days 3650 -out server-crt.pem

 

 

 

#可以将server-crt.pem转为.crt扩展名

openssl x509 -outform pem -in server-crt.pem -out server.crt

 

#生成服务端pfx或p12证书

openssl pkcs12 -export -in server.crt -inkey server.key -out server.pfx

 

注:在将.pem扩展文件转为key,csr,crt文件的时候,先查看文件内容
包含 -----BEGIN RSA PRIVETE KEY ----- 的内容可以导出转换.key扩展名的文件
包含 -----BEGIN CERTIFICATE REQUEST ----- 的内容可以导出转换.csr扩展名的文件
包含 -----BEGIN CERTIFICATE ----- 的内容可以导出转换.crt扩展名的文件

 

3、生成客户端证书
客户端证书⽣成步骤和服务端基本⼀样,需要注意的就是在⽣成签发请求的时候填写的信息中,comm name也要是访问的域名。

#生成客户端私钥

openssl genrsa -out client.key 2048

 

#生成客户端证书签名请求文件

openssl req -new -key client.key -out client.csr

 

#根据签发请求和⽣成客户端证书

openssl x509 -req -sha256 -in client.csr -CA ca.crt -CAkey ca.key -CAcreateserial -days 3650 -out client.crt

 

#生成客户端pfx或p12证书

openssl pkcs12 -export -in client.crt -inkey client.key -out client.p12

 

4、开启https,并校验客户端(双向认证)
#配置nginx,开启https

开启https请求
进入nginx目录,编辑nginx.conf – vim nginx.conf
找到HTTPS server
ssl_certificate 服务端crt证书路径
ssl_certificatie_key 服务端私钥路径
 
配置完以后 启动或者容器一下nginx
启动:在nginx目录执行 ./sbin/nginx
重启:在nginx目录执行 ./sbin/nginx -s reload
在浏览器访问是成功的,因为我们是自签证书,因此显示不安全

 

 

 

 


#开启客户端认证

vim nginx.conf #继续编辑nginx.conf
ssl_client_certificate #指定客户端认证时使⽤的根证书路径,⽤来验证客户端证书的正确性,我们使用的自签ca证书签发的客户端证书,因此使用ca.crt
ssl_verify_client on #为开启客户端校验

 

 

配置完成后重启

nginx ./sbin/nginx -s reload

 

为了方便测试。我们直接使用curl 命令进行测试

curl https://ip -k -v

 

ip为访问的具体ip地址
-k编码忽略服务端证书的校验,因为我们这里服务端证书也是自签的,所以要加上-k
不加-k,会有异常提示

 

 

 

-v为显示具体的信息,也可以不加
使用上述命令访问后

 

 

提示需要携带客户端的证书,说明我们配置的客户端认证已经生效了

curl --cert client.crt --key client.key https://ip -k

 

 可以看到携带证书后访问是成功的,说明客户端已经认证成功了,因此https双向认证完成了

5.java代码
附一段java代码,代码来自 https://wenku.baidu.com/view/ee8e04315c0e7cd184254b35eefdc8d376ee1494.html

public class SSLService {
    // 客户端证书路径,⽤了本地绝对路径,需要修改
    private final static String CLIENT_CERT_FILE = "C:\\Users\\tzx\\Desktop\\client.p12";
    // 客户端证书密码
    private final static String CLIENT_PWD = "131112";
    // 信任库路径,即keytool⽣成的那个⾃定义名称的库⽂件
    private final static String TRUST_STRORE_FILE = "D:\\Java\\jdk1.8.0_131\\jre\\lib\\security\\test.truststore";
    // 信任库密码,即keytool时的密码
    private final static String TRUST_STORE_PWD = "131112";
    private static String readResponseBody(InputStream inputStream) throws IOException {
        try {
            BufferedReader br = new BufferedReader(new InputStreamReader(inputStream, Charset.forName("UTF-8")));
            StringBuffer sb = new StringBuffer();
            String buff = null;
            while ((buff = br.readLine()) != null) {
                sb.append(buff + "\n");
            }
            return sb.toString();
        } finally {
            inputStream.close();
        }
    }
    public static void httpsCall() throws Exception {
        // 初始化密钥库
        KeyManagerFactory keyManagerFactory = KeyManagerFactory
                .getInstance("SunX509");
        KeyStore keyStore = getKeyStore(CLIENT_CERT_FILE, CLIENT_PWD, "PKCS12");
        keyManagerFactory.init(keyStore, CLIENT_PWD.toCharArray());
        // 初始化信任库
        TrustManagerFactory trustManagerFactory = TrustManagerFactory
                .getInstance("SunX509");
        KeyStore trustkeyStore = getKeyStore(TRUST_STRORE_FILE, TRUST_STORE_PWD, "JKS");
        trustManagerFactory.init(trustkeyStore);
        // 初始化SSL上下⽂
        SSLContext ctx = SSLContext.getInstance("SSL");
        ctx.init(keyManagerFactory.getKeyManagers(), trustManagerFactory
                .getTrustManagers(), null);
        SSLSocketFactory sf = ctx.getSocketFactory();
        HttpsURLConnection.setDefaultSSLSocketFactory(sf);
        String url = "https://blog.tzx.com";
        URL urlObj = new URL(url);
        HttpsURLConnection con = (HttpsURLConnection) urlObj.openConnection();
        con.setRequestProperty("User-Agent", "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 " +
                "(KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36");
        con.setRequestProperty("Accept-Language", "zh-CN;en-US,en;q=0.5");
        con.setRequestMethod("GET");
        String res = readResponseBody(con.getInputStream());
        System.out.println(res);
    }
    /**
     * 获得KeyStore
     */
    private static KeyStore getKeyStore(String keyStorePath, String password, String type)
            throws Exception {
        FileInputStream is = new FileInputStream(keyStorePath);
        KeyStore ks = KeyStore.getInstance(type);
        ks.load(is, password.toCharArray());
        is.close();
        return ks;
    }
   public static void main(String[] args) throws Exception {
    httpsCall(null);
  }
}

 

posted @ 2022-08-26 22:41  iDropper  阅读(2118)  评论(0编辑  收藏  举报