数据加密及Java实现
数据加密讲解:对称加密、非对称加密、散列函数
目录
1、概念
加密的核心目的是将明文(可读的数据)通过一种算法和密钥转换为密文(不可读的数据),以确保数据的机密性。只有授权的持有正确密钥的一方才能将密文恢复为明文,这个过程称为解密。
-
明文:原始可读信息。
-
密文:加密后的乱码信息。
-
加密算法:用于执行加密和解密的数学函数。
-
密钥:一串参与加密和解密过程的秘密数据,是保密的关键。
2、加密的主要类型
2.1 对称加密
核心特点:加密和解密使用同一个密钥。
工作原理:
-
发送方和接收方事先共享同一个秘密密钥。
-
发送方用该密钥和加密算法将明文加密为密文。
-
接收方用同一个密钥和解密算法将密文解密为明文。
优点:
-
速度快:算法通常比较简单,计算量小,加解密效率高,适合处理大量数据。
缺点:
-
密钥分发困难:如何安全地将密钥传递给接收方是一个巨大的挑战。如果密钥在传递过程中被截获,整个通信就不再安全。
-
密钥管理复杂:如果与多人通信,需要为每一对通信方维护一个独立的密钥,密钥数量会呈指数级增长(n个用户需要 n(n-1)/2 个密钥)。
常见算法:
-
DES:数据加密标准,曾是早期标准。使用56位密钥,现已因密钥过短而被破解,不再安全。
-
3DES:三重DES,对每个数据块应用三次DES加密,比DES更安全,但速度较慢。
-
AES:高级加密标准,目前最流行、最安全的对称加密算法。密钥长度可以是128、192或256位。被美国政府选为标准,广泛应用于Wi-Fi加密(WPA2/WPA3)、文件加密(如WinRAR, 7-Zip)、SSL/TLS等领域。是当今对称加密的事实标准。
-
ChaCha20:一种流密码,由Google推广,在移动设备上性能通常优于AES,常用于替代RC4,是TLS的可选套件之一。
典型应用场景:
-
大量数据的加密(如整个硬盘、文件、数据库字段)。
-
安全通信通道建立后(例如通过TLS)的会话加密。
AES-GCM模式代码实现:
import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import javax.crypto.spec.GCMParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.security.SecureRandom;
import java.util.Base64;
/**
* 简化的AES-GCM加密工具类
*/
public class SimpleAESGCM {
// 常量定义
private static final String ALGORITHM = "AES";
private static final String TRANSFORMATION = "AES/GCM/NoPadding";
private static final int TAG_LENGTH = 128; // 认证标签长度
private static final int IV_LENGTH = 12; // IV长度
private static final int KEY_SIZE = 256; // 密钥长度
/**
* 生成AES密钥
*/
public static SecretKey generateKey() throws Exception {
KeyGenerator keyGen = KeyGenerator.getInstance(ALGORITHM);
keyGen.init(KEY_SIZE);
return keyGen.generateKey();
}
/**
* 从Base64字符串还原密钥
*/
public static SecretKey keyFromString(String keyStr) {
byte[] keyBytes = Base64.getDecoder().decode(keyStr);
return new SecretKeySpec(keyBytes, ALGORITHM);
}
/**
* 密钥转Base64字符串
*/
public static String keyToString(SecretKey key) {
return Base64.getEncoder().encodeToString(key.getEncoded());
}
/**
* 加密方法
* @param plaintext 明文
* @param key 密钥
* @return 包含密文和IV的字符串数组 [密文, IV]
*/
public static String[] encrypt(String plaintext, SecretKey key) throws Exception {
// 生成随机IV
byte[] iv = new byte[IV_LENGTH];
new SecureRandom().nextBytes(iv);
// 初始化加密器
Cipher cipher = Cipher.getInstance(TRANSFORMATION);
GCMParameterSpec spec = new GCMParameterSpec(TAG_LENGTH, iv);
cipher.init(Cipher.ENCRYPT_MODE, key, spec);
// 执行加密
byte[] encrypted = cipher.doFinal(plaintext.getBytes("UTF-8"));
// 返回Base64编码的密文和IV
return new String[] {
Base64.getEncoder().encodeToString(encrypted),
Base64.getEncoder().encodeToString(iv)
};
}
/**
* 解密方法
* @param encryptedText Base64编码的密文
* @param iv Base64编码的IV
* @param key 密钥
* @return 解密后的明文
*/
public static String decrypt(String encryptedText, String iv, SecretKey key) throws Exception {
// 初始化解密器
Cipher cipher = Cipher.getInstance(TRANSFORMATION);
GCMParameterSpec spec = new GCMParameterSpec(TAG_LENGTH, Base64.getDecoder().decode(iv));
cipher.init(Cipher.DECRYPT_MODE, key, spec);
// 执行解密
byte[] decrypted = cipher.doFinal(Base64.getDecoder().decode(encryptedText));
return new String(decrypted, "UTF-8");
}
}
使用示例:
/**
* 简单使用示例
*/
public class SimpleExample {
public static void main(String[] args) {
try {
// 1. 生成密钥
SecretKey key = SimpleAESGCM.generateKey();
String keyString = SimpleAESGCM.keyToString(key);
System.out.println("密钥: " + keyString);
// 2. 加密数据
String plaintext = "这是一段秘密信息";
String[] encryptedResult = SimpleAESGCM.encrypt(plaintext, key);
System.out.println("密文: " + encryptedResult[0]);
System.out.println("IV: " + encryptedResult[1]);
// 3. 解密数据
String decryptedText = SimpleAESGCM.decrypt(encryptedResult[0], encryptedResult[1], key);
System.out.println("解密: " + decryptedText);
// 4. 验证
System.out.println("验证: " + plaintext.equals(decryptedText));
} catch (Exception e) {
e.printStackTrace();
}
}
}
2.2 非对称加密
核心特点:使用一对密钥,即公钥和私钥。公钥可以公开,私钥必须严格保密。
工作原理:
-
公钥和私钥在数学上关联,但从公钥推导出私钥在计算上是不可行的。
-
加密过程:用接收方的公钥加密数据,加密后的数据只有用接收方的私钥才能解密。
-
签名过程:用自己的私钥对数据进行签名,其他人可以用你的公钥来验证签名,以确认数据的完整性和来源的真实性。
优点:
-
解决了密钥分发问题:公钥可以随意公开,无需秘密通道传递。
-
提供了身份验证和不可否认性:数字签名可以证明消息确实来自声称的发送者。
缺点:
-
速度非常慢:计算复杂,比对称加密慢几个数量级,不适合加密大量数据。
常见算法:
-
RSA:最著名、应用最广泛的非对称加密算法。其安全性基于大整数质因数分解的难度。常用于SSL/TLS证书、数字签名和密钥交换。
-
ECC:椭圆曲线密码学。与RSA相比,它能使用更短的密钥达到同等级别的安全性(例如,256位的ECC密钥安全性相当于3072位的RSA密钥),因此计算更快、资源消耗更少。在移动设备和资源受限环境中越来越受欢迎。
-
Diffie-Hellman:一种密钥交换协议,而不是用于加密数据的算法。它允许双方在不安全的信道上共同建立一个共享的秘密密钥,这个共享密钥随后可用于对称加密。是许多安全协议(如TLS, IPSec)的基础。
典型应用场景:
-
安全密钥交换:在通信开始时,通过非对称加密安全地传递对称加密的会话密钥(这就是TLS/SSL的工作方式)。
-
数字签名和证书:用于验证软件发布者、网站身份(HTTPS锁图标)和文档签名。
-
加密小量数据:如加密对称会话密钥。
RSA加密代码实现:
import javax.crypto.Cipher;
import java.security.*;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;
public class RSAEncryption {
/**
* 生成RSA密钥对
* @return 包含公钥和私钥的Map
*/
public static Map<String, String> generateKeyPair() {
Map<String, String> keyMap = new HashMap<>();
try {
KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance("RSA");
keyPairGen.initialize(2048); // 密钥长度2048位
KeyPair keyPair = keyPairGen.generateKeyPair();
// 获取公钥和私钥
PublicKey publicKey = keyPair.getPublic();
PrivateKey privateKey = keyPair.getPrivate();
// 将密钥转换为Base64编码的字符串
String publicKeyStr = Base64.getEncoder().encodeToString(publicKey.getEncoded());
String privateKeyStr = Base64.getEncoder().encodeToString(privateKey.getEncoded());
keyMap.put("publicKey", publicKeyStr);
keyMap.put("privateKey", privateKeyStr);
} catch (Exception e) {
throw new RuntimeException("生成密钥对失败", e);
}
return keyMap;
}
/**
* RSA公钥加密
* @param data 待加密数据
* @param publicKey 公钥字符串
* @return 加密后的Base64编码字符串
*/
public static String encrypt(String data, String publicKey) {
try {
// 将Base64编码的公钥字符串转换为PublicKey对象
byte[] keyBytes = Base64.getDecoder().decode(publicKey);
X509EncodedKeySpec keySpec = new X509EncodedKeySpec(keyBytes);
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
PublicKey pubKey = keyFactory.generatePublic(keySpec);
// 使用公钥加密
Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
cipher.init(Cipher.ENCRYPT_MODE, pubKey);
byte[] encryptedBytes = cipher.doFinal(data.getBytes());
return Base64.getEncoder().encodeToString(encryptedBytes);
} catch (Exception e) {
throw new RuntimeException("公钥加密失败", e);
}
}
/**
* RSA私钥解密
* @param encryptedData 加密后的Base64编码字符串
* @param privateKey 私钥字符串
* @return 解密后的原始数据
*/
public static String decrypt(String encryptedData, String privateKey) {
try {
// 将Base64编码的私钥字符串转换为PrivateKey对象
byte[] keyBytes = Base64.getDecoder().decode(privateKey);
PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(keyBytes);
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
PrivateKey priKey = keyFactory.generatePrivate(keySpec);
// 使用私钥解密
Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
cipher.init(Cipher.DECRYPT_MODE, priKey);
byte[] encryptedBytes = Base64.getDecoder().decode(encryptedData);
byte[] decryptedBytes = cipher.doFinal(encryptedBytes);
return new String(decryptedBytes);
} catch (Exception e) {
throw new RuntimeException("私钥解密失败", e);
}
}
public static void main(String[] args) {
try {
System.out.println("=== RSA加密解密示例 ===");
// 1. 生成密钥对
System.out.println("1. 生成RSA密钥对...");
Map<String, String> keyPair = generateKeyPair();
String publicKey = keyPair.get("publicKey");
String privateKey = keyPair.get("privateKey");
System.out.println("公钥: " + publicKey.substring(0, 50) + "...");
System.out.println("私钥: " + privateKey.substring(0, 50) + "...");
// 2. 原始数据
String originalData = "Hello, RSA加密解密测试! 当前时间: " + System.currentTimeMillis();
System.out.println("\n2. 原始数据: " + originalData);
// 3. 公钥加密
System.out.println("\n3. 使用公钥加密...");
String encryptedData = encrypt(originalData, publicKey);
System.out.println("加密后的数据: " + encryptedData.substring(0, 50) + "...");
// 4. 私钥解密
System.out.println("\n4. 使用私钥解密...");
String decryptedData = decrypt(encryptedData, privateKey);
System.out.println("解密后的数据: " + decryptedData);
// 5. 验证结果
System.out.println("\n5. 验证结果...");
if (originalData.equals(decryptedData)) {
System.out.println("✓ 加密解密成功!");
} else {
System.out.println("✗ 加密解密失败!");
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
2.3 散列函数(单向)
核心特点:单向、不可逆。它将任意长度的输入(明文)映射为固定长度的、看似随机的字符串(称为散列值或摘要)。它不是为了解密而设计的。
核心属性:
-
确定性:相同的输入永远产生相同的散列值。
-
快速计算:给定输入,能快速计算出散列值。
-
抗碰撞性:极难找到两个不同的输入具有相同的散列值。
-
单向性:从散列值反向推算出原始输入在计算上是不可行的。
普通散列函数-常见算法:
-
MD5:产生128位散列值。已被证实存在严重碰撞漏洞,完全不安全,仅用于校验数据完整性(非安全性目的)。
-
SHA-1:产生160位散列值。也已发现理论上的碰撞攻击,被大多数安全应用弃用。
-
SHA-2家族:包括SHA-224, SHA-256, SHA-384, SHA-512等。目前安全且被广泛使用,是SHA-1的替代品。
-
SHA-3:最新的SHA标准,采用与SHA-2不同的设计结构,作为SHA-2的后备选择。
普通散列函数的问题:
-
速度太快:它们被设计为快速计算,以验证文件完整性等。但这对密码来说是致命的。攻击者可以利用GPU、ASIC等硬件每秒进行数十亿甚至万亿次哈希计算,从而快速暴力破解密码。
-
无法抵御硬件提速:普通散列函数在专用硬件上的运行效率极高,使得暴力破解的成本非常低。
-
对彩虹表攻击脆弱:如果两个用户使用相同的密码,他们的散列值也会相同。攻击者可以预先计算海量密码的散列值形成“彩虹表”,然后直接反向查询获取明文密码。
密码哈希函数-常见算法:
-
bcrypt:专门为密码存储设计的算法,内置了盐和成本因子(可调节计算轮数,越慢越安全)。
-
PBKDF2:通过多次重复哈希(例如10万次)来增加计算时间。
-
scrypt:不仅需要大量计算,还需要大量内存,使得硬件加速攻击更加困难。
-
Argon2:目前被认为是冠军算法,能同时抵抗计算和内存优化攻击,是最新的推荐标准。
加盐流程:
在用户设置密码时,系统为这个密码单独生成一个随机的盐(一个随机生成的、足够长的字符串)。
将盐与用户的密码拼接在一起(例如
MyPassword123 + a9$fG&zL)。对拼接后的字符串进行哈希运算。
将最终的哈希值和盐一起存储到数据库中。
为何有效:盐使得同一个密码(如
123456)在不同用户那里也会产生完全不同的哈希值,因为他们的盐不同。这彻底废除了彩虹表攻击,因为攻击者不可能为每个盐都预计算一个巨大的彩虹表。
典型应用场景:
-
密码存储:网站不存储你的明文密码,而是存储其散列值。当你登录时,系统对你输入的密码进行散列,并与存储的散列值比对。为了抵御彩虹表攻击,通常会加“盐”。
-
数据完整性校验:下载文件时,提供者会公布文件的散列值。下载后,你可以计算文件的散列值并与公布的值比对,以确保文件在传输过程中未被篡改。
-
区块链和加密货币:工作量证明(PoW)和生成区块标识的核心技术。
-
数字签名:通常是对数据的散列值进行签名,而非对数据本身,以提高效率。
BCrypt加密代码实现:使用Spring Security的BCryptPasswordEncoder
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-crypto</artifactId>
<version>5.7.3</version>
</dependency>
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
public class BCryptUtil {
private static final BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();
/**
* 加密密码
* @param password 明文密码
* @return 加密后的密码
*/
public static String encrypt(String password) {
return encoder.encode(password);
}
/**
* 验证密码
* @param rawPassword 明文密码
* @param encodedPassword 加密后的密码
* @return 是否匹配
*/
public static boolean matches(String rawPassword, String encodedPassword) {
return encoder.matches(rawPassword, encodedPassword);
}
public static void main(String[] args) {
// 示例用法
String password = "myPassword123";
// 加密
String encryptedPassword = encrypt(password);
System.out.println("加密后的密码: " + encryptedPassword);
// 验证
boolean isMatch = matches(password, encryptedPassword);
System.out.println("密码验证结果: " + isMatch);
// 验证错误密码
boolean isWrongMatch = matches("wrongPassword", encryptedPassword);
System.out.println("错误密码验证结果: " + isWrongMatch);
}
}
3、现代加密的典型工作模式:混合加密系统
在实际应用中(如访问HTTPS网站),通常结合使用对称和非对称加密,以取长补短,这就是混合加密系统。
以访问 https://www.example.com 为例:
-
握手与身份验证:
-
你的浏览器连接到 example.com 的服务器。
-
服务器将其SSL证书(包含其公钥)发送给你的浏览器。
-
浏览器验证证书是否由可信的证书颁发机构签发,以确认你正在与真实的 example.com 通信。
-
-
密钥交换:
-
浏览器生成一个随机的对称会话密钥。
-
浏览器使用服务器的公钥(从证书中获得)加密这个对称会话密钥,然后发送给服务器。
-
服务器使用自己的私钥解密,得到这个对称会话密钥。
-
-
安全通信:
-
现在,浏览器和服务器都拥有了相同的对称会话密钥。
-
后续所有的数据传输都使用对称加密算法(如AES) 和这个会话密钥进行快速加密和解密。
-
核心:
-
利用非对称加密解决了最困难的密钥分发和身份验证问题。
-
利用对称加密来处理大量数据的加密,保证了高效率。
更多推荐
所有评论(0)