网络OSI模型中安全服务建立在大量密码学算法之上:消息摘要算法用于验证数据完整性服务,对称加密算法和非对称加密算法用于保证数据保密性服务,数字签名算法用于抗否认性服务。
网络安全向来都不是一个简单的事情,单一的防御手段,很难达到期望的安全强度!安全的网络平台,需要多重密码学算法有机结合!
数字证书正是多种密码算法结合的产物:自身带有公钥信息,可以完成数据的加密/解密操作; 同时,携带的数字i签名,可以鉴别消息来源; 自身带有的消息摘要信息,可验证证书完整性; 由于证书本身含有用户身份信息,因而具有认证性。
数字证书为发布公钥提供了一种简单的途径,成为加密算法以及公钥的载体。数字证书也叫做电子证书,类似我们生活中的身份证。数字证书由数字证书颁发机构(Certificate Authority, CA)签发, 只有经过CA签发的证书在网络中才具备认证性。
国际权威数字证书颁发认证机构三巨头: VeriSign, GeoTrust, Thawte,以VeriSign签发的电子商务用数字证书应用最为广泛。通常这三大机构签发证书时需要收费,而且价格不菲。当然我们可以选择申请免费的数字证书,例如CAcert(http://www.cacert.org/)这个数字证书国际组织。
证书签发过程实际是对申请数字证书的公钥做数字签名,证书的验证过程实际是对数字证书公钥做验证签名,其中包含证书有效期验证。
CA机构颁发给自己的证书叫做 “根证书”
通过使用CA颁发的数字证书,我们可以对网络上传输的数据进行加密/解密 和签名/验证操作,确保数据的机密性、完整性和抗否认性。数字证书中包含的用户信息,保证了认证性、真实性。
实际上,数字证书采用了公钥基础设施(Public Key Infrastructure, PKI),使用了相应的加密算法确保网络应用的安全性:
目前,数字证书中最为常用的非对称加密算法是RSA算法,与之配套使用的签名算法是SHA1withRSA算法,而最为常用的消息摘要算法是SHA1算法。
除了RSA算法外,还可以使用DSA算法。DSA算法证书不包含加密/解密功能。
数字证书有多种文件编码格式,主要包含cer编码、der编码等:
所有证书符合公钥基础设施(PKI)制定的ITU-T X509国际标准(X.509标准),目前包含3个版本。
公钥加密标准 | 描述信息 | 文件后缀 |
---|---|---|
PKCS#7 | 密码消息语法标准 | .p7b、 .p7c、.spc |
PKCS#10 | 证书请求语法标准 | .p10、.csr |
PKCS#12 | 个人信息交换语法标准 | .p12、.pfx |
以上标准主要用于证书的申请和更新等操作,例如,PKCS#10文件用于证书签发申请, PKCS#12文件可作为Java中密钥库或信任库直接使用。
通常使用Base64编码格式作为数字证书存储格式,如下图所示
实际应用中,很多数字证书属于自签名证书,即证书申请者为自己的证书签名。
证书通常是向权威CA机构申请,任何机构和个人都可以申请。常用数字证书管理工具是KeyTool(Java领域)和OpenSSL。OpenSSL比KeyTool强大很多。
获取数字证书流程:我们使用数字证书管理工具构建CSR(Certification Siging Request,数字证书签发申请),交由CA机构签发,形成最终数字证书。
自签名证书:自己也可以给自己签名,这种签名后的证书叫做自签名证书。
根证书:用于给证书签名的证书叫做根证书。
KeyTool是Java中数字证书管理工具,用于数字证书的申请、导入、导出和撤销操作。
KeyTool与本地密钥库相关联,私钥存于密钥库,公钥以数字证书输出。
~/certificationTest$ keytool -genkeypair -keyalg RSA -keysize 2048 -sigalg SHA1withRSA -validity 36000 -alias www.calvin.com -keystore calvin.keystore
参数含义:
-genkeypair
表示生成密钥
-keyalg
表示密钥算法,这里指定为RSA算法
-keysize
指定密钥长度,默认1024,这里指定2048位。
-sigalg
指定数字签名算法,这里指定为SHA1withRSA算法。
-validity
指定证书有效期,这里指定为36000天。
-keystore
指定密钥库存储位置,且DSA算法为默认算法。
执行上述命令,会要求交互式输入密码、组织、域名等信息, 域名也可以使用带通配符"*"的泛域名,如*.calvin.com
标识用户身份。
可以使用 -storepass
指定密码, 使用-dname
指定用户信息。完整命令如下:
~/certificationTest$ keytool -genkeypair -keyalg RSA -keysize 2048 -sigalg SHA1withRSA -validity 36000 -alias www.calvin.com -keystore calvin.keystore -storepass 123456 -dname "CN=www.calvin.com, OU=calvin, O=calvin, L=hz, ST=hz, C=CN"
上述操作后,密钥库中已经创建了数字证书。虽然没有经过CA机构认证,但是仍然可以使用。下面是导出证书命令。
keytool -exportcert -alias www.calvin.com -keystore calvin.keystore -file calvin.cer -rfc
参数含义:
-exportcert
表示证书导出操作
-alias
指定别名,这里是www.calvin.com
-keystore
指定密钥库文件,这里是calvin.keystore
-file
指定导出文件路径,这里是calvin.cer
-rfc
指定以Base64编码格式输出
执行上述命令,会要求输入密码,可以使用-storepass
参数指定密码。
查看证书命令如下:
~certificationTest$ keytool -printcert -v -file calvin.cer
参数说明:
-printcert
表示证书打印操作
-file
指定要打印的证书文件
-v
详细信息
执行结果如下图所示
上述的calvin.cer
证书是一个自签名的X.509 v3类型的根证书,并以Base64编码保存。
在使用自签名证书时,我们需要将其导入系统。
要想获取CA机构认证的数字证书,需要将数字证书签发申请(CSR)导出,经由CA机构认证并颁发,同时将认证后的证书导入本地密钥库和信任库。
导出数字证书签发申请命令如下
~certificationTest$ keytool -certreq -alias www.calvin.com -keystore calvin.keystore -file calvin.csr -v -storepass 123456
参数说明:
-certreq
表示数字证书申请操作。
-alias
指定别名
-file
指定导出文件路径
-keystore
指定密钥库文件,这里是calvin.keystore
-v
详细信息
执行结果如下图所示
calvin.csr
文件是一个PKCS#10编码格式的数字证书签发申请文件。
CA证书签发机构
获取到签发的数字证书后,将其导入信任库,导入数字证书命令如下:
~certificationTest$ keytool -importcert -trustcacerts -alias www.calvin.com -file calvin.cer -keystore calvin.keystore -storepass 123456
参数说明:
-importcert
表示数字证书导入操作。
-alias
指定别名
-file
指定要导入的证书文件路径
-trustcacerts
表示将数字证书导入信任库
-keystore
指定密钥库文件,这里是calvin.keystore
-storepass
密钥库密码
查看导入的数字证书, 命令如下:
~certificationTest$ keytool -list -alias www.calvin.com -keystore calvin.keystore -storepass 123456 -v
执行结果如下图所示:
参数说明:
-list
表示导入的数字证书列表
-alias
指定别名
-keystore
指定密钥库文件,这里是calvin.keystore
上述命令中,可以加入“-v” 或 “-rfc” 参数查看证书详细信息。
OpenSSL是 ssl/tls协议的开源实现,也是常用的证书管理工具, 可用于根证书、服务器证书、客户端证书的管理,功能远胜Keytool。
请到openssl官网下载安装,openssl官网下载地址:https://www.openssl.org/source/
作者的电脑是Ubuntu,所以安装的是linux版本。
首先做一些准备工作,构建一些目录,用于存放证书、密钥, 构建一些文件用于证书生成过程。
工作目录在
certificationTest$
在该目录下依次键入下面的命令
#构建已发行证书存放目录 certs
mkdir certs
#构建新证书存放目录 newcerts
mkdir newcerts
#构建私钥存放目录
mkdir private
#构建证书吊销列表存放目录crl
mkdir crl
# 构建索引文件
echo 0>index.txt
#构建序列号文件 serial
echo 01>serial
接下来,我们可以进行证书构建和签发工作了。
#构建随机数文件, 需要root权限
openssl rand -out private/.rand 1000
#构建根证书私钥 private/ca.key.pem
openssl genrsa -aes256 -out private/ca.key.pem 2048
openssl genrsa -aes256 -out private/ca.key.pem 2048
命令参数解释如下:
genrsa
产生RSA密钥命令
-aes256
使用AES算法(256位密钥)对产生的私钥加密。可选算法DES、DESede、IDEA和AES
-out
指定私钥输出路径
上述命令需要交互式输入密钥。
完成密钥构建后,下一步是生成根证书签发申请文件(ca.csr), 命令如下:
#生成根证书签发申请
openssl req -new -key private/ca.key.pem -out private/ca.csr -subj"/C=CN/ST=zj/L=hz/O=calvin/OU=calvin/CN=*.calvin.com"
参数含义如下:
req
产生证书签发申请命令
-new
表示新请求
-key
密钥
-out
证书申请文件输出路径
-subj
指定用户信息
得到根证书申请文件后,我们可以发给CA机构签发,也可以自行签发根证书。
签发根证书命令如下:
#签发根证书private/ca.cer
openssl x509 -req -days 10000 -sha1 -extensions v3_ca -signkey private/ca.key.pem -in private/ca.csr -out certs/ca.cer
参数含义:
x509
签发X.509格式证书命令
-req
表示证书输入请求
-days
表示有效天数
-sha1
证书摘要算法,这里为SHA1算法
-extensions
表示按OpenSSL配置文件v3_ca项添加扩展
-signkey
表示自签名密钥,这里是private/ca.key.pem
-in
表示输入文件
-out
表示输出文件
OpenSSL产生的数字证书不能在Java环境下直接属于,需要转成PKCS#12编码格式,转换命令如下:
#证书转换
openssl pkcs12 -export -cacerts -inkey private/ca.key.pem -in certs/ca.cer -out certs/ca.p12
参数含义
pkcs12
PKCS#12编码格式证书命令
-export
导出证书
-cacerts
仅导出CA证书
-inkey
表示输入密钥
-in
表示输入密钥
-out
表示输出文件
certs/ca.p12
是PKCS#12格式的文件,可以作为密钥库或可信任库使用, 使用keytool工具可以查看其详细信息。
查看命令如下:
keytool -list -keystore certs/ca.p12 -storetype pkcs12 -v -storepass 123456
查询结果如下图所示:
现在,我们已经有了根证书(ca.cer), 可以使用根证书签发服务器证书和客户证书了。
构建服务器证书的命令同根证书命令几乎一致
openssl genrsa -aes256 -out private/server.key.pem 2048
openssl req -new -key private/server.key.pem -out private/server.csr -subj "/C=CN/ST=HZ/L=HZ/O=calvin/OU=calvin/CN=www.calvin.com"
openssl x509 -req -days 3650 -sha1 -extensions v3_req -CA certs/ca.cer -CAkey private/ca.key.pem -CAserial ca.srl -CAcreateserial -in private/server.csr -out certs/server.cer
openssl pkcs12 -export -cacerts -inkey private/server.key.pem -in certs/server.cer -out certs/server.p12
客户证书构建与服务器证书构建基本一致,这里就省略相关构建流程。
以keytool生成的证书文件为例,演示证书的操作,包括加密/解密以及签名/验证等。
密钥库可以看作私钥的操作入口, 数字证书则可以看作公钥匙操作入口。
package com.calvin.android.demo2.secrity;
import java.io.FileInputStream;
import java.security.KeyStore;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.Signature;
import java.security.cert.Certificate;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import javax.crypto.Cipher;
/**
* Author:cl
* Email:[email protected]
* Date:20-10-26
*/
public class CertificateCoder {
public static final String CERT_TYPE = "X.509";
/**
* 由KeyStore获得私钥
* @param keyStorePath 密钥库路径
* @param alias 别名
* @param password 密码
* @return PrivateKey 私钥
* @throws Exception 异常
*/
private static PrivateKey getPrivateKeyByKeyStore(String keyStorePath, String alias, String password) throws Exception {
//获取密钥库
KeyStore ks = getKeyStore(keyStorePath, password);
//获得私钥
return (PrivateKey) ks.getKey(alias, password.toCharArray());
}
private static PublicKey getPublicKeyByCertificate(String certificatePath) throws Exception{
//获得证书
Certificate certificate = getCertificate(certificatePath);
//获得公钥
return certificate.getPublicKey();
}
private static Certificate getCertificate(String certificatePath) throws Exception {
//实例化证书工厂
CertificateFactory certificateFactory = CertificateFactory.getInstance(CERT_TYPE);
//取得证书文件流
FileInputStream in = new FileInputStream(certificatePath);
Certificate certificate = certificateFactory.generateCertificate(in);
in.close();
return certificate;
}
/**
* 获得Certificate
* @param keyStorePath 密钥库路径
* @param alias 别名
* @param password 密码
* @return Certificate 证书
* @throws Exception 异常
*/
private static Certificate getCertificate(String keyStorePath, String alias, String password) throws Exception {
//获取密钥库
KeyStore ks = getKeyStore(keyStorePath, password);
//获得证书
return ks.getCertificate(alias);
}
/**
* 获取KeyStore
* @param keyStorePath 密钥库路径
* @param password 密码
* @return KeyStore 密钥库
* @throws Exception 异常
*/
private static KeyStore getKeyStore(String keyStorePath, String password) throws Exception{
String ksType = KeyStore.getDefaultType();
System.out.println("getKeyStore, ksType = "+ksType);
//实例化密钥库
KeyStore ks = KeyStore.getInstance(ksType);
//获取密钥库文件流
FileInputStream is = new FileInputStream(keyStorePath);
//加载密钥库
ks.load(is, password.toCharArray());
//关闭密钥库文件流
is.close();
return ks;
}
/**
* 私钥加密
* @param data 待加密数据
* @param keyStorePath 密钥库路径
* @param alias 别名
* @param password 密码
* @return byte[] 加密数据
* @throws Exception
*/
public static byte[] encryptByPrivateKey(byte[] data, String keyStorePath, String alias, String password) throws Exception {
//取得私钥
PrivateKey privateKey = getPrivateKeyByKeyStore(keyStorePath, alias, password);
//对数据加密
Cipher cipher = Cipher.getInstance(privateKey.getAlgorithm());
cipher.init(Cipher.ENCRYPT_MODE, privateKey);
return cipher.doFinal(data);
}
/**
* 私钥解密
* @param data 待解密数据
* @param keyStorePath 密钥库路径
* @param alias 别名
* @param password 密码
* @return byte[] 解数据
* @throws Exception
*/
public static byte[] decryptByPrivateKey(byte[] data, String keyStorePath, String alias, String password) throws Exception {
//取得私钥
PrivateKey privateKey = getPrivateKeyByKeyStore(keyStorePath, alias, password);
//对数据解密
Cipher cipher = Cipher.getInstance(privateKey.getAlgorithm());
cipher.init(Cipher.DECRYPT_MODE, privateKey);
return cipher.doFinal(data);
}
/**
* 公钥加密
* @param data 待加密数据
* @param certificatePath 证书路径
* @return byte[] 加密数据
* @throws Exception
*/
public static byte[] encryptByPublicKey(byte[] data, String certificatePath) throws Exception {
//取得公钥
PublicKey publicKey = getPublicKeyByCertificate(certificatePath);
//对数据加密
Cipher cipher = Cipher.getInstance(publicKey.getAlgorithm());
cipher.init(Cipher.ENCRYPT_MODE, publicKey);
return cipher.doFinal(data);
}
/**
* 公钥解密
* @param data 待解密数据
* @param certificatePath 证书路径
* @return byte[] 解数据
* @throws Exception
*/
public static byte[] decryptByPublicKey(byte[] data, String certificatePath) throws Exception {
//取得私钥
PublicKey publicKey = getPublicKeyByCertificate(certificatePath);
//对数据解密
Cipher cipher = Cipher.getInstance(publicKey.getAlgorithm());
cipher.init(Cipher.DECRYPT_MODE, publicKey);
return cipher.doFinal(data);
}
/**
* 亲名
* @param data byte[] 待签名数据
* @param keyStorePath 密钥库路径
* @param alias 别名
* @param password 密码
* @return byte[] 签名后的数据
* @throws Exception
*/
public static byte[] sign(byte[] data, String keyStorePath, String alias, String password) throws Exception {
X509Certificate x509Certificate = (X509Certificate) getCertificate(keyStorePath, alias, password);
Signature signature = Signature.getInstance(x509Certificate.getSigAlgName());
PrivateKey privateKey = getPrivateKeyByKeyStore(keyStorePath, alias, password);
signature.initSign(privateKey);
signature.update(data);
return signature.sign();
}
/**
* 验签
* @param data 数据
* @param sign 签名
* @param certificatePath 证书路径
* @return boolean 验证通过为true
* @throws Exception
*/
public static boolean verify(byte[] data, byte[] sign, String certificatePath) throws Exception {
//获取证书
X509Certificate x509Certificate = (X509Certificate) getCertificate(certificatePath);
Signature signature = Signature.getInstance(x509Certificate.getSigAlgName());
//由证书初始化签名,实际上是使用了证书中的公钥
signature.initVerify(x509Certificate);
signature.update(data);
return signature.verify(sign);
}
}
private String password = "123456";
private String alias = "www.calvin.com";
private String certificatePath = Environment.getExternalStorageDirectory().getAbsolutePath()+ File.separator+"calvin.cer";
private String keyStorePath = Environment.getExternalStorageDirectory().getAbsolutePath()+ File.separator+"calvin.bks";
//java.io.IOException: Wrong version of key store.
//at com.android.org.bouncycastle.jcajce.provider.keystore.bc.BcKeyStoreSpi.engineLoad(BcKeyStoreSpi.java:811)
//at java.security.KeyStore.load(KeyStore.java:1247)
// 该异常解决办法链接
// http://shaoyance.com/2020/04/20/35-https%E5%8F%8C%E5%90%91%E8%AE%A4%E8%AF%81%E5%AE%9E%E8%B7%B5/
//java.io.IOException: Wrong version of key store.的解决记录
//http://www.xidige.com/785
@Test
public void certificateTest() throws Exception {
System.out.println("公钥加密-私钥解密");
String inputStr = "数字证书";
byte[] data = inputStr.getBytes();
//公钥加密
byte[] encrypt = CertificateCoder.encryptByPublicKey(data, certificatePath);
//私钥解密
byte[] decrypt = CertificateCoder.decryptByPrivateKey(encrypt, keyStorePath, alias, password);
String outputStr = new String(decrypt);
System.out.println("加密前:\n"+inputStr);
System.out.println("解密后:\n"+outputStr);
//验证一致性
assertArrayEquals(data, decrypt);
}
@Test
public void certificateTest2() throws Exception {
System.out.println("私钥加密-公钥解密");
String inputStr = "数字证书";
byte[] data = inputStr.getBytes();
//私钥加密
byte[] encrypt = CertificateCoder.encryptByPrivateKey(data, keyStorePath, alias, password);
//公钥解密
byte[] decrypt = CertificateCoder.decryptByPublicKey(encrypt, certificatePath);
String outputStr = new String(decrypt);
System.out.println("加密前:\n"+inputStr);
System.out.println("解密后:\n"+outputStr);
//验证一致性
assertEquals(inputStr, outputStr);
}
@Test
public void certificateSignTest() throws Exception {
String inputStr = "签名";
byte[] data = inputStr.getBytes();
System.out.println("私钥签名-公钥验证");
//产生签名
byte[] sign = CertificateCoder.sign(data, keyStorePath, alias, password);
System.out.println("签名:\n"+Hex.toHexString(sign));
//验证签名
boolean status = CertificateCoder.verify(data, sign,certificatePath);
System.out.println("状态: \n"+status);
assertTrue(status);
}
2020-10-27 11:40:17.427 24007-24022/com.calvin.android.demo2 I/System.out: 公钥加密-私钥解密
2020-10-27 11:40:17.456 24007-24022/com.calvin.android.demo2 I/System.out: getKeyStore, ksType = BKS
2020-10-27 11:40:17.652 24007-24022/com.calvin.android.demo2 I/System.out: 加密前:
2020-10-27 11:40:17.652 24007-24022/com.calvin.android.demo2 I/System.out: 数字证书
2020-10-27 11:40:17.653 24007-24022/com.calvin.android.demo2 I/System.out: 解密后:
2020-10-27 11:40:17.653 24007-24022/com.calvin.android.demo2 I/System.out: 数字证书
2020-10-27 11:41:07.447 24088-24103/com.calvin.android.demo2 I/System.out: 私钥加密-公钥解密
2020-10-27 11:41:07.448 24088-24103/com.calvin.android.demo2 I/System.out: getKeyStore, ksType = BKS
2020-10-27 11:41:07.671 24088-24103/com.calvin.android.demo2 I/System.out: 加密前:
2020-10-27 11:41:07.671 24088-24103/com.calvin.android.demo2 I/System.out: 数字证书
2020-10-27 11:41:07.671 24088-24103/com.calvin.android.demo2 I/System.out: 解密后:
2020-10-27 11:41:07.671 24088-24103/com.calvin.android.demo2 I/System.out: 数字证书
2020-10-27 11:41:50.918 24168-24183/com.calvin.android.demo2 I/System.out: 私钥签名-公钥验证
2020-10-27 11:41:50.918 24168-24183/com.calvin.android.demo2 I/System.out: getKeyStore, ksType = BKS
2020-10-27 11:41:50.982 24168-24183/com.calvin.android.demo2 I/System.out: getKeyStore, ksType = BKS
2020-10-27 11:41:51.127 24168-24183/com.calvin.android.demo2 I/System.out: 签名:
2020-10-27 11:41:51.127 24168-24183/com.calvin.android.demo2 I/System.out: 9734edf6f2bf4b62583f633505576085db3474df3835af8b331c5c6f09e6ee2f20ac163badd5dc6800b33ac437d28704c80092e3620d4c474948da48180a5d1115f46027ca51b03810732933c4db8aaa22dd1943ad4b9a098256691a99289ac7f149d3c53a5a3fc560652b0efdd1b7bcd28e2d5cc2768f1bc4a7b59c8a2a8d681293b944496e6f088d373ad6f07920e2f27ae1eb210597206a65c317bd72e0c41c1af6c67b15e85bb5f4b374771102177b01e20f8f5e7d42da2f674aacb3b14c7d370d3c570dd8f5954d98b6427530cf7cd5dac9b94aecc4bb6bcf8c97f21e401a90c8d0a0789e52295585c37bfa3836d9c82e32fbb0c915d38fab8e7841bae0
2020-10-27 11:41:51.153 24168-24183/com.calvin.android.demo2 I/System.out: 状态:
2020-10-27 11:41:51.153 24168-24183/com.calvin.android.demo2 I/System.out: true