加密这个事情其实之前和小伙伴们聊过很多次,不过最近松哥又想到一些细节问题,再和小伙伴们补充聊一聊。
对称加密和非对称加密是两种不同的加密方法,它们在数据安全和信息传输中扮演着重要的角色。下面我将分别介绍这两种加密技术:
对称加密是指加密和解密都使用相同的密钥。这意味着发送方和接收方都必须拥有这个密钥才能进行加密和解密操作。
常见的对称加密算法:
一般来说,对称加密具有如下特点:
从这里可以看到,对称加密主要有两大优势:第一就是运算速度快;第二就是适用于大量数据。
但是,对称加密有一个致命的问题,就是密钥管理。如何从服务端将密钥安全的传输到客户端是个问题!另外就是当一对多通信的时候,如何管理好密钥不被泄露也是一个考验。这是对称加密的不足之处。
接下来松哥给大家演示下 Java 代码如何做对称加解密。
在 Java 中实现对称加密,通常使用 Java 加密架构(Java Cryptography Architecture, JCA)提供的类和接口。
下面是一个使用 AES(高级加密标准)算法进行对称加密和解密的简单示例:
public class SymmetricEncryptionExample {
// 生成密钥
public static SecretKey generateKey() throws Exception {
KeyGenerator keyGenerator = KeyGenerator.getInstance("AES");
keyGenerator.init(128); // 可以是128, 192或256位
return keyGenerator.generateKey();
}
// 加密方法
public static String encrypt(String data, SecretKey key) throws Exception {
Cipher cipher = Cipher.getInstance("AES");
cipher.init(Cipher.ENCRYPT_MODE, key);
byte[] encryptedBytes = cipher.doFinal(data.getBytes());
return Base64.getEncoder().encodeToString(encryptedBytes);
}
// 解密方法
public static String decrypt(String encryptedData, SecretKey key) throws Exception {
Cipher cipher = Cipher.getInstance("AES");
cipher.init(Cipher.DECRYPT_MODE, key);
byte[] decryptedBytes = cipher.doFinal(Base64.getDecoder().decode(encryptedData));
return new String(decryptedBytes);
}
public static void main(String[] args) {
try {
// 生成密钥
SecretKey key = generateKey();
// 原始数据
String originalData = "Hello, JavaBoy!";
// 加密
String encryptedData = encrypt(originalData, key);
System.out.println("加密数据: " + encryptedData);
// 解密
String decryptedData = decrypt(encryptedData, key);
System.out.println("解密数据: " + decryptedData);
} catch (Exception e) {
e.printStackTrace();
}
}
}
KeyGenerator
生成一个AES密钥。Cipher
类进行加密,将数据转换成字节后加密,并使用 Base64 编码转换为字符串,以便于存储或传输。以上代码大家需要注意的是:
出于安全考虑,我们一般使用上面的方案生成密钥。这种方案生成的密钥有一个特点就是系统每次重启就会变。如果你希望能够自己控制密钥的生成,那么可以通过如下方式生成密钥:
public static SecretKey generateKeyFromPassword(String password, int keySize) throws NoSuchAlgorithmException, InvalidKeySpecException {
SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256");
KeySpec spec = new PBEKeySpec(password.toCharArray(), "salt".getBytes(), 65536, keySize);
return new SecretKeySpec(factory.generateSecret(spec).getEncoded(), "AES");
}
这样就可以通过自己传入的参数去控制密钥。
在跨语言(如 JavaScript 和 Java)使用 AES 算法进行加密和解密时,关键是确保两端使用相同的密钥、算法模式(如 CBC, ECB 等)、填充模式(如 PKCS5Padding, PKCS7Padding 等)和初始化向量(IV,如果使用了需要 IV的 模式如 CBC)。
之前有小伙伴说自己前端加密之后后端总是无法解密,松哥这里也给一个前后端搭配的例子。
前端代码:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>AES加密示例</title>
<script src="https://cdnjs.cloudflare.com/ajax/libs/crypto-js/4.0.0/crypto-js.min.js"></script>
</head>
<body>
<script>
function encryptAES(text, secretKey) {
const key = CryptoJS.enc.Utf8.parse(secretKey);
const iv = CryptoJS.lib.WordArray.random(128 / 8); // 对于CBC模式,需要IV
const encrypted = CryptoJS.AES.encrypt(text, key, {
iv: iv,
mode: CryptoJS.mode.CBC,
padding: CryptoJS.pad.Pkcs7
});
// 返回加密后的文本和IV(Base64格式),实际使用中可能需要安全地传输这些值
return {
ciphertext: encrypted.toString(),
iv: iv.toString(CryptoJS.enc.Base64)
};
}
const secretKey = 'helloworldhelloworldhelloworld11'; // 确保密钥是32个字符长(256位)
const text = 'Hello, javaboy!';
const result = encryptAES(text, secretKey);
console.log(result);
</script>
</body>
</html>
后端代码:
import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.util.Base64;
public class AESDecrypt {
public static String decryptAES(String encryptedData, String secretKey, String iv) throws Exception {
IvParameterSpec ivParameterSpec = new IvParameterSpec(Base64.getDecoder().decode(iv));
SecretKeySpec secretKeySpec = new SecretKeySpec(secretKey.getBytes("UTF-8"), "AES");
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.DECRYPT_MODE, secretKeySpec, ivParameterSpec);
byte[] decodedValue = Base64.getDecoder().decode(encryptedData);
byte[] decryptedValue = cipher.doFinal(decodedValue);
return new String(decryptedValue, "UTF-8");
}
public static void main(String[] args) {
try {
//前端加密后的文本
String encryptedText = "PYANpAjMsRnBIEhovtEXQw==";
String secretKey = "helloworldhelloworldhelloworld11";
//前端 IV,要和加密后的文本一起传到后端
String iv = "y/jUHcgSOpOiyNlsfjNUBg==";
String decryptedText = decryptAES(encryptedText, secretKey, iv);
System.out.println("解密文本: " + decryptedText);
} catch (Exception e) {
e.printStackTrace();
}
}
}
注意这里前端加密时会产生一个 iv 参数,要随着前端加密结果一起传递给后端。
在 Java 进行 AES 加密并在 JavaScript 中解密时,同样需要确保两端使用相同的密钥、算法模式(如 CBC、ECB 等)、填充模式(如 PKCS5Padding、PKCS7Padding 等)以及(如果适用)相同的初始化向量(IV)。
后端代码:
public class AESEncrypt {
public static String encryptAES(String plainText, String secretKey, String iv) throws Exception {
KeyGenerator keyGenerator = KeyGenerator.getInstance("AES");
keyGenerator.init(256); // 对于AES-256
SecretKey secret = new SecretKeySpec(secretKey.getBytes("UTF-8"), "AES");
IvParameterSpec ivParameterSpec = new IvParameterSpec(Base64.getDecoder().decode(iv));
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, secret, ivParameterSpec);
byte[] encrypted = cipher.doFinal(plainText.getBytes("UTF-8"));
return Base64.getEncoder().encodeToString(encrypted);
}
public static void main(String[] args) {
try {
String plainText = "Hello, 江南一点雨!";
String secretKey = "helloworldhelloworldhelloworld11"; // 确保密钥是32个字符长(256位)
String iv = Base64.getEncoder().encodeToString(new byte[16]); // 示例IV,实际应用中应更安全地生成
String encryptedText = encryptAES(plainText, secretKey, iv);
System.out.println("Encrypted text: " + encryptedText);
System.out.println("IV (Base64): " + iv); // 确保将IV发送给解密方
} catch (Exception e) {
e.printStackTrace();
}
}
}
前端代码:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<script src="https://cdnjs.cloudflare.com/ajax/libs/crypto-js/4.0.0/crypto-js.min.js"></script>
<script>
function decryptAES(ciphertext, secretKey, iv) {
const key = CryptoJS.enc.Utf8.parse(secretKey);
const ivParsed = CryptoJS.enc.Base64.parse(iv);
const decrypted = CryptoJS.AES.decrypt(
{
ciphertext: CryptoJS.enc.Base64.parse(ciphertext)
},
key,
{
iv: ivParsed,
mode: CryptoJS.mode.CBC,
padding: CryptoJS.pad.Pkcs7
}
);
return decrypted.toString(CryptoJS.enc.Utf8);
}
const secretKey = 'helloworldhelloworldhelloworld11';
const iv = 'AAAAAAAAAAAAAAAAAAAAAA=='; // 从Java代码获取
const ciphertext = '1lPNVF1injas78KUWeKp5FusEr6f0pGgcrLAg9ELFr8='; // 从Java代码获取
const decryptedText = decryptAES(ciphertext, secretKey, iv);
console.log(decryptedText);
</script>
</head>
<body>
</body>
</html>
以上前后端交互加解密代码松哥亲测都是没问题的,大家有这方面的需求记得及时收藏本文,可以作为参考。
非对称加密,也称为公钥加密,是一种使用两个不同密钥(公钥和私钥)的加密方式。
一般来说,非对称加密有如下几种不同的特点:
非对称加密有两个经典使用场景。
针对第二点用途,有的小伙伴会将之表述为用私钥进行加密,公钥进行解密,反正大伙知道说的是同一回事。
非对称加密算法,尽管在理论上能够用于数据加密和数字签名,但在实践中,其高计算复杂度和低效率成为了主要障碍。
相比对称加密算法,非对称加密的运算速度要慢上几个数量级,这极大地影响了其处理大数据量的能力。此外,由于非对称加密算法的加密和解密过程与密钥长度紧密相关,且不支持分组加密模式,导致它只能处理不超过密钥长度的少量数据,无法进行大量数据的加密。
为了克服非对称加密在性能上的不足,现代加密系统通常采用混合加密策略,即结合对称加密和非对称加密的优点。在这种策略中,非对称加密主要用于安全地传输一个对称加密的密钥(即“密钥协商”)给另一方。一旦双方安全地共享了这个对称密钥,就可以使用高效的对称加密算法来加密和解密大量数据。这种结合使用的方法不仅提高了加密效率,还增强了通信的安全性,被广泛应用于各种安全通信协议中,如SSL/TLS。
在数字签名领域,为了提升非对称加密的效率也做了一些适配。具体做法是,首先对原始数据进行摘要处理(可以利用 MD5、SHA 等),得到一个固定长度的摘要值。然后,使用非对称加密算法对这个摘要值进行加密,生成数字签名。由于摘要算法能够高效地将任意长度的数据压缩为固定长度的摘要,因此不管原始数据多大,摘要数据长度都一样,签名过程也能保持高效。当验证签名时,只需重新计算原始数据的摘要,并与解密后的签名进行比较,即可快速判断数据是否被篡改。
Java 代码使用 RSA 加解密案例:
public class RsaDemo {
public static void main(String[] args) {
try {
KeyPair keyPair = generateRSAKeyPair();
PublicKey publicKey = keyPair.getPublic();
PrivateKey privateKey = keyPair.getPrivate();
String originalData = "Hello, 江南一点雨!";
String encryptedData = encrypt(publicKey, originalData);
String decryptedData = decrypt(privateKey, encryptedData);
System.out.println("加密后的数据: " + encryptedData);
System.out.println("解密后的数据: " + decryptedData);
} catch (Exception e) {
e.printStackTrace();
}
}
public static String decrypt(PrivateKey privateKey, String encryptedData) throws Exception {
Cipher cipher = Cipher.getInstance("RSA");
cipher.init(Cipher.DECRYPT_MODE, privateKey);
byte[] decryptedBytes = cipher.doFinal(Base64.getDecoder().decode(encryptedData));
return new String(decryptedBytes);
}
public static String encrypt(PublicKey publicKey, String data) throws Exception {
Cipher cipher = Cipher.getInstance("RSA");
cipher.init(Cipher.ENCRYPT_MODE, publicKey);
byte[] bytes = cipher.doFinal(data.getBytes());
return Base64.getEncoder().encodeToString(bytes);
}
public static KeyPair generateRSAKeyPair() throws NoSuchAlgorithmException {
KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSA");
keyGen.initialize(2048); // 可以指定密钥长度,如2048位
return keyGen.generateKeyPair();
}
}
后端代码和上面案例中一致,不同的是,我们在拿到公钥之后,可以将公钥打印出来,这个公钥将来要传递给前端:
public static void main(String[] args) {
try {
KeyPair keyPair = generateRSAKeyPair();
PublicKey publicKey = keyPair.getPublic();
PrivateKey privateKey = keyPair.getPrivate();
System.out.println("公钥: " + Base64.getEncoder().encodeToString(publicKey.getEncoded()));
String originalData = "Hello, 江南一点雨!";
String encryptedData = encrypt(publicKey, originalData);
String decryptedData = decrypt(privateKey, encryptedData);
System.out.println("加密后的数据: " + encryptedData);
System.out.println("解密后的数据: " + decryptedData);
} catch (Exception e) {
e.printStackTrace();
}
}
前端代码如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>RSA Encryption Example</title>
<!-- 引入jsencrypt库 -->
<script src="https://cdn.jsdelivr.net/npm/jsencrypt@3.0.0-beta.1/bin/jsencrypt.min.js"></script>
</head>
<body>
<script>
// 重写前端加密方法
function encryptData(publicKey, data) {
// 创建一个新的JSEncrypt对象
var encryptor = new JSEncrypt();
// 设置公钥
encryptor.setPublicKey(publicKey);
// 加密数据
var encrypted = encryptor.encrypt(data);
return encrypted;
}
// 示例公钥(实际使用时应该替换为服务器提供的公钥)
var publicKey = `MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAnP5FOXIghidLDE2FgFwzi4Y+qTdYjnc0eMoiL5i4gdOeTE+7Uu9YvDB51GcBWKD9lvmZr5rX8z0OBpwe526qsTekNpXoIVk9DB34US0HOrAXEEwpUTVuSR656cJGAwmWBkVaalQynEz4Dlzrm53zExVYueruYUuzFyuZDaQcFnl3rWUH/XDkCIe23z0R1TfT3Q2OYNKft0u56r0S/ko99utXuYJK9yowe7QGT6q4cSJwsITQTomCARAq9q+bSNuGEYa4FlYCKIKWIhKbMhz0FYIMB2fJN10GyZbbvKASqeMkuCoD2Efgd8/6uMwOaMcx9LgEkcFaQ3qgDutsPXNUswIDAQAB`;
// 要加密的数据
var data = 'Hello, javaboy!';
// 调用加密方法
var encryptedData = encryptData(publicKey, data);
// 输出加密后的数据
console.log('Encrypted Data:', encryptedData);
</script>
</body>
</html>
在 Java 中使用 RSA 算法加密数据,并在 JavaScript 中解密这些数据,意味着服务端用前端的公钥加密,前端用自己的私钥解密,这种场景前端私钥很容易被盗取,因此不推荐这种用法。我也就不举例了。
好啦,又和小伙伴们聊了一遍对称加密和非对称加密,上面的案例代码松哥都是测试通过的,小伙伴们可以作为参考。