前段时间在做HTTPS相关的需求碰到了一些问题,今天有空整理一下HTTPS的相关知识,希望对您能有所帮助。
图片
HTTPS,即HTTP Security,是建立在SSL / TSL协议之上,其中,TSL是SSL协议的升级版,TLS 1.0通常被标示为SSL 3.1,TLS 1.1为SSL 3.2,TLS 1.2为SSL 3.3,可以理解为同一套协议。他的作用主要以下三点:
防止窃听。 所有信息都是加密传播,第三方无法窃听。
防止篡改。具有校验机制,一旦被篡改,通信双方会立刻发现。
防止冒充。配备身份证书,防止身份被冒充。本文将从Android使用者的角度,尽量解释清楚什么是HTTPS。
交互流程如下:
至于为什么一定要用三个随机数,来生成"会话密钥",dog250解释得很好:
"不管是客户端还是服务器,都需要随机数,这样生成的密钥才不会每次都一样。由于SSL协议中证书是静态的,因此十分有必要引入一种随机因素来保证协商出来的密钥的随机性。
对于RSA密钥交换算法来说,pre-master-key本身就是一个随机数,再加上hello消息中的随机,三个随机数通过一个密钥导出器最终导出一个对称密钥。
pre master的存在在于SSL协议不信任每个主机都能产生完全随机的随机数,如果随机数不随机,那么pre master secret就有可能被猜出来,那么仅适用pre master secret作为密钥就不合适了,因此必须引入新的随机因素,那么客户端和服务器加上pre master secret三个随机数一同生成的密钥就不容易被猜出了,一个伪随机可能完全不随机,可是是三个伪随机就十分接近随机了,每增加一个自由度,随机性增加的可不是一。"
HTTPS涉及到的概念比较多,什么X509,.pem,.crt等等,理解了HTTPS的交互流程之后,先来理一理这些概念。
openssl x509 -in certificate.pem -text -noout
Apache和*NIX服务器偏向于使用这种编码格式.
openssl x509 -in certificate.der -inform der -text -noout
上面是证书标准和两种常用的编码格式。下面我们来认识一下都有哪些文件扩展名。
openssl rsa -in mykey.key -text -noout
如果是DER格式的话,同理应该这样了:openssl rsa -in mykey.key -text -noout -inform der
openssl req -noout -text -in my.csr
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其实是个证书密钥库.
keytool -importcert -tRustcacerts -keystore e:key.bks -file e:server.crt -storetype BKS -provider org.bouncycastle.jce.provider.BouncyCastleProvider
bks的生成参考:SSL通信中使用的bks格式证书的生成
java上使用HTTPS很简单,一个典型的HTTPS方式如下:
URL url = new URL("https://google.com");
HttpsURLConnection urlConnection = url.openConnection();
InputStream in = urlConnection.getInputStream();
此时使用的是默认的SSLSocketFactory,与下段代码使用的SSLContext是一致的
private synchronized SSLSocketFactory getDefaultSSLSocketFactory() {
try {
SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(null, null, null);
return defaultSslSocketFactory = sslContext.getSocketFactory();
} catch (GeneralSecurityException e) {
throw new AssertionError(); // The system has no TLS. Just give up.
}
}
这段代码里面最重要的方法是sslContext.init(null, null, null);,这里有三个参数,分别是:
有时候CA也是不可信的,为了获得更高的安全新,需要我们自己指定新人的锚点,可以采用如下的代码:
// 取到证书的输入流
InputStream is = new FileInputStream("anchor.crt");
CertificateFactory cf = CertificateFactory.getInstance("X.509");
Certificate ca = cf.generateCertificate(is);
// 创建 Keystore 包含我们的证书
String keyStoreType = KeyStore.getDefaultType();
KeyStore keyStore = KeyStore.getInstance(keyStoreType);
keyStore.load(null);
keyStore.setCertificateEntry("anchor", ca);
// 创建一个 TrustManager 仅把 Keystore 中的证书 作为信任的锚点
String algorithm = TrustManagerFactory.getDefaultAlgorithm();
TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(algorithm);
trustManagerFactory.init(keyStore);
TrustManager[] trustManagers = trustManagerFactory.getTrustManagers();
// 用 TrustManager 初始化一个 SSLContext
SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(null, trustManagers, null);
return sslContext.getSocketFactory();
这种情况下,只有 anchor.crt 以及他签发的证书才会被信任。
注意这里使用的是单向认证,也就是只需要客户端验证服务端的证书,客户端本地部缓存证书,所以这里sslContext.init()方法的三个参数都是空的。
默认的 SSLSocketFactory 校验服务器的证书时(也就是TrustManager[]传空的时候),会信任设备内置的100多个根证书。
既然有单向验证,那么也有双向验证,即不仅仅客户端需要校验服务端,服务端也需要验证客户端。这种情况下,客户端需要将自己的证书发给服务端做验证,这种情况只需要将证书的KeyManager作为在init()的第一个参数来SSLContext就行了。