文章目录
一、对称加密算法
1、概述
对称加算法就是传统的==用一个密钥进行加密和解密==,例如,常用的 WinZip 和WinRAR 对压缩包的加密和解密,就是使用对称加密算法
2、常用的对称加密算法
算法 | 密钥长度 | 工作模式 | t填充模式 |
---|---|---|---|
DES | 56/54 | ECB/CBC/PCBC/CTR | NoPadding/PKCS5Padding |
AES | 128/192/256 | ECB/CBC/PCBC/CTR | NoPadding/PKCS5Padding/PKCS7Padding |
IDEA | 128 | ECB | PKCS5Padding/PKCS7Padding |
说明:
- 密钥长度直接决定加密强度
- 工作模式和填充模式可以看成是对称加密算法的参数和格式选择
- DES算法由于密钥过短,可以在短时间内被暴力破解,所以现在已经不安全了。
3、使用 AES 加密
AES算法是目前应用最广泛的加密算法。比较常见的工作模式是ECB和CBC。
1、ECB 模式
ECB模式是最简单的AES加密模式,它需要一个固定长度的密钥,固定的明文会生成固定的密文
解密与加密均使用同一个密钥,所以安全性较低
2、AES 加密 + ECB 模式
// AES + ECB
public class Demo01 {
public static void main(String[] args) throws GeneralSecurityException {
// 原文:
String message = "天生我材必有用飞流直下三千尺";
System.out.println("Message(原始信息): " + message);
// 128位密钥 = 16 bytes Key:
byte[] key = "1234567890abcdef".getBytes();
// 加密:
byte[] data = message.getBytes();
byte[] encrypted = encrypt(key, data);
System.out.println("Encrypted(加密内容): " + Base64.getEncoder().encodeToString(encrypted));
// 解密:
byte[] decrypted = decrypt(key, encrypted);
System.out.println("Decrypted(解密内容): " + new String(decrypted));
}
// 加密:
public static byte[] encrypt(byte[] key, byte[] input) throws GeneralSecurityException {
// 创建密码对象,需要传入算法/工作模式/填充模式
Cipher cipher=Cipher.getInstance("AES/ECB/PKCS5Padding");
// 根据key的字节内容,"恢复"秘钥对象
SecretKey keySpec=new SecretKeySpec(key, "AES");
// 初始化秘钥:设置加密模式ENCRYPT_
cipher.init(Cipher.ENCRYPT_MODE, keySpec);
// 根据原始内容(字节),进行加密
return cipher.doFinal(input);
}
// 解密:
public static byte[] decrypt(byte[] key, byte[] input) throws GeneralSecurityException {
// 创建密码对象,需要传入算法/工作模式/填充模式
Cipher cipher=Cipher.getInstance("AES/ECB/PKCS5Padding");
// 根据key的字节内容,"恢复"秘钥对象
SecretKey keySpec=new SecretKeySpec(key, "AES");
// 初始化秘钥:设置解密模式DECRYPT_MODE
cipher.init(Cipher.DECRYPT_MODE, keySpec);
// 根据原始内容(字节),进行解密
return cipher.doFinal(input);
}
}
步骤:
- 根据算法名称/工作模式/填充模式获取Cipher实例;
- 根据算法名称初始化一个SecretKey实例,密钥必须是指定长度
- 使用SerectKey初始化Cipher实例,并设置加密或解密模式
- 传入明文或密文,获得密文或明文
3、CBC 模式
CBC模式,需要一个随机数作为 IV 参数,这样对于同一份明文,每次生成的密文都不同
4、AES 加密 + CBC 模式
//AES + CBC
public class Demo02 {
public static void main(String[] args) throws Exception {
// 原文:
String message = "Hello";
System.out.println("Message(原始信息): " + message);
// 256位密钥 = 32 bytes Key:
byte[] key = "1234567890abcdef1234567890abcdef".getBytes();
// 加密:
byte[] data = message.getBytes();
byte[] encrypted = encrypt(key, data);
System.out.println("Encrypted(加密内容): " + Base64.getEncoder().encodeToString(encrypted));
// 解密:
byte[] decrypted = decrypt(key, encrypted);
System.out.println("Decrypted(解密内容): " + new String(decrypted));
}
// 加密:
public static byte[] encrypt(byte[] key, byte[] input) throws GeneralSecurityException {
// 设置算法/工作模式CBC/填充
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
// 恢复秘钥对象
SecretKey keySpec = new SecretKeySpec(key, "AES");
// CBC模式需要生成一个16 bytes的initialization vector:
SecureRandom sr = SecureRandom.getInstanceStrong();// 生成器
byte[] iv = sr.generateSeed(16);
System.out.println("iv字节数组内容:" + Arrays.toString(iv));
System.out.println("iv字节数组长度:" + iv.length);
// 随机数封装成 IvParameterSpec 参数对象
IvParameterSpec ivps = new IvParameterSpec(iv);
// 初始化秘钥:操作模式、秘钥、IV参数
cipher.init(Cipher.ENCRYPT_MODE, keySpec, ivps);
// 加密
byte[] data = cipher.doFinal(input);
// IV不需要保密,把IV和密文一起返回:
return join(iv,data);
}
// 解密:
public static byte[] decrypt(byte[] key, byte[] input) throws GeneralSecurityException {
// 把input分割成IV和密文:
byte[] iv=new byte[16];
byte[] data=new byte[input.length-16];
System.arraycopy(input, 0, iv, 0, 16);
System.arraycopy(input, 16, data, 0, data.length);
// 解密:
Cipher cipher=Cipher.getInstance("AES/CBC/PKCS5Padding");
SecretKeySpec keySpec=new SecretKeySpec(key, "AES");
IvParameterSpec ivps = new IvParameterSpec(iv);
// 初始化秘钥:操作模式、秘钥、IV参数
cipher.init(Cipher.DECRYPT_MODE, keySpec,ivps);
// 解密操作
return cipher.doFinal(data);
}
// 合并数组
public static byte[] join(byte[] bs1, byte[] bs2) {
byte[] r=new byte[bs1.length+bs2.length];
System.arraycopy(bs1, 0, r, 0, bs1.length);
System.arraycopy(bs2, 0, r, bs1.length, bs2.length);
return r;
}
}
步骤与 ECB 模式步骤相似,只需要额外添加一个随机生成的 IV 参数,然后将这个参数加密后与原文合并,解密时也需要将合并的数据拆分,分别获取 IV 参数和加密后的原文
5、小结
- 对称加密算法使用同一个密钥进行加密和解密,常用算法有DES、AES和IDEA等;
- 密钥长度由算法设计决定,AES的密钥长度是128/192/256位;
- 使用对称加密算法需要指定算法名称、工作模式和填充模式。
二、密钥交换算法
1、概述
AES 加密算法,需要将密钥进行传输,在这个过程中存在安全隐患,导致密钥泄露因此,需要使用密钥交换算法:DH
DH算法解决了密钥在双方不直接传递密钥的情况下完成密钥交换,这个神奇的交换原理完全由数学理论支持。
2、步骤
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-GOzHanTA-1689078393304)(E:\桌面\870edf07cee6af069cffdd61c74ac66.png)]
- Alice 和 Bob 均有自己的私钥
- 将各自的私钥进行计算,会产生一个公钥
- 将各自产生的公钥传给对方
- 然后结合自己的私钥和对方的公钥进行计算,双方可以得到一个相同的共享密钥
- 通过对比最后产生的共享密钥
由于在这个过程中,只有公钥进行传输,私钥并没有参与传输,因此保证了数据在传输过程的安全性
3、代码示例
public class Demo03 {
public static void main(String[] args) {
// Bob和Alice:
Person bob = new Person("Bob");
Person alice = new Person("Alice");
// 各自生成KeyPair: 公钥+私钥
bob.generateKeyPair();
alice.generateKeyPair();
// 双方交换各自的PublicKey(公钥):
// Bob根据Alice的PublicKey生成自己的本地密钥(共享公钥):
bob.generateSecretKey(alice.publicKey.getEncoded());
// Alice根据Bob的PublicKey生成自己的本地密钥(共享公钥):
alice.generateSecretKey(bob.publicKey.getEncoded());
// 检查双方的本地密钥是否相同:
bob.printKeys();
alice.printKeys();
//使用共享密钥,继续dosth
}
}
//用户类
class Person {
public final String name; // 姓名
// 密钥
public PublicKey publicKey; // 公钥
private PrivateKey privateKey; // 私钥
private byte[] secretKey; // 本地秘钥(共享密钥)
// 构造方法
public Person(String name) {
this.name = name;
}
// 生成本地KeyPair:(公钥+私钥)
public void generateKeyPair() {
KeyPairGenerator kpGen;
try {
// 创建DH算法的“密钥对”生成器
kpGen = KeyPairGenerator.getInstance("DH");
kpGen.initialize(512);
KeyPair kp=kpGen.generateKeyPair();
this.privateKey=kp.getPrivate();
this.publicKey=kp.getPublic();
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}
}
// 按照 "对方的公钥" => 生成"共享密钥"
public void generateSecretKey(byte[] receivedPubKeyBytes) {
try {
// 从byte[] 恢复PublicKey
X509EncodedKeySpec keySpec = new X509EncodedKeySpec(receivedPubKeyBytes);
// 根据DH算法获取KeyFactory
KeyFactory kf = KeyFactory.getInstance("DH");
// 通过KeyFactory创建公钥
PublicKey recePublicKey = kf.generatePublic(keySpec);
// 创建密钥协议对象(用于密钥协商)
KeyAgreement keyAgreement = KeyAgreement.getInstance("DH");
keyAgreement.init(this.privateKey); // 初始化“自己的PrivateKey”
keyAgreement.doPhase(recePublicKey, true); // 根据"对方的PublicKey"
// 生成SecretKey本地密钥(共享公钥)
this.secretKey = keyAgreement.generateSecret();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public void printKeys() {
System.out.printf("Name: %s\n", this.name);
System.out.printf("Private key: %x\n", new BigInteger(1, this.privateKey.getEncoded()));
System.out.printf("Public key: %x\n", new BigInteger(1, this.publicKey.getEncoded()));
System.out.printf("Secret key: %x\n", new BigInteger(1, this.secretKey));
}
}
三、非对称加密算法(RSA)
1、概述
非对称加密:加密和解密使用的不是相同的密钥,用户A密钥加密后所得的信息,只能用用户A的解密密钥才能解密。如果知道了其中一个,并不能计算出另外一个。因此如果公开了一对密钥中的一个,并不会危害到另外一个的秘密性质。称公开的密钥为公钥;不公开的密钥为私钥。只有同一个公钥-私钥对才能正常加解密。
非对称加密的缺点:运算速度非常慢,比对称加密要慢很多。
2、案例
小明要加密一个消息发送给小红,他应该首先向小红索取她的公钥,然后,他用小红的公钥加密,把加密结果发送给小红,此文件只能由小红的私钥解开,因为小红的私钥在她自己手里,所以,除了小红,没有任何人能解开此加密结果。
// RSA
public class Demo05 {
public static void main(String[] args) throws Exception {
// 明文:
byte[] plain = "Hello, encrypt use RSA".getBytes("UTF-8");
// 创建公钥/私钥对
Human hong = new Human("小红");
Human ming = new Human("小明");
// 小明使用小红的公钥进行加密
// 1.获取小红的公钥
PublicKey hongPublicKey = hong.getPublicKey();
System.out.println(String.format("小红的public key(公钥): %x", new BigInteger(1, hongPublicKey.getEncoded())));
// 2.使用公钥加密
byte[] encrypted = ming.encrypt(plain, hongPublicKey);
System.out.println(String.format("encrypted(加密): %x", new BigInteger(1, encrypted)));
// 小红使用自己的私钥解密:
// 1.获取小红的私钥,并输出
PrivateKey hongPrivateKey = hong.getPrivateKey();
System.out.println(String.format("小红的private key(私钥): %x", new BigInteger(1, hongPrivateKey.getEncoded())));
// 2.使用私钥解密
byte[] decrypted = hong.decrypt(encrypted);
System.out.println("decrypted(解密): " + new String(decrypted, "UTF-8"));
}
}
// 用户类
class Human {
// 姓名
String name;
// 私钥:
PrivateKey privatekey;
// 公钥:
PublicKey publickey;
// 构造方法
public Human(String name) throws GeneralSecurityException {
// 初始化姓名
this.name = name;
// 生成公钥/私钥对:
KeyPairGenerator kpGen=KeyPairGenerator.getInstance("RSA");
kpGen.initialize(1024);
KeyPair kp=kpGen.generateKeyPair();
this.privatekey=kp.getPrivate();
this.publickey=kp.getPublic();
}
// 把私钥导出为字节
public PrivateKey getPrivateKey() {
return this.privatekey;
}
// 把公钥导出为字节
public PublicKey getPublicKey() {
return this.publickey;
}
// 用公钥加密
public byte[] encrypt(byte[] message,PublicKey publickey) throws GeneralSecurityException {
// 使用公钥进行初始化
Cipher cipher=Cipher.getInstance("RSA");
cipher.init(Cipher.ENCRYPT_MODE, publickey); // 使用公钥进行初始化
return cipher.doFinal(message);
}
// 用私钥解密:
public byte[] decrypt(byte[] input) throws GeneralSecurityException {
// 使用私钥进行初始化
Cipher cipher=Cipher.getInstance("RSA");
cipher.init(Cipher.DECRYPT_MODE, privatekey);
return cipher.doFinal(input);
}
}
RSA的公钥和私钥都可以通过getEncoded()方法获得以byte[]表示的二进制数据,并根据需要保存到文件中。要从byte[]数组恢复公钥或私钥,可以这么写:
byte[] pkData = ...
byte[] skData = ...
KeyFactory kf = KeyFactory.getInstance("RSA");
// 恢复公钥:
X509EncodedKeySpec pkSpec = new X509EncodedKeySpec(pkData);
PublicKey pk = kf.generatePublic(pkSpec);
// 恢复私钥:
PKCS8EncodedKeySpec skSpec = new PKCS8EncodedKeySpec(skData);
PrivateKey sk = kf.generatePrivate(skSpec);
3、小结
- 非对称加密就是加密和解密使用的不是相同的密钥,只有同一个公钥-私钥对才能正常加解密;