对称加密
对称加密是指加密和解密都是使用同一个密钥来进行的加密方式。这种加密方式的优点是加密和解密速度快,适合加密大量数据。常见的对称加密算法有 DES、AES 等。
很典型的一个场景就是当我们保存数据到数据库时,如果有一些敏感的信息(比如用户的密码),不想以明文形式存到数据库。我们可以采用对称加密,把加密后的数据存入数据库,然后读取数据时再用密钥解密,下面是一个AES算法的代码示例:
代码示例
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.util.Base64;
import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
public class SymmetricEncryptionExample {
private static final String ALGORITHM = "AES";
public static void main(String[] args) {
String plainText = "This is a secret message";
String key = "mySecretKey";
try {
SecretKey secretKey = generateKey(key);// 生成密钥
byte[] cipherText = encrypt(plainText.getBytes(), secretKey);// 加密
System.out.println("Encrypted Text: " + Base64.getEncoder().encodeToString(cipherText));// 输出加密后的密文
byte[] decryptedText = decrypt(cipherText, secretKey);// 解密
System.out.println("Decrypted Text: " + new String(decryptedText));// 输出解密后的明文
} catch (Exception e) {
e.printStackTrace();
}
}
// 生成密钥
public static SecretKey generateKey(String key) throws NoSuchAlgorithmException {
KeyGenerator keyGenerator = KeyGenerator.getInstance(ALGORITHM);
SecureRandom secureRandom = new SecureRandom(key.getBytes());
keyGenerator.init(128, secureRandom);
return new SecretKeySpec(keyGenerator.generateKey().getEncoded(), ALGORITHM);
}
// 加密
public static byte[] encrypt(byte[] plainText, SecretKey secretKey) throws Exception {
Cipher cipher = Cipher.getInstance(ALGORITHM);
cipher.init(Cipher.ENCRYPT_MODE, secretKey);
return cipher.doFinal(plainText);
}
// 解密
public static byte[] decrypt(byte[] cipherText, SecretKey secretKey) throws Exception {
Cipher cipher = Cipher.getInstance(ALGORITHM);
cipher.init(Cipher.DECRYPT_MODE, secretKey);
return cipher.doFinal(cipherText);
}
}
控制台打印结果如下:
Encrypted Text: cQx5Se744piQzzG0g4upF66EfVCeHt5JQcUWQ+3iirQ=
Decrypted Text: This is a secret message
非对称加密
非对称加密是指使用一对不同的密钥来进行加密和解密的加密方式。其中一个密钥是公开密钥(public key),任何人都可以获得;另一个密钥是私有密钥(private key),只有密钥的拥有者才能使用。这种加密方式的优点是加密和解密使用不同的密钥,安全性更高。常见的非对称加密算法有 RSA、ECC 等。非对称加密的一般流程是,用公钥(public key)进行加密,用私钥(private key)进行解密。
下面是一个简单的非对称加密的例子:
![](https://i-blog.csdnimg.cn/blog_migrate/f0a1e093ba636d95b8ea4e3e9198d705.png)
你可能会有疑问,既然已经有了对称加密了,为什么还要有非对称加密?我们假设上面使用对称加密,加密解密都是用同一个密钥,那这个密钥是Server生成还是User来生成呢?不管谁来生成,都要把密钥发送给对方,对方才能用它来解密,也就是说这个密钥需要经过网络传输,这是极不安全的!!另外即使它是安全的,当有多个User时,不同的User则需要生成不同的私钥,因为如果不同的User都使用同一个私钥,那UserA也可以破解UserB发送的消息了?显然这样是不行的!如果使用不同的私钥,Server端那还需要维护私钥和User的对应关系,即私钥A对应UserA,私钥B对应UserB,实在繁琐。而非对称加密使用公钥加密,私钥解密,既解决了对称加密私钥传输的安全问题,也解决了不同User需要不同密钥的问题。
但这样真的就安全了么?答案是否定的!黑客针对上面的情况,还是有办法的,那就是中间人攻击。
中间人攻击
中间人攻击(Man-in-the-MiddleAttack,简称“MITM攻击”)是一种“间接”的入侵攻击,这种攻击模式是通过各种技术手段将受入侵者控制的一台计算机虚拟放置在网络连接中的两台通信计算机之间,这台计算机就称为“中间人”。
![](https://i-blog.csdnimg.cn/blog_migrate/6409bcdc0f16fad189ebb37b89c1d7da.png)
攻击流程大致如下:
黑客拦截User获取公钥的请求,并代替User向Server发起获取公钥请求
Server返回公钥
黑客拦截到真的公钥,并伪造了一对假的公钥私钥,将伪造的公钥返回给User
User拿到假的公钥,加密数据,发送数据
黑客拦截数据,使用假的私钥来解密,并且篡改数据后,再用真的公钥加密
黑客发送篡改后的数据给Server
那如何解决这个问题呢?下面我们可以引入证书。
数字证书
上面中间人攻击的关键就是黑客拦截并返回了一个伪造的公钥给User,所以解决这个问题的办法就是如何拿到一个正确的公钥,如何证明这个公钥是Server端自己的公钥,而不是伪造的公钥。
此时一种叫证书中心(Certificate Authority,简称CA)的机构出现了,可以理解为一个第三方的公钥公证机构,专门负责对公钥进行审核认证,审核通过后你会得到一个证书。
所以Server需要先去申请一个证书,证书里面包含了Server的信息以及公钥,然后将可信任的证书返回给User。
![](https://i-blog.csdnimg.cn/blog_migrate/842bf2c2ea520330bc0e132a15cb3af6.png)
此时你可能会有疑问,要是黑客把证书也拦截掉,返回一个伪造的证书怎么办?
客户端(浏览器)的"证书管理器",有"受信任的根证书颁发机构"列表。客户端会根据这张列表,查看解开数字证书的公钥是否在列表之内,如果不在这个列表内。会出现如下警告:
![](https://i-blog.csdnimg.cn/blog_migrate/638db8e828f9b821ddd24153c410f8e0.png)
还有一种情况就是假如这个证书不是由权威的CA机构发放,也会弹出相应的警告:
![](https://i-blog.csdnimg.cn/blog_migrate/36c7c627346ecc8d34606ca6de0e1237.png)
代码示例
最后附上一个Java非对称加密的代码示例:
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.util.Base64;
import javax.crypto.Cipher;
public class RSADemo {
public static void main(String[] args) throws Exception {
// 生成密钥对
KeyPair keyPair = KeyPairGenerator.getInstance("RSA").generateKeyPair();
PrivateKey privateKey = keyPair.getPrivate();
PublicKey publicKey = keyPair.getPublic();
// 明文
String plainText = "Hello, world!";
// 加密
Cipher cipher = Cipher.getInstance("RSA");
cipher.init(Cipher.ENCRYPT_MODE, publicKey);
byte[] encryptedText = cipher.doFinal(plainText.getBytes());
// 解密
cipher.init(Cipher.DECRYPT_MODE, privateKey);
byte[] decryptedText = cipher.doFinal(encryptedText);
System.out.println("明文:" + plainText);
//对称加密算法加密后的数据通常是二进制数据,而非文本数据。如果直接将二进制数据转化为字符串输出,那么在绝大多数情况下会打印出一堆乱码。
//我们可以使用Base64编解码器将二进制数据转化为字符串
System.out.println("加密后:" + Base64.getEncoder().encodeToString(encryptedText));
System.out.println("解密后:" + new String(decryptedText));
}
}
控制台打印结果如下:
明文:Hello, world!
加密后:OVxXHqu60lELWwgMmEKOCam3ypoH1jwPIl2IXH0ZSqcYbfSQTF/AU+wLqNliRuY7DrCfF3ca8ii99m6KszeHe+n6SvHHURF+u7F7V5QexsQku35GvQfvDesd2NPnZtXhG9D8Dsq3XDWONobCbhFU4HGdAgv0Q6KbZAQp/+tYDn+xJjypclora8CbmD31B9f9MKqJPNR9MEMc6QBLqpYyQhs9UMNRbBWXZohlgY7f6RZh5O0dGtAzwQWExe1N6VSm2R2UJdHM9up2Q/ppKOsLLrZldwwOtNRbuPH+jepwHiomOEjST3XTNfCvemImEMi736zPajXTteC/2KzQLg68qw==
解密后:Hello, world!
总结
对称加密:加密解密都使用同一个密钥,常用算法AES
优点:算法简单,速度快,效率高,适合大量数据的加密。
缺点:加密解密都用的同一个密钥,不适合加解密分散在不同地方场景,因为密钥传递不安全
非对称加密:加密解密用的不同的密钥,常用算法RSA
优点:不需要传递私钥,安全性更高
缺点:流程和算法更为复杂,速度和效率比对称加密低