互联网安全是个庞大的概念,这里仅讨论网络中两个节点之间的通信(通常是客户端和服务端),如何保证数据不被窃取?
答案是通过加解密,使得数据即使被第三方拦截,第三方也无法获取有效的信息。
对一段消息,通过单向Hash函数,生成一段固定长度的并且唯一的摘要。
有些人也称消息摘要算法为单向加密的算法,因为生成的摘要无法解密回原来的消息。
算法 | 摘要长度(位) |
---|---|
MD5 | 128 |
SHA-1 | 160 |
jdk定义了MessageDegist类,代表消息摘要算法。
public static byte[] messageDigest(String algorithm, byte[] content) {
try {
MessageDigest digest = MessageDigest.getInstance(algorithm);
return digest.digest(content);
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
throw new RuntimeException(e);
}
}
public static byte[] md5(byte[] content) {
return messageDigest("MD5", content);
}
public static byte[] sha1(byte[] content) {
return messageDigest("SHA-1", content);
}
对称加密算法只有一个密钥,加密消息和解密消息都使用同一个密钥。
使用对称加密算法,只要私钥不泄露,双方传输的消息就是安全的。
算法 | 密钥长度(位) |
---|---|
DES | 56 |
AES | 128或192或256 |
实际上jdk提供了加解密的框架,主要接口如下:
接口 | 说明 |
---|---|
SecretKey | 密钥,用于加解密 |
Cipher | 代表一种加密算法,使用密钥进行加密或解密 |
java代码如下:
/**
* 生成对称加密算法密钥
* @param algorithm 算法
* @param keySize 密钥长度
* @return
*/
public static SecretKey genSymmetricSecretKey(String algorithm, int keySize) {
try {
KeyGenerator generator = KeyGenerator.getInstance(algorithm);
generator.init(keySize);
return generator.generateKey();
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
throw new RuntimeException(e);
}
}
public static SecretKey genDESSecretKey() {
return genSymmetricSecretKey("DES", 56);
}
public static SecretKey genAESSecretKey() {
return genSymmetricSecretKey("AES", 128);
}
/**
* 使用密钥对明文加密
* @param secretKey 密钥
* @param content 明文
* @return
*/
public static byte[] encrypt(Key secretKey, byte[] content) {
try {
Cipher cipher = Cipher.getInstance(secretKey.getAlgorithm());
cipher.init(Cipher.ENCRYPT_MODE, secretKey);
return cipher.doFinal(content);
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException(e);
}
}
/**
* 解密
* @param secretKey
* @param secret
* @return
*/
public static byte[] decrypt(Key secretKey, byte[] secret) {
try {
Cipher cipher = Cipher.getInstance(secretKey.getAlgorithm());
cipher.init(Cipher.DECRYPT_MODE, secretKey);
return cipher.doFinal(secret);
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException(e);
}
}
非对称加密算法中,存在两个密钥。一个公钥、一个私钥。
私钥加密的消息,可被私钥解密;公钥加密的消息,可被私钥解密。
如下所示:
私钥不对外开发,而公钥则对外开放。
由于公钥对外开放,因此,公钥加密的消息实际上是不安全的,因为其他人也可以获得公钥。
而由于私钥不对外公开,经过私钥加密的消息只能由公钥解密,所以私钥加密的消息可认为是安全的。
因此,非对称算法可用于私钥持有方的身份验证,而不能确保通信双方的信息安全。
常用的非对称加密算法是RSA算法。
和对称加密不同的是,新增了KeyPair类,通过KeyPair可以获取PrivateKey以及PublicKey。
但和SecretKey类一致的是,他们都继承了java.security.Key接口,因此加解密的方式相同,只是非对称加密在加密和解密时需要传入不同的Key。
/**
* 生成非对称加密算法密钥对(包括公钥私钥)
* @param algorithm
* @param keySize
* @return
*/
public static KeyPair genKeyPair(String algorithm, int keySize) {
try {
KeyPairGenerator generator = KeyPairGenerator.getInstance(algorithm);
generator.initialize(keySize);
return generator.generateKeyPair();
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException(e);
}
}
public static KeyPair genRSAKeyPair() {
return genKeyPair("RSA", 512);
}
数字签名 = (非对称加密 + 消息摘要) + 消息正文
前面说过,在非对称算法中,私钥拥有方发送的消息是安全的。
那么,私有持有者每次加密整个正文,再发送消息,则可保证消息的安全性。
问题
每次加密整个消息正文,需要耗费很大的CPU资源。
解决方案
先使用效率更高的消息摘要算法,生成消息摘要,然后只对消息摘要使用私钥加密,将加密后的摘要和消息原文一起发送。
对方收到报文,使用公钥解密摘要,然后使用相同的消息摘要算法将正文生成摘要,和解密后的摘要进行对比,即可确定正文是否被更改了。
这样的消息被称为数字签名。
数字证书是一种特殊的数字签名,该数字签名的消息正文内容包括:证书持有者身份、证书持有者的公钥。
签名说过,公钥的获取是不安全的,有没有办法使得获取公钥的过程是安全的呢?
答案是肯定的,解决方案如下:
由受信赖的第三方机构(称为CA)颁发公钥,颁发的方式是,对公钥持有者的身份,以及公钥,使用CA的私钥进行数字签名。
如百度的数字证书为:
那么问题来了,CA的公钥如何获取呢?
实际上,知名的CA机构的证书已经默认安装在操作系统中,称为根证书,为CA机构给自己颁发的数字证书。
安装了根证书,代表信任该CA机构。
通过IE浏览器可以查看操作系统中已安装的证书:
SSL利用数字证书,保证公钥的合法性。使用非对称加密算法,协商对称加密算法密钥。此后使用对称加密算法对消息加密后进行通信。对称加密算法效率高于非对称加密算法,是这样实现的原因。
关于SSL,有一篇文章讲述的很好,就不再赘述了:
https://showme.codes/2017-02-20/understand-https/