加密算法学习与SpringBoot实践

目录

  1. 基础概念
  2. 对称加密算法
  3. 非对称加密算法
  4. 哈希算法
  5. 密码哈希专用算法
  6. SpringBoot中的加密最佳实践
  7. 不同加密算法对比
  8. 加密结果示例

由于接触的新项目是用户认证相关,把密码相关的知识重新复习并记录下来

基础概念

什么是加密?

加密是将明文信息转换为密文的过程,目的是保护数据的机密性、完整性和真实性。

加密的主要类型

  1. 对称加密:使用相同的密钥进行加密和解密
  2. 非对称加密:使用一对密钥(公钥和私钥)进行加密和解密
  3. 哈希算法:将任意长度的数据映射为固定长度的数据,不可逆
  4. 密码哈希专用算法:专门为密码存储设计的哈希算法,通常包含盐值和多次迭代

加密的主要目标

  • 机密性:确保只有授权用户才能访问信息
  • 完整性:确保数据在传输过程中未被篡改
  • 认证:验证通信双方的身份
  • 不可否认性:防止发送方否认曾发送过信息

对称加密算法

对称加密使用相同的密钥进行加密和解密,速度快但密钥分发是个挑战。

常见对称加密算法

AES (Advanced Encryption Standard)
  • 密钥长度:128位、192位或256位
  • 特点:目前最安全和广泛使用的对称加密算法
  • 应用场景:文件加密、数据库字段加密、通信加密
DES (Data Encryption Standard)
  • 密钥长度:56位
  • 特点:已被破解,不再安全
  • 应用场景:历史遗留系统
3DES (Triple DES)
  • 密钥长度:168位(实际安全强度约112位)
  • 特点:对DES的增强版,但速度较慢
  • 应用场景:金融系统中仍有使用
Blowfish
  • 密钥长度:32-448位
  • 特点:快速、免费、无专利限制
  • 应用场景:文件和磁盘加密

非对称加密算法

非对称加密使用一对密钥:公钥用于加密,私钥用于解密。解决了密钥分发问题,但计算开销较大。

常见非对称加密算法

RSA
  • 密钥长度:通常为2048位或4096位
  • 特点:最广泛使用的非对称加密算法
  • 应用场景:数字签名、密钥交换、HTTPS通信
ECC (Elliptic Curve Cryptography)
  • 密钥长度:256位ECC提供与3072位RSA相当的安全性
  • 特点:更短的密钥长度,更高的效率
  • 应用场景:移动设备、IoT设备、TLS
DSA (Digital Signature Algorithm)
  • 特点:专为数字签名设计
  • 应用场景:数字签名验证

哈希算法

哈希算法将任意长度的输入转换为固定长度的输出,且不可逆。

常见哈希算法

MD5 (Message Digest Algorithm 5)
  • 输出长度:128位(16字节)
  • 特点:已被破解,存在碰撞风险
  • 应用场景:文件完整性校验(不安全),不应用于安全场景
SHA-1 (Secure Hash Algorithm 1)
  • 输出长度:160位(20字节)
  • 特点:已被证明不安全
  • 应用场景:不应再用于安全场景
SHA-256 (SHA-2家族)
  • 输出长度:256位(32字节)
  • 特点:目前广泛使用的安全哈希算法
  • 应用场景:数字签名、区块链、文件完整性验证
SHA-3
  • 输出长度:可变(224, 256, 384, 512位)
  • 特点:最新的哈希标准,结构与SHA-2完全不同
  • 应用场景:高安全性要求的系统

密码哈希专用算法

这类算法专为存储用户密码设计,具有计算开销大、抗暴力破解的特性。

常见密码哈希算法

BCrypt
  • 特点:自带盐值,计算开销可调整
  • 应用场景:用户密码存储,Spring Security默认使用
PBKDF2 (Password-Based Key Derivation Function 2)
  • 特点:可配置迭代次数,使用HMAC作为伪随机函数
  • 应用场景:密码存储、密钥派生
Argon2
  • 特点:2015年密码哈希竞赛冠军,抗ASIC攻击
  • 应用场景:高安全性密码存储
SCrypt
  • 特点:内存密集型,抗硬件攻击
  • 应用场景:密码存储、加密货币

SpringBoot中的加密最佳实践

密码存储 - BCrypt

Spring Security默认使用BCrypt进行密码加密,这是处理用户密码的最佳实践。

import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class SecurityConfig {
    
    
    
    @Bean
    public PasswordEncoder passwordEncoder() {
    
    
        // 可以指定强度因子,默认为10
        return new BCryptPasswordEncoder(12);
    }
}

使用示例:

@Service
public class UserService {
    
    
    
    private final PasswordEncoder passwordEncoder;
    private final UserRepository userRepository;
    
    public UserService(PasswordEncoder passwordEncoder, UserRepository userRepository) {
    
    
        this.passwordEncoder = passwordEncoder;
        this.userRepository = userRepository;
    }
    
    public void registerUser(String username, String rawPassword) {
    
    
        // 加密密码
        String encodedPassword = passwordEncoder.encode(rawPassword);
        
        User user = new User();
        user.setUsername(username);
        user.setPassword(encodedPassword);
        
        userRepository.save(user);
    }
    
    public boolean verifyPassword(String rawPassword, String encodedPassword) {
    
    
        // 验证密码
        return passwordEncoder.matches(rawPassword, encodedPassword);
    }
}

敏感数据加密 - AES

对称加密适合加密数据库中的敏感字段,如信用卡号、身份证号等。

import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
import java.util.Base64;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

@Component
public class AESEncryptionService {
    
    
    
    @Value("${app.encryption.secret-key}")
    private String secretKey; // 应该是16、24或32字节长度的密钥
    
    public String encrypt(String data) throws Exception {
    
    
        SecretKeySpec keySpec = new SecretKeySpec(secretKey.getBytes(), "AES");
        Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
        cipher.init(Cipher.ENCRYPT_MODE, keySpec);
        byte[] encryptedBytes = cipher.doFinal(data.getBytes());
        return Base64.getEncoder().encodeToString(encryptedBytes);
    }
    
    public String decrypt(String encryptedData) throws Exception {
    
    
        SecretKeySpec keySpec = new SecretKeySpec(secretKey.getBytes(), "AES");
        Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
        cipher.init(Cipher.DECRYPT_MODE, keySpec);
        byte[] decodedBytes = Base64.getDecoder().decode(encryptedData);
        byte[] decryptedBytes = cipher.doFinal(decodedBytes);
        return new String(decryptedBytes);
    }
}

非对称加密 - RSA

RSA适合加密API密钥、JWT签名等场景。

import java.security.*;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Base64;
import javax.crypto.Cipher;
import org.springframework.stereotype.Component;

@Component
public class RSAEncryptionService {
    
    
    
    private final PrivateKey privateKey;
    private final PublicKey publicKey;
    
    public RSAEncryptionService() throws Exception {
    
    
        // 在实际应用中,应从配置文件或安全存储中加载密钥
        KeyPair keyPair = generateKeyPair();
        this.privateKey = keyPair.getPrivate();
        this.publicKey = keyPair.getPublic();
    }
    
    // 生成RSA密钥对
    private KeyPair generateKeyPair() throws Exception {
    
    
        KeyPairGenerator generator = KeyPairGenerator.getInstance("RSA");
        generator.initialize(2048); // 推荐使用2048位或更高
        return generator.generateKeyPair();
    }
    
    // 使用公钥加密
    public String encrypt(String data) throws Exception {
    
    
        Cipher cipher = Cipher.getInstance("RSA");
        cipher.init(Cipher.ENCRYPT_MODE, publicKey);
        byte[] encryptedBytes = cipher.doFinal(data.getBytes());
        return Base64.getEncoder().encodeToString(encryptedBytes);
    }
    
    // 使用私钥解密
    public String decrypt(String encryptedData) throws Exception {
    
    
        Cipher cipher = Cipher.getInstance("RSA");
        cipher.init(Cipher.DECRYPT_MODE, privateKey);
        byte[] decodedBytes = Base64.getDecoder().decode(encryptedData);
        byte[] decryptedBytes = cipher.doFinal(decodedBytes);
        return new String(decryptedBytes);
    }
    
    // 获取Base64编码的公钥字符串
    public String getPublicKeyString() {
    
    
        return Base64.getEncoder().encodeToString(publicKey.getEncoded());
    }
    
    // 从Base64字符串加载公钥
    public static PublicKey loadPublicKey(String publicKeyBase64) throws Exception {
    
    
        byte[] keyBytes = Base64.getDecoder().decode(publicKeyBase64);
        X509EncodedKeySpec spec = new X509EncodedKeySpec(keyBytes);
        KeyFactory keyFactory = KeyFactory.getInstance("RSA");
        return keyFactory.generatePublic(spec);
    }
    
    // 从Base64字符串加载私钥
    public static PrivateKey loadPrivateKey(String privateKeyBase64) throws Exception {
    
    
        byte[] keyBytes = Base64.getDecoder().decode(privateKeyBase64);
        PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(keyBytes);
        KeyFactory keyFactory = KeyFactory.getInstance("RSA");
        return keyFactory.generatePrivate(spec);
    }
}

在实际应用中,应该将RSA密钥对安全存储:

扫描二维码关注公众号,回复: 17573050 查看本文章
# application.properties 或 application.yml
app.encryption.rsa.private-key=MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBK...
app.encryption.rsa.public-key=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMI...

然后在服务中加载:

@Component
public class RSAEncryptionService {
    
    
    
    private final PrivateKey privateKey;
    private final PublicKey publicKey;
    
    public RSAEncryptionService(
            @Value("${app.encryption.rsa.private-key}") String privateKeyBase64,
            @Value("${app.encryption.rsa.public-key}") String publicKeyBase64) throws Exception {
    
    
        
        this.privateKey = loadPrivateKey(privateKeyBase64);
        this.publicKey = loadPublicKey(publicKeyBase64);
    }
    
    // 其他方法...
}

数据摘要 - SHA-256

用于生成数据指纹,验证数据完整性。

import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.util.Base64;
import org.springframework.stereotype.Component;

@Component
public class HashService {
    
    
    
    public String sha256Hash(String data) throws Exception {
    
    
        MessageDigest digest = MessageDigest.getInstance("SHA-256");
        byte[] hashBytes = digest.digest(data.getBytes(StandardCharsets.UTF_8));
        
        // 转换为十六进制字符串
        StringBuilder hexString = new StringBuilder();
        for (byte b : hashBytes) {
    
    
            String hex = Integer.toHexString(0xff & b);
            if (hex.length() == 1) hexString.append('0');
            hexString.append(hex);
        }
        return hexString.toString();
    }
    
    // 验证数据完整性
    public boolean verifyIntegrity(String data, String expectedHash) throws Exception {
    
    
        String actualHash = sha256Hash(data);
        return actualHash.equals(expectedHash);
    }
}

不同加密算法对比

算法类型 算法名称 安全性 性能 密钥长度 主要用途
对称加密 AES 128/192/256位 数据加密
DES 低(已破解) 56位 不推荐使用
3DES 168位 遗留系统
非对称加密 RSA 2048/4096位 密钥交换、数字签名
ECC 256位 移动设备加密
哈希算法 MD5 低(已破解) 输出128位 不用于安全场景
SHA-1 低(已破解) 输出160位 不用于安全场景
SHA-256 输出256位 数据完整性验证
密码哈希 BCrypt 可调整 N/A 密码存储
PBKDF2 可调整 N/A 密码存储、密钥派生
Argon2 非常高 可调整 N/A 高安全性密码存储

加密结果示例

以下是使用不同算法加密字符串"mypassword"的结果示例:

对称加密 (AES-256)

明文: mypassword
密钥: ThisIsA32ByteSecretKeyForAES256!
加密结果: 9HzYiT7qO5Hk3zGJ4Y8D/w==

非对称加密 (RSA-2048)

明文: mypassword
加密结果: 
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAqLmkfX6NpFLQxgWOsUHM
7XCjl0XgPKiLs+AY8i9qvF0tVwuWZ0HiTVMuZ4/3L8P/xzJ3CpEkwcYx0xUH9BIH
jIYJQqQmfIKGJL+Uc8jBxQximBcKMIHZG9LKxPZxY0XPLT+QVt5v5lkGkJ4Vn4oU
5QWzO+lQ5gVZ5n8YHCHxT4fQ3Jn+5ZVgB8XxhQl0YZ/axBz/jfK/iCqHHyiHl8Iq
8qgzODcnTrGYJQH1QvhXdPiQj5YNnbp5eQ==

哈希算法

MD5 (不安全,仅作示例)
明文: mypassword
哈希结果: 34819d7beeabb9260a5c854bc85b3e44
SHA-1 (不安全,仅作示例)
明文: mypassword
哈希结果: 91dfd9ddb4198affc5c194cd8ce6d338fde470e2
SHA-256
明文: mypassword
哈希结果: 89e01536ac207279409d4de1e5253e01f4a1769e696db0d6062ca9b8f56767c8

密码哈希专用算法

BCrypt
明文: mypassword
哈希结果(强度因子10): $2a$10$dPXHNHdNgZW6OIZQe0ypzOm2NXjcPQkjACYIkbLCGkqPPZIpwM3IS
哈希结果(强度因子12): $2a$12$1InE3VZmMr5pQdQ.ECxVXOQqrVGUz.yJjFZm5UNJj2L5ej8ZyMEHy
PBKDF2 (使用HMAC-SHA256,10000次迭代)
明文: mypassword
盐值: randomsalt
哈希结果: 7987c3e6f26a0d63fbbf4a7cc7d1565bd9af1dc6a068e2a3c6cf2114ea1e7dda
Argon2id
明文: mypassword
哈希结果: $argon2id$v=19$m=65536,t=3,p=4$c2FsdHNhbHRzYWx0$3mRCDpZzXRbG4LYsN0bPHnBZnZ7lL1oGJl6j9xn8ukw

注意:每次使用BCrypt、PBKDF2和Argon2等算法生成的哈希值都会不同,因为它们会自动生成随机盐值。

选择建议

在选择加密算法时,需要根据具体应用场景和安全需求做出权衡。以下是针对不同场景的建议:

用户密码存储

  • 最佳选择:BCrypt 或 Argon2
  • 理由:这些算法专为密码存储设计,内置盐值和工作因子调整,能有效抵抗彩虹表攻击和暴力破解
  • 实现方式:在 SpringBoot 中使用 PasswordEncoder 接口,默认实现为 BCryptPasswordEncoder

敏感数据加密(数据库字段)

  • 最佳选择:AES-256 (GCM模式)
  • 理由:提供高安全性和良好性能的平衡,GCM模式提供认证加密
  • 注意事项:密钥管理至关重要,考虑使用密钥管理服务

API通信安全

  • 最佳选择:HTTPS + JWT (使用RSA或ECDSA签名)
  • 理由:HTTPS保护传输层,JWT保护应用层,签名确保数据完整性和来源认证
  • 实现方式:使用 SpringBoot 的 spring-boot-starter-securityjjwt

数据完整性验证

  • 最佳选择:SHA-256 或 SHA-3
  • 理由:提供强大的抗碰撞性,生成唯一的数据指纹
  • 应用场景:文件校验、数据签名、区块链

移动和资源受限设备

  • 最佳选择:ECC (椭圆曲线加密)
  • 理由:比RSA使用更短的密钥提供同等安全性,计算开销更小
  • 应用场景:IoT设备、移动应用

实际应用建议

  1. 分层加密策略:对不同敏感级别的数据采用不同的加密方法
  2. 密钥轮换:定期更换加密密钥,减少密钥泄露风险
  3. 安全存储密钥:使用密钥管理服务或硬件安全模块(HSM)存储密钥
  4. 加密与认证结合:单纯的加密不足以保证安全,应结合认证机制
  5. 保持更新:关注加密算法的安全动态,及时更新到更安全的算法

最后,记住安全是一个整体过程,加密只是其中一环。完整的安全策略还应包括访问控制、安全编码实践、定期安全审计等多方面措施。