已做更新,请移步:https://blog.csdn.net/u014644574/article/details/126190061

https=http+ssl,顾名思义,https是在http的基础上加上了SSL保护壳,信息的加密过程就是在SSL中完成的。

这个过程需要非对称加密来完成,参考:HTTPS原理和CA证书申请(满满的干货)_黄亚的博客-CSDN博客_ca证书 https

一、背景

为什么需要证书?为了用户安全的获取公钥。

安全的获取公钥

细心的人可能已经注意到了如果使用非对称加密算法,我们的客户端A,B需要一开始就持有公钥,要不没法开展加密行为啊。

这下,我们又遇到新问题了,如何让A、B客户端安全地得到公钥?

client获取公钥最最直接的方法是服务器端server将公钥发送给每一个client用户,但这个时候就出现了公钥被劫持的问题,client请求公钥,在请求返回的过程中被×××劫持,那么我们将采用劫持后的假秘钥进行通信,则后续的通讯过程都是采用假秘钥进行,风险仍然存在。在获取公钥的过程中,我们又引出了一个新的话题:如何安全的获取公钥,并确保公钥的获取是安全的, 那就需要用到终极武器了:SSL 证书(需要购买)和CA机构。

如上图所示,在第 ② 步时服务器发送了一个SSL证书给客户端,SSL 证书中包含的具体内容有证书的颁发机构、有效期、公钥、证书持有者、签名,通过第三方的校验保证了身份的合法,解决了公钥获取的安全

以浏览器为例说明如下整个的校验过程:

(1)首先浏览器读取证书中的证书所有者、有效期等信息进行一一校验

(2)浏览器开始查找操作系统中已内置的受信任的证书发布机构CA,与服务器发来的证书中的颁发者CA比对,用于校验证书是否为合法机构颁发

(3)如果找不到,浏览器就会报错,说明服务器发来的证书是不可信任的。

(4)如果找到,那么浏览器就会从操作系统中取出 颁发者CA 的公钥,然后对服务器发来的证书里面的签名进行解密

(5)浏览器使用相同的hash算法计算出服务器发来的证书的hash值,将这个计算的hash值与证书中签名做对比

(6)对比结果一致,则证明服务器发来的证书合法,没有被冒充

(7)此时浏览器就可以读取证书中的公钥,用于后续加密了

至此第一部分关于HTTPS的原理介绍已经结束了,总结一下:

HTTPS要使客户端与服务器端的通信过程得到安全保证,必须使用的对称加密算法,但是协商对称加密算法的过程,需要使用非对称加密算法来保证安全,然而直接使用非对称加密的过程本身也不安全,会有中间人篡改公钥的可能性,所以客户端与服务器不直接使用公钥,而是使用数字证书签发机构颁发的证书来保证非对称加密过程本身的安全。这样通过这些机制协商出一个对称加密算法,就此双方使用该算法进行加密解密。从而解决了客户端与服务器端之间的通信安全问题。

自己的理解(可能有错,请指正):用户电脑中内置了一些知名的第三方认证机构(公钥),我们自己生成的证书要发给某一家第三方认证机构(私钥)签名,这个签名后的证书放到服务器,用户请求时发送给用户,用户将浏览器请求得到的证书使用内置的第三方机构(公钥)验证签名,保证用户安全的获取公钥。写这个理解的目的是让我们知道后面生成证书步骤中根证书签名的过程实际是要发给第三方机构认证的,只不过我们自己代劳了而已。

二、证书生成

1、概念

数字证书是一种权威性的电子文档,可以由权威公正的第三方机构,即CA(例如中国各地方的CA公司)中心签发的证书,也可以由企业级CA系统进行签发。

一般证书分有三类,根证书、服务器证书和客户端证书。

根证书,是生成服务器证书和客户端证书的基础,是信任的源头,也可以叫自签发证书,即CA证书。

服务器证书,由根证书签发,配置在服务器上的证书。

客户端证书,由根证书签发,配置在服务器上,并发送给客户,让客户安装在浏览器里的证书。

接下来,认识了证书的基本概念之后,我们来认识下这几个概念,公钥/私钥/签名/验证签名/加密/解密/对称加密/非对称加密。

我们一般的加密是用一个密码加密文件,然后解密也用同样的密码。这很好理解,这个是对称加密。而有些加密时,加密用的一个密码,而解密用另外一组密码,这个叫非对称加密,意思就是加密解密的密码不一样。其实这是数学上的一个素数积求因子的原理应用,其结果就是用这一组密钥中的一个来加密数据,用另一个来解密,这就是所谓的公钥私钥。公钥和私钥都可以用来加密数据,而他们的区别是,公钥是密钥对中公开的部分,私钥则是非公开的部分。
公钥加密数据,然后私钥解密的情况被称为加密解密
私钥加密数据,公钥解密一般被称为签名验证签名
其中签名和验证签名就是我们本文需要说明和用到的,因为证书的生成过程中就需要签名,而证书的使用则需要验证签名。

2、环境

Linux系统,安装了 opensslopensslopenssl 的。升级OpenSSL隐藏版本号、升级OpenSSH隐藏版本号_u014644574的博客-CSDN博客_openssh隐藏版本号

本文的linux系统是centos 7,openssl版本是OpenSSL 1.0.2k。可以通过以下命令来验证是否安装了openssl,或者查看当前openssl的版本。

openssl version

3、创建根证书CA

(1)查看openssl的配置文件 openssl.cnf 的存放位置(即openssl的安装位置)

openssl version -a

 

(2)查看openssl的配置文件 openssl.cnf 因为配置文件中对证书的名称和存放位置等相关信息都做了定义。

cat /etc/pki/tls/openssl.cnf

 

 (3)创建为根证书CA所需的目录及文件

上面我的 openssl.cnf 配置文件中,根目录为 dir = /etc/pki/CA

#根据配置文件信息,到CA根目录,若没有则自己创建
cd /etc/pki/CA
 
#创建配置文件信息中所需的目录及文件
mkdir -pv {certs,crl,newcerts,private}
touch {serial,index.txt}

 (4)指明证书的开始编号

echo 01 >> serial

(5)生成根证书的私钥(注意:私钥的文件名与存放位置要与配置文件中的设置相匹配)。

上面我的 openssl.cnf 配置文件中 private_key = $dir/private/cakey.pem

#根据配置文件信息,到CA根目录
cd /etc/pki/CA
#生成根证书的私钥
(umask 077; openssl genrsa -out private/cakey.pem 2048)

参数说明:
genrsa  --产生rsa密钥命令
-aes256--使用AES算法(256位密钥)对产生的私钥加密,这里没有此参数,则只是用了rsa算法加密。
-out  ---输出路径,这里指private/cakey.pem
这里的参数2048,指的是密钥的长度位数,默认长度为512位

(6)生成自签证书,即根证书CA,自签证书的存放位置也要与配置文件中的设置相匹配,生成证书时需要填写相应的信息。

上面我的 openssl.cnf 配置文件中
dir = /etc/pki/CA
private_key = $dir/private/cakey.pem
certificate = $dir/cacert.pem

#根据配置文件信息,到CA根目录
cd /etc/pki/CA
#生成根证书的私钥
openssl req -new -x509 -key /etc/pki/CA/private/cakey.pem -out cacert.pem -days 365

生成证书参数参考:
Country Name (2 letter code) [XX]:CN
State or Province Name (full name) []:省份拼音,比如 SiChuan
Locality Name (eg, city) [Default City]:地市拼音,比如 ChengDu
Organization Name (eg, company) [Default Company Ltd]:公司名称拼音,比如 BaiDu
Organizational Unit Name (eg, section) []:你所在公司的部门拼音,比如 YanFa
Common Name (eg, your name or your server's hostname) []:域名或者ip,比如 192.168.5.200
Email Address []:邮箱地址,比如 xiao@qq.com

Name姓名描述示例
Country Name国家名称代表国家的两个字母 ISO 缩写。CN(中国)
State or Province Name州或省组织所在州或省的名称。此名称不可使用缩写。SiChuan(四川)
Locality Name所在地名称组织所在城市的名称。ChengDu(成都)
Organization Name组织名称组织的法定全称。请勿缩写组织名称。百度、腾讯、网易
Organizational Unit Name组织部门可选,用于提供额外的组织信息。开发、测试、市场营销
Common Name公用名别名记录的完全限定域名。如果两者不能精确匹配,那么您会收到一条证书名称检测警告。www.yourdomain.com
Email Address电子邮件地址服务器管理员的电子邮件地址someone@yourdomain.com

参数说明:
-new:表示生成一个新证书签署请求
-x509:专用于CA生成自签证书,如果不是自签证书则不需要此项
-key:用到的私钥文件
-out:证书的保存路径
-days:证书的有效期限,单位是day(天),默认是openssl.cnf的default_days

这样子,根证书CA就已经生成完成了。

4、颁发证书

在需要证书的服务器上生成私钥,然后通过此私钥生成证书签署请求,最后将请求通过可靠的方式发送给根证书CA的主机。根证书CA服务器在拿到证书签署请求后,即可颁发那一服务器的证书。

4.1 在需要证书的服务器上,生成服务器证书签署请求

(1)生成服务器证书的私钥,该私钥的位置可随意定

mkdir test
cd test
#生成服务器证书的私钥
(umask 077; openssl genrsa -out test.key 2048)

(2)生成服务器证书签署请求

openssl req -new -key test.key -out test.csr -days 365

生成证书参数,前4项必须和 生成根证书CA时的参数相同,最好全部相同,参考上面步骤3、(6)

最后提示输入密码:设置自己的密码,这里我输入,123456

 4.2 在根证书服务器上,颁发服务器证书

(1)颁发证书,即签名服务器证书,生成crt文件

上面我的 openssl.cnf 配置文件中
dir = /etc/pki/CA
certs = $dir/certs #发出的证书存放在哪里

#我们创建一个req文件夹来接受服务器发送过来的文件(签署请求的csr文件、key文件等)
mkdir /etc/pki/CA/req

#我这里用的同一台服务器,直接复制过来
cp /root/test/test.csr /etc/pki/CA/req/

#颁发证书
openssl ca -in /etc/pki/CA/req/test.csr -out /etc/pki/CA/certs/test.crt -days 365
 
#查看证书信息
openssl x509 -in /etc/pki/CA/certs/test.crt -noout -serial -subject

(2)格式转换为pfx格式的私钥

cd /etc/pki/CA/certs
#我这里用的同一台服务器,直接复制过来
cp /root/test/test.csr /etc/pki/CA/req/
# 将crt格式转换为带私钥的pfx格式
openssl pkcs12 -export -inkey /etc/pki/CA/req/test.key -in test.crt -out test.pfx 

这里我设置的密码为:654321

(3)格式转换为cer格式的公钥

cd /etc/pki/CA/certs
openssl x509 -inform pem -in test.crt -outform der -out test.cer
#查看cer证书信息
openssl x509 -in test.cer -text -noout
#若报错unable to load certificate,则说明你打开的证书编码是der格式,需要用以下命令
openssl x509 -in test.cer -inform der -text -noout
参数含义:
-inform pem,由于输入的test.crt文件是以pem编码的,故需要指定以pem编码来读取。
-outform der,输出的test.cer文件需要以der编码。


至此,服务器的证书颁发就完成了,只需要将此签名证书发送给服务器,服务器就可以使用此签名证书了。

5、测试

我们此处用java代码来测试分别读取test.pfx和test.cer文件的证书信息。

5.1 读取test.pfx文件

package test;

import java.io.FileInputStream;
import java.security.KeyStore;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.cert.Certificate;
import java.util.Enumeration;
 
public class ReadPFX {
 
	public static void main(String[] args) throws Exception {
		String strPfx = "C:\\test\\test.pfx";
		String strPassword = "654321";
		KeyStore ks = KeyStore.getInstance("PKCS12");
		
		FileInputStream fis = new FileInputStream(strPfx);
		// If the keystore password is empty(""), then we have to set
		// to null, otherwise it won't work!!!
		char[] nPassword = null;
		if ((strPassword == null) || strPassword.trim().equals("")) {
			nPassword = null;
		} else {
			nPassword = strPassword.toCharArray();
		}
		ks.load(fis, nPassword);
		fis.close();
		
		System.out.println("keystore type=" + ks.getType());
		// Now we loop all the aliases, we need the alias to get keys.
		// It seems that this value is the "Friendly name" field in the
		// detals tab <-- Certificate window <-- view <-- Certificate
		// Button <-- Content tab <-- Internet Options <-- Tools menu
		// In MS IE 6.
		Enumeration enumas = ks.aliases();
		String keyAlias = null;
		if (enumas.hasMoreElements())// we are readin just one certificate.
		{
			keyAlias = (String) enumas.nextElement();
			System.out.println("alias=[" + keyAlias + "]");
		}
		
		// Now once we know the alias, we could get the keys.
		System.out.println();
		System.out.println("is key entry=" + ks.isKeyEntry(keyAlias));
		PrivateKey prikey = (PrivateKey) ks.getKey(keyAlias, nPassword);
		Certificate cert = ks.getCertificate(keyAlias);
		PublicKey pubkey = cert.getPublicKey();
		System.out.println("cert class = " + cert.getClass().getName());
		System.out.println("cert = " + cert);
		System.out.println("public key = " + pubkey);
		System.out.println("private key = " + prikey);
	}
 
}

5.2 读取test.cer文件

package test;

import java.io.FileInputStream;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
 
public class ReadCER {
 
	public static void main(String[] args) {
		try {
			CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");
			FileInputStream fileInputStream = new FileInputStream("C:\\test\\test.cer");
			X509Certificate cer = (X509Certificate)certificateFactory.generateCertificate(fileInputStream);
			fileInputStream.close();
			
			System.out.println("读取Cer证书信息...");
			System.out.println("cer_序列号___:"+cer.getSerialNumber());
			System.out.println("cer_发布方标识名___:"+cer.getIssuerDN().getName()); 
			System.out.println("cer_主体标识___:"+cer.getSubjectDN());
			System.out.println("cer_证书算法OID字符串___:"+cer.getSigAlgOID());
			System.out.println("cer_证书有效期___:" + cer.getNotBefore() + "~" + cer.getNotAfter());
			System.out.println("cer_签名算法___:"+cer.getSigAlgName());
			System.out.println("cer_版本号___:"+cer.getVersion());
			System.out.println("cer_公钥___:"+cer.getPublicKey());
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
 
}

5.3 Springboot配置ssl证书

将生成好的 .pfx 复制到 springboot 中的 resources 文件目录下,并配置 .yml/.propertie

server.port=8443

server.ssl.enabled=true
server.ssl.key-store-type=PKCS12
server.ssl.key-store=classpath:test.pfx
server.ssl.key-store-password=654321

测试代码

@RestController
public class TestController {
    // http://localhost:8080/hello
    // https://localhost:8443/hello
    @GetMapping("/hello")
    public String hello() {
        return "hello";
    }
}

在启动类@SpringBootApplication注解类中 或者 @Configuration注解的类中   加上下面代码 让http重定向到https

    /**
     * http重定向到https
     */
    @Bean
    public ServletWebServerFactory servletContainer() {
        TomcatServletWebServerFactory tomcat = new TomcatServletWebServerFactory() {
            @Override
            protected void postProcessContext(Context context) {
                SecurityConstraint securityConstraint = new SecurityConstraint();
                securityConstraint.setUserConstraint("CONFIDENTIAL");
                SecurityCollection collection = new SecurityCollection();
                collection.addPattern("/*");
                securityConstraint.addCollection(collection);
                context.addConstraint(securityConstraint);
            }
        };
        tomcat.addAdditionalTomcatConnectors(httpConnector());
        return tomcat;
    }

    @Bean
    public Connector httpConnector() {
        Connector connector = new Connector("org.apache.coyote.http11.Http11NioProtocol");
        connector.setScheme("http");
        //Connector监听的http的端口号
        connector.setPort(8080);
        connector.setSecure(false);
        //监听到http的端口号后转向到的https的端口号
        connector.setRedirectPort(8443);
        return connector;
    }

Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐