Post

一些 Https 方面的记录

使用Https建议先读一下官方Training Security SSL,很多公司现在都有已经是全站Https了,有些用的并不正确,我这里简单记录一下自己遇到的问题

需要自己去了解的问题:

  • 对称加密
  • 非对称加密

一些签名的类型

  • .DER 一般是一个二进制的证书,扩展名是 CER或者 CRT,指的是一个DER编码的证书
  • .PEM 这个我们看起来就是一个---BEGIN...开头的一个密钥,其实是一个用 Base64X.509v3不同类型密钥编码出来的一个证书
  • .CRTCER 两个基本差不多,不过CRT符合微软的标准
  • .key 使用PKCS #8算法处理完成私钥和公钥的一种证书

对于自签名或者说自己测试的话最好安装openssl自己体验一下,一般签名生成的过程如下

1
2
3
4
5
6
7
8
9
10
11
$ openssl genrsa -out key.pem 1024
Generating RSA private key, 1024 bit long modulus
....................++++++
.....................++++++
e is 65537 (0x10001)
To create a certificate request with that key, you will need to answer some questions. 
The most important one is "Common Name" which MUST be the hostname of the server you're 
creating a certificate for. Only "Common Name" is required, for the SSL-RFC, but your 
Certificate Authority (Verisign, Thawte, etc.) will require some of the other fields. 
Just fill them all out, to be safe. The last two questions may be required for the 
registration procedure of some certificate authorities.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
$ openssl req -new -key key.pem -out request.pem
You are about to be asked to enter information that will be incorporated
into your certificate request.
What you are about to enter is what is called a Distinguished Name or a DN.
There are quite a few fields but you can leave some blank
For some fields there will be a default value,
If you enter '.', the field will be left blank.
-----
Country Name (2 letter code) [AU]:CN
State or Province Name (full name) [Some-State]:Beijing
Locality Name (eg, city) []:Beijing
Organization Name (eg, company) [Internet Widgits Pty Ltd]:Hxq
Organizational Unit Name (eg, section) []:hxq
Common Name (e.g. server FQDN or YOUR name) []:hao
Email Address []:haoxiqiang@live.com
Please enter the following 'extra' attributes
to be sent with your certificate request
A challenge password []:haoxiqiang
An optional company name []:Hxq
You can now send the request.pem file to some commercial Certificate Authority and wait for 
the certificate, or create your own "self-signed" certificate for testing purposes.
To create the self-signed certificate, do the following:
1
2
$ openssl req -x509 -days 30 -key key.pem -in request.pem -out certificate.pem
This may be wrong. The right way may be:
1
2
$ openssl x509 -req -days 30 -in request.pem -signkey key.pem -out certificate.pem
When you get your actual certificate from the CA, you may have to convert it from binary/DER to PEM format:
1
2
3
4
5
6
7
8
9
10
11
12
$ openssl x509 -inform der -in certificate.crt -out certificate.pem
Obtaining a server certificate with Internet Explorer
Most Certificate Authorities have an website that lets you request a certificate with Internet 
Explorer. It is very simple to convert such a certificate to a cert.pem and key.pem file. After 
you have obtained the certificate (do make sure that you keep the key exportable when you request 
the certificate!), you must first export it to a file:
From internet explorer, open the Tools menu, and choose Internet options. On the 'Content' tab 
click the 'Certificates' button. Select your server certificate and click 'export'.
Select that you want to export the private key, on the .PFX options, DO NOT include all certificate 
in the path and DO NOT enable strong protection. When asked to, provide a nice and long password 
if you need to transfer the resulting certificate.pfx file over email.
Now on your server machine:
1
$ openssl pkcs12 -nodes -in certificate.pfx -out certificate.pem

对于服务端已有证书的可以直接取下来使用

1
echo | openssl s_client -connect hostname:443 2>&1 |  sed -ne '/-BEGIN CERTIFICATE-/,/-END CERTIFICATE-/p' > hostname.pem

因为Android一般只能识别BKS类型的证书,需要转换一下. 这个文件一般都是放到了 raw内的,但是很多时候不加文件直接用文本也是可以的, 因为HttpsURLConnection.setSSLSocketFactory(SSLSocketFactory)时,创建的形式一般是

1
2
3
4
5
6
7
8
9
InputStream inputStream = context.getResources().openRawResource(res);
KeyStore keyStore = KeyStore.getInstance("BKS");
keyStore.load(inputStream, password.toCharArray());
KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
keyManagerFactory.init(keyStore, password.toCharArray());
TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
trustManagerFactory.init(keyStore);
SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(keyManagerFactory.getKeyManagers(), trustManagerFactory.getTrustManagers(), new SecureRandom());

所以直接用String->Stream也是可以的

转换证书类型一般使用有:

Bouncy Castle 下面贴一下命令 Portecle 这个似乎有 UI 版本的,不过没有用过

1
2
3
4
5
6
7
8
keytool -importcert -v 
 -trustcacerts 
 -alias 0 
 -file <(openssl x509 -in hostname.pem) 
 -keystore $CERTSTORE -storetype BKS 
 -providerclass org.bouncycastle.jce.provider.BouncyCastleProvider 
 -providerpath bcprov-jdk16-1.46.jar 
 -storepass password

#查看一些证书的信息能直接使用openssl来操作

1
2
3
openssl x509 -in cert.pem -text -noout
openssl x509 -in cert.cer -text -noout
openssl x509 -in cert.crt -text -noout
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# PEM to DER
$ openssl x509 -outform der -in certificate.pem -out certificate.der
# PEM to P7B
$ openssl crl2pkcs7 -nocrl -certfile certificate.cer -out certificate.p7b -certfile CAcert.cer
# PEM to PFX
$ openssl pkcs12 -export -out certificate.pfx -inkey privateKey.key -in certificate.crt -certfile CAcert.crt
# DER to PEM
$ openssl x509 -inform der -in certificate.cer -out certificate.pem
# P7B to PEM
$ openssl pkcs7 -print_certs -in certificate.p7b -out certificate.cer
# P7B to PFX
$ openssl pkcs7 -print_certs -in certificate.p7b -out certificate.cer
$ openssl pkcs12 -export -in certificate.cer -inkey privateKey.key -out certificate.pfx -certfile CAcert.cer
# PFX to PEM
$ openssl pkcs12 -in certificate.pfx -out certificate.cer -nodes

备注几个遇到的问题

  • SSLPeerUnverifiedException:Hostname not verified 因为我自签发的证书没有注意到我自己搭建的服务器域名不匹配的问题,解决的办法有
    1
    2
    3
    4
    5
    6
    7
    8
    
    HostnameVerifier hostnameVerifier = new HostnameVerifier() {
      @Override
      public boolean verify(String hostname, SSLSession session) {
          HostnameVerifier hv =
              HttpsURLConnection.getDefaultHostnameVerifier();
          return hv.verify("example.com", session);
      }
    };
    

Itroadmap

This post is licensed under CC BY 4.0 by the author.