非对称加密
也称为公钥加密,是一种加密技术,使用一对公钥和私钥进行加密和解密操作。这种加密方式的核心特点是:公钥和私钥是成对出现的,公钥用于加密数据,私钥用于解密数据,反之亦然。由于公钥可以公开,而私钥必须保密,因此非对称加密在安全性、密钥管理和通信中具有独特的优势。
非对称加密的工作原理
- 密钥对生成:
- 用户生成一对密钥:公钥(Public Key)和私钥(Private Key)。
- 公钥可以公开分发给任何人,而私钥必须严格保密。
- 加密过程:
- 发送方使用接收方的公钥对数据进行加密。
- 加密后的数据只有持有对应私钥的接收方才能解密。
- 解密过程:
- 接收方使用自己的私钥对加密数据进行解密,恢复原始数据。
非对称加密的应用场景
- 安全通信:
- 在网络通信中,非对称加密用于保护数据的机密性。例如,HTTPS协议中,服务器的公钥用于加密客户端和服务器之间的通信数据,只有服务器的私钥才能解密。
- 数字签名:
- 非对称加密用于验证消息的完整性和发送者的身份。发送方使用自己的私钥对消息生成数字签名,接收方使用发送方的公钥验证签名的真实性。
- 密钥交换:
- 非对称加密用于安全地交换对称加密的密钥。例如,Diffie-Hellman密钥交换协议允许双方在不安全的通道上协商一个共享密钥,用于后续的对称加密通信。
- 身份认证:
- 在身份认证中,客户端使用服务器的公钥加密数据,服务器使用私钥解密,从而验证客户端的身份。
证书
非对称加密的公钥载体。
用户证书
标志用户身份的证书,比如某个server端的身份证书。
X509证书结构
我们使用java的X509Certificate类即可获得证书的信息,代码如下:
public final class CertUtil {
public static X509Certificate certFromPath(String path) throws Exception {
CertificateFactory cf = CertificateFactory.getInstance("X.509");
try (InputStream is = new FileInputStream(path)) {
return (X509Certificate) cf.generateCertificate(is);
}
}
输入文件路径,即可得到一个X509Certificate类实例,我们打印这个实例,看下其中的内容:
[
[
Version: V3
Subject: O=ABC, CN=ABC2048
Signature Algorithm: SHA1withRSA, OID = 1.2.840.113549.1.1.5
Key: Sun RSA public key, 2048 bits
modulus: …
public exponent: 65537
Validity: [From: Fri Aug 02 16:15:39 CST 2013,
To: Thu Jul 28 17:15:39 CST 2033]
Issuer: O=ABC, CN=ABC2048
SerialNumber: [ 7b97ca10 275a0000 0000]
Certificate Extensions: 4
…
]
Algorithm: [SHA1withRSA]
Signature:
0000: 4D DA C0 7C 5D 2F 30 AD D2 28 F7 11 B3 AA 48 48 M…]/0…(…HH
0010: F3 02 39 4E B2 CE FF 99 06 2D 20 B6 F9 42 23 93 …9N…- …B#.
…
]
主要是这么几项:
-
证书版本,一般是V3;
-
证书持有者(Subject),注意,身份证书的Subject字段里的CN字段必须是要访问的域名,访问某个server时就匹配域名和CN,来从证书库(一般为trustStore文件)里选取对应的身份证书;
-
签名算法(这里是SHA1withRSA,该算法会有一个固定的OID);
-
公钥(这是最关键的,证书本质上就是公钥的载体),由modulus+public exponent组成;
-
证书有效时间区间;
-
颁发者(Issuer),一般是某个CA机构;
-
序列号(每个证书唯一,用于CRL中的证书失效列表);
-
一些扩展项,包括CRL召回网址等;
上述内容我们不妨称之为核心证书C。
证书的验证
核心证书C后跟着证书签名,签名是用私钥对C进行摘要并加密后的内容,它起到校验证书的作用,我们知道,不对称加密体系下,公钥和私钥是一一对应的,公钥加密的内容只有私钥才能解,反之亦然。私钥是我自己持有的,我用私钥加密核心证书C的摘要得到一份签名S,然后把C+S传到对端,对端用C里的公钥+签名算法就可以解出S的明文T,接着再用公开的摘要算法对C做摘要得到T’,比较T和T’就可以判断公私钥是否匹配,从而判断我的身份。
上述过程用java代码表示是:
public void testVerify() throws Exception {
X509Certificate dev = CertUtil.certFromPath("E:\\01study\\07Java\\certs\\dev1.cer");
Signature sig = Signature.getInstance(dev.getSigAlgName());
sig.initVerify(dev.getPublicKey()); //设置公钥
//we should use getTBSCertificate NOT getEncoded, cause the later is whole file which includes signature
sig.update(dev.getTBSCertificate()); //设置核心证书C
boolean match = sig.verify(dev.getSignature()); //对签名做解密及比对
if (match) {
System.out.println("matched");
} else {
System.out.println("ERROR:unmatched");
}
}
其实,testVerify的整个过程已由X509Certificate::verify封装好了提供出来。我们这里相当于解释了一下X509Certificate::verify的原理。
CA证书
只有用户证书是不够的,任何人都可以广播一份用户证书出来说“我是刘德华,你们把消息都发给我吧”,可谁能证明这份证书真的就是刘德华的而非别人冒充的?换个说法,用户证书的可靠性该由谁来保证?于是就有了CA(certificate authority)这个第三方权威机构,大家只认CA签发的用户证书。
好,用户证书的可靠性解决了,新的问题来了:如何确认CA自身的身份不是伪造的?
一般说来,我们向CA申请用户证书时,随用户证书一起,CA也会公布它自己的身份证书(Certificate Authority Certificate),该证书即为CA证书。CA证书可不止一份,它和用户证书一起构成一条证书链,链的顶层为根CA证书,链的次级为中间CA证书,最底层才是用户证书。根CA证书是由CA自签名的证书,因而不再依赖其它证书,一般是预置在系统或浏览器中,它是信任验证的终点。中间证书是由根证书或其他中间证书签发的证书,位于根证书和最终用户证书之间,一般不预置。
证书链的验证过程:
- 获取用户证书(例如,server端身份证书),得到其颁发CA,并找到该CA对应的中间CA证书。
- 通过中间证书向上追溯到根证书。
- 如果根证书可信,则整个证书链验证通过。
自签名证书
是由证书所有者自己生成和签名的数字证书,而不是由权威的第三方证书颁发机构(CA)签发的证书。它的Subject和Issuer是相同的,由于未经过权威CA的验证,因此在公共网络中信任度较低,一般用于内部开发。
java的X509TrustManager类
x509校验接口定义:
public interface X509TrustManager extends TrustManager {
// 验证客户端证书
void checkClientTrusted(X509Certificate[] var1, String authType) throws CertificateException;
// 验证服务端证书
void checkServerTrusted(X509Certificate[] var1, String authType) throws CertificateException;
// 返回受信任的CA(证书颁发机构)证书列表
X509Certificate[] getAcceptedIssuers();
}
checkServerTrusted就是前文说的client验证server端证书,校验不通过则抛异常。一般情况下我们实现checkServerTrusted接口即可。下面是一个实现的例子:
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import javax.net.ssl.X509TrustManager;
public class CustomTrustManager implements X509TrustManager {
@Override
public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
// 不验证客户端证书
}
@Override
public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
// 验证服务器证书链,chain的第一个是用户证书,最后一个是根证书
TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
trustManagerFactory.init((KeyStore) null);
TrustManager[] trustManagers = trustManagerFactory.getTrustManagers();
for (TrustManager trustManager : trustManagers) {
if (trustManager instanceof X509TrustManager) {
((X509TrustManager) trustManager).checkServerTrusted(chain, authType);
return;
}
}
throw new CertificateException("Failed to validate server certificate chain.");
}
@Override
public X509Certificate[] getAcceptedIssuers() {
return new X509Certificate[0];
}
}