1. 项目概述:为什么Java开发者必须懂加密?

如果你是一名Java开发者,无论是刚入行还是已经摸爬滚打多年,加密这个话题你绝对绕不开。它不像Spring Boot的自动配置那样开箱即用,也不像MyBatis的XML映射那样直观。加密更像是一个沉默的守护者,平时你可能感觉不到它的存在,但一旦涉及到用户密码、支付信息、敏感数据传输,它就成了整个系统安全防线的基石。我见过太多项目,业务逻辑写得天花乱坠,性能优化做到极致,却在加密方案上草草了事,要么直接MD5走天下,要么密钥硬编码在代码里,安全审计时漏洞百出,轻则数据泄露,重则直接下线整改。

所以,今天我们不谈那些高深莫测的密码学理论,就从一名一线Java开发者的视角出发,拆解那些在真实项目中 最常用、最实用、也最容易踩坑 的加密解决方案。我们会从最基础的哈希算法聊起,到对称加密、非对称加密,再到国密算法和实际应用中的最佳实践。目标很明确:让你看完之后,不仅能回答面试官关于加密的“八股文”,更能真正在项目中设计出安全、合规、高效的加密方案,避开我当年踩过的那些“坑”。

2. 加密基础概念与核心分类

在动手写代码之前,我们必须先统一“语言”。加密领域有很多术语,如果概念不清,后续的选择和实现就会像无头苍蝇。

2.1 加密的核心目标:机密性、完整性与不可否认性

所有加密技术,最终都是为了实现三个核心安全目标:

  1. 机密性 :确保信息不被未授权的第三方读取。这是最直观的目标,比如用AES加密一段消息,只有持有密钥的人才能解密看到原文。
  2. 完整性 :确保信息在传输或存储过程中没有被篡改。例如,你下载一个软件安装包,如何确认它和官网发布的一模一样,没有被植入恶意代码?这就需要用到哈希算法或消息认证码。
  3. 不可否认性 :确保信息的发送方无法事后否认其发送行为。这在电子合同、交易日志等场景至关重要,通常通过数字签名实现。

2.2 Java中加密方案的三大分类

根据密钥的使用方式,我们可以把常用的加密方案分为三类,这是选择方案时的第一道选择题。

1. 哈希算法 这不是严格意义上的“加密”,因为它是单向的,无法解密。它的核心是生成一段固定长度的、唯一的“指纹”。任何微小的输入变化都会导致“指纹”天差地别。在Java中,你肯定用过 MD5 SHA-256 。它们常用于密码存储(需加盐)、文件完整性校验。但切记,MD5和SHA-1因为碰撞风险已不再安全,对于密码存储,应使用 BCrypt PBKDF2 Scrypt 这类专门设计的慢哈希函数。

2. 对称加密 加密和解密使用 同一把密钥 。就像你用同一把钥匙锁门和开门。优点是 速度快 ,适合加密大量数据。缺点是密钥分发和管理困难:如何安全地把密钥交给通信的另一方?Java内置的 AES DES (已不安全)、 3DES 都属于此类。AES是目前全球最主流、最安全的对称加密算法。

3. 非对称加密 使用一对密钥: 公钥 私钥 。公钥公开,私钥自己严格保密。用公钥加密的数据,只有对应的私钥能解密;用私钥签名的数据,任何人都可以用公钥验证签名者。它完美解决了对称加密的密钥分发问题,但缺点是 速度慢 ,比对称加密慢几个数量级。 RSA 是最典型的非对称加密算法,常用于密钥协商、数字签名。

在实际系统中,这三者往往是组合使用的。一个经典的HTTPS连接建立过程就包含了所有三者:非对称加密(RSA)协商一个临时会话密钥,然后用对称加密(AES)加密后续所有通信数据,期间还用哈希算法保证数据完整性。

3. 哈希算法的应用与陷阱

哈希函数是开发中最先接触的加密相关工具,但坑也最多。

3.1 基础哈希:MessageDigest的使用与误区

Java提供了 java.security.MessageDigest 类来支持MD5、SHA系列等算法。

import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;

public class SimpleHashDemo {
    public static String md5(String input) throws NoSuchAlgorithmException {
        MessageDigest md = MessageDigest.getInstance("MD5");
        byte[] digest = md.digest(input.getBytes());
        // 将字节数组转换为十六进制字符串
        StringBuilder hexString = new StringBuilder();
        for (byte b : digest) {
            String hex = Integer.toHexString(0xff & b);
            if (hex.length() == 1) hexString.append('0');
            hexString.append(hex);
        }
        return hexString.toString();
    }
}

看起来很简单,对吧?但这里有三个新手常犯的错误:

  1. 未指定字符集 input.getBytes() 会使用平台默认字符集,在不同环境下可能导致哈希结果不一致。必须明确指定,如 input.getBytes(StandardCharsets.UTF_8)
  2. 直接存储密码 绝对不要 用MD5或SHA-256的哈希值直接存储用户密码!彩虹表攻击可以瞬间破解常用密码的哈希值。
  3. 误用于需要解密场景 :哈希不可逆。如果你想着“我先哈希存起来,以后需要时再解密看看”,那方向就全错了。

3.2 密码存储的正确姿势:加盐与慢哈希

存储用户密码是哈希算法最重要的应用场景,也是安全重灾区。正确的做法是: 加盐 并使用 自适应慢哈希函数

  • 加盐 :在密码哈希前,拼接一个随机生成的、足够长的字符串(盐值)。这样,即使两个用户密码相同,哈希值也不同,彻底防御彩虹表攻击。盐值需要和哈希结果一起存储。
  • 慢哈希 :MD5、SHA-256设计初衷是快,这对密码存储是致命的。攻击者可以每秒进行数十亿次哈希计算来暴力破解。慢哈希函数(如BCrypt)故意设计得很慢,并且可以调整“工作因子”来增加计算成本,让暴力破解变得不切实际。

在Java中,强烈推荐使用 Spring Security Crypto 模块或 Bouncy Castle 库提供的BCrypt实现。

import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;

public class PasswordStorageDemo {
    public static void main(String[] args) {
        BCryptPasswordEncoder encoder = new BCryptPasswordEncoder(12); // 工作因子设为12
        String rawPassword = "mySecretPassword123";
        
        // 加密密码(自动生成并包含盐值)
        String encodedPassword = encoder.encode(rawPassword);
        System.out.println("Stored hash: " + encodedPassword); // 类似 $2a$12$...长达60字符
        
        // 验证密码
        boolean matches = encoder.matches(rawPassword, encodedPassword);
        System.out.println("Password matches: " + matches); // true
    }
}

实操心得

  • BCryptPasswordEncoder 构造时的工作因子(strength)默认是10。每增加1,计算时间大约翻一倍。建议在生产环境使用10-12。太高会影响登录体验,太低则安全性不足。
  • encodedPassword 字符串本身已经包含了算法标识、工作因子、盐值和最终的哈希值,你只需要把它作为一个整体存到数据库的密码字段即可,无需自己管理盐值。
  • 当硬件性能提升后,你可以通过提高工作因子来维持安全性,而无需让用户修改密码。

3.3 文件完整性校验与消息摘要

哈希另一个关键用途是校验文件完整性。比如从官网下载JDK安装包,如何确保下载过程中没出错、没被篡改?官网通常会提供该文件的SHA-256校验和。

import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.security.MessageDigest;

public class FileChecksumDemo {
    public static String calculateFileSHA256(String filePath) throws Exception {
        MessageDigest digest = MessageDigest.getInstance("SHA-256");
        try (InputStream is = Files.newInputStream(Paths.get(filePath))) {
            byte[] buffer = new byte[8192];
            int read;
            while ((read = is.read(buffer)) > 0) {
                digest.update(buffer, 0, read); // 分批更新,避免大文件内存溢出
            }
        }
        // ... 转换为十六进制字符串(同上)
        return bytesToHex(digest.digest());
    }
}

注意事项

  • 对于大文件,一定要使用 update 方法分批处理,切忌一次性读入内存。
  • 对比校验和时,务必进行 恒定时间比较 ,避免计时攻击。不要用 String.equals() ,而应使用 MessageDigest.isEqual() 方法。

4. 对称加密:AES的深度解析与实践

当需要加密可解密的数据时,对称加密是首选。而AES是当之无愧的王者。

4.1 AES的核心概念:模式、填充与初始化向量

直接使用 Cipher.getInstance("AES") 会抛异常,因为AES需要更多参数:

  • 工作模式 :定义如何重复应用算法来加密比一个块更长的数据。
    • ECB :最简单的模式,每个块独立加密。 绝对不要用! 因为相同的明文块会产生相同的密文块,会泄露数据模式。
    • CBC :最常用的模式之一,每个块加密前会与前一个密文块进行异或操作,因此需要 初始化向量 。安全性好,但无法并行加密。
    • GCM :现代推荐模式。它不仅提供机密性,还提供完整性认证(自动验证数据是否被篡改)。是当前TLS 1.3等的首选,无需额外填充。
  • 填充方案 :AES是块加密,数据长度必须是16字节的倍数。如果不是,就需要填充。
    • PKCS5Padding / PKCS7Padding :最常用的填充方式。
  • 初始化向量 :一个随机数,用于CBC、GCM等模式,确保即使相同的明文、相同的密钥,每次加密也会产生不同的密文。 IV不需要保密,但必须不可预测,且每次加密都应随机生成 。通常将IV和密文一起存储/传输。

4.2 AES-CBC模式完整示例与坑点

下面是一个使用AES-256-CBC-PKCS5Padding的完整示例,包含了密钥生成、加密、解密以及IV的处理。

import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.security.SecureRandom;
import java.util.Base64;

public class AesCbcDemo {
    private static final String ALGORITHM = "AES/CBC/PKCS5Padding";
    private static final int KEY_SIZE = 256; // AES-256

    // 生成一个安全的随机密钥(仅演示,实际密钥应从安全的地方获取)
    public static SecretKey generateKey() throws Exception {
        KeyGenerator keyGen = KeyGenerator.getInstance("AES");
        keyGen.init(KEY_SIZE);
        return keyGen.generateKey();
    }

    // 加密
    public static String encrypt(String plainText, SecretKey key) throws Exception {
        Cipher cipher = Cipher.getInstance(ALGORITHM);
        // 生成一个随机的16字节IV
        byte[] iv = new byte[16];
        SecureRandom random = new SecureRandom();
        random.nextBytes(iv);
        IvParameterSpec ivSpec = new IvParameterSpec(iv);
        
        cipher.init(Cipher.ENCRYPT_MODE, key, ivSpec);
        byte[] cipherText = cipher.doFinal(plainText.getBytes(StandardCharsets.UTF_8));
        
        // 将IV和密文拼接在一起,方便传输/存储。IV不需要保密。
        byte[] combined = new byte[iv.length + cipherText.length];
        System.arraycopy(iv, 0, combined, 0, iv.length);
        System.arraycopy(cipherText, 0, combined, iv.length, cipherText.length);
        
        return Base64.getEncoder().encodeToString(combined);
    }

    // 解密
    public static String decrypt(String combinedBase64, SecretKey key) throws Exception {
        byte[] combined = Base64.getDecoder().decode(combinedBase64);
        // 分离IV和密文
        byte[] iv = new byte[16];
        byte[] cipherText = new byte[combined.length - 16];
        System.arraycopy(combined, 0, iv, 0, 16);
        System.arraycopy(combined, 16, cipherText, 0, cipherText.length);
        
        Cipher cipher = Cipher.getInstance(ALGORITHM);
        cipher.init(Cipher.DECRYPT_MODE, key, new IvParameterSpec(iv));
        byte[] plainText = cipher.doFinal(cipherText);
        
        return new String(plainText, StandardCharsets.UTF_8);
    }

    public static void main(String[] args) throws Exception {
        SecretKey key = generateKey();
        String originalText = "这是一段需要加密的敏感信息";
        
        String encrypted = encrypt(originalText, key);
        System.out.println("加密后 (Base64): " + encrypted);
        
        String decrypted = decrypt(encrypted, key);
        System.out.println("解密后: " + decrypted);
        System.out.println("匹配: " + originalText.equals(decrypted)); // true
    }
}

关键点与避坑指南

  1. 密钥管理是核心 :示例中的 generateKey() 方法每次运行都会生成新密钥,仅用于演示。 生产环境中,密钥必须通过安全的方式生成、存储和轮换 ,例如使用硬件安全模块、云服务商的密钥管理服务,或至少从安全的配置中心获取。切忌硬编码在源代码中!
  2. IV必须随机且唯一 :每次加密都必须使用新的随机IV。重复使用IV会严重削弱CBC模式的安全性。
  3. 算法字符串必须完整 "AES/CBC/PKCS5Padding" 不能简写为 "AES" ,否则JVM会使用默认实现,而默认实现可能不安全且在不同JDK版本间不一致。
  4. 处理异常 Cipher.doFinal() 可能抛出 BadPaddingException 等异常。在解密时,这通常意味着密钥错误、IV错误或数据被篡改。 切勿在异常信息中泄露具体原因 ,给攻击者提供线索,统一返回“解密失败”即可。

4.3 更现代的选择:AES-GCM模式

GCM模式更受现代安全协议推崇,因为它同时提供了加密和认证。在Java中使用GCM稍微复杂一点,因为它需要一个额外的“认证标签”。

import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.spec.GCMParameterSpec;
import java.security.SecureRandom;

public class AesGcmDemo {
    private static final String ALGORITHM = "AES/GCM/NoPadding"; // GCM不需要填充
    private static final int TAG_LENGTH_BIT = 128; // 认证标签长度,通常为128位
    private static final int IV_LENGTH_BYTE = 12; // GCM推荐使用12字节的IV

    public static byte[] encrypt(byte[] plainText, SecretKey key) throws Exception {
        Cipher cipher = Cipher.getInstance(ALGORITHM);
        byte[] iv = new byte[IV_LENGTH_BYTE];
        new SecureRandom().nextBytes(iv);
        
        GCMParameterSpec parameterSpec = new GCMParameterSpec(TAG_LENGTH_BIT, iv);
        cipher.init(Cipher.ENCRYPT_MODE, key, parameterSpec);
        
        byte[] cipherText = cipher.doFinal(plainText); // 这里已经包含了认证标签
        
        // 同样,将IV和密文拼接
        byte[] combined = new byte[iv.length + cipherText.length];
        System.arraycopy(iv, 0, combined, 0, iv.length);
        System.arraycopy(cipherText, 0, combined, iv.length, cipherText.length);
        return combined;
    }
    
    // 解密方法类似,需要从combined数据中分离IV,并用GCMParameterSpec初始化Cipher为解密模式
}

GCM的优势

  • 内置完整性校验 :解密时会自动验证数据是否被篡改,如果被篡改会抛出 AEADBadTagException
  • 性能 :在某些硬件上,GCM有专门的优化。
  • 无需填充 :因为它是流加密模式。

GCM的注意事项

  • IV绝对不能重复 :对于同一个密钥,IV重复使用是灾难性的,会完全破坏安全性。
  • 认证标签长度 :通常使用128位,这是安全性和性能的平衡点。

5. 非对称加密:RSA的密钥对管理与混合加密系统

非对称加密解决了密钥分发难题,但性能是硬伤。因此,它很少用于直接加密大量数据,而是用于更关键的任务。

5.1 生成与管理RSA密钥对

Java中可以使用 KeyPairGenerator 来生成RSA密钥对。

import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.util.Base64;

public class RsaKeyGenDemo {
    public static void main(String[] args) throws Exception {
        KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSA");
        keyGen.initialize(2048); // 密钥长度,目前推荐至少2048位
        KeyPair keyPair = keyGen.generateKeyPair();
        
        PublicKey publicKey = keyPair.getPublic();
        PrivateKey privateKey = keyPair.getPrivate();
        
        // 通常将公钥以PEM格式(Base64编码的DER)分发出去
        String publicKeyPem = "-----BEGIN PUBLIC KEY-----\n" +
                               Base64.getMimeEncoder().encodeToString(publicKey.getEncoded()) +
                               "\n-----END PUBLIC KEY-----";
        System.out.println(publicKeyPem);
        
        // 私钥必须严格保密存储
        // 可以考虑使用PKCS#8格式,并用密码进行加密保护
    }
}

密钥长度选择 :RSA-1024已被认为不安全, 生产环境最低使用2048位 ,对长期安全要求高的系统应考虑3072或4096位。注意,密钥长度翻倍,性能会显著下降。

5.2 RSA的典型应用:数据加密与数字签名

1. 数据加密(效率低,不推荐用于大数据)

// 使用公钥加密
Cipher encryptCipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
encryptCipher.init(Cipher.ENCRYPT_MODE, publicKey);
// RSA有长度限制,例如2048位密钥最多加密245字节明文
byte[] secretMessageBytes = encryptCipher.doFinal(plainText.getBytes(StandardCharsets.UTF_8));

// 使用私钥解密
Cipher decryptCipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
decryptCipher.init(Cipher.DECRYPT_MODE, privateKey);
byte[] decryptedMessageBytes = decryptCipher.doFinal(secretMessageBytes);

2. 数字签名(核心应用场景) 数字签名用于验证数据的完整性和来源。发送方用私钥签名,接收方用公钥验签。

import java.security.Signature;

// 发送方:用私钥签名
Signature privateSignature = Signature.getInstance("SHA256withRSA");
privateSignature.initSign(privateKey);
privateSignature.update(dataToSign.getBytes(StandardCharsets.UTF_8));
byte[] signature = privateSignature.sign();

// 接收方:用公钥验签
Signature publicSignature = Signature.getInstance("SHA256withRSA");
publicSignature.initVerify(publicKey);
publicSignature.update(receivedData.getBytes(StandardCharsets.UTF_8));
boolean isCorrect = publicSignature.verify(receivedSignature);

5.3 混合加密系统:结合对称与非对称的优势

这是实际中最常用的模式,结合了对称加密的高效和非对称加密的安全密钥交换。

  1. 发送方随机生成一个 对称密钥 (称为会话密钥)。
  2. 发送方使用接收方的 公钥 加密这个对称密钥。
  3. 发送方使用这个 对称密钥 ,用AES等算法加密实际要发送的 大量数据
  4. 发送方将“加密后的对称密钥”和“加密后的数据”一起发送给接收方。
  5. 接收方用自己的 私钥 解密出对称密钥。
  6. 接收方用对称密钥解密出原始数据。

这样,只有持有私钥的接收方能解密出会话密钥,进而解密数据。而数据本身的加密是高效的对称加密。SSL/TLS协议的核心思想正是如此。

6. 国密算法在Java中的集成与应用

在一些对信息安全有特定要求的领域,需要使用国家密码管理局认定的商用密码算法,即“国密算法”。主要包括:

  • SM3 :哈希算法,类似SHA-256。
  • SM4 :对称加密算法,类似AES,分组长度128位。
  • SM2 :基于椭圆曲线的非对称加密算法,类似ECC,用于加密和签名。

Java标准库并未内置国密算法支持,需要引入第三方库,最常用的是 Bouncy Castle

6.1 引入Bouncy Castle依赖

以Maven为例:

<dependency>
    <groupId>org.bouncycastle</groupId>
    <artifactId>bcprov-jdk15on</artifactId>
    <version>1.70</version> <!-- 使用最新稳定版 -->
</dependency>

在使用前,需要将Bouncy Castle注册为安全提供者。

import org.bouncycastle.jce.provider.BouncyCastleProvider;
import java.security.Security;

public class GmDemo {
    static {
        Security.addProvider(new BouncyCastleProvider());
    }
    // ... 后续可以使用SM3, SM4, SM2等算法
}

6.2 SM4加密解密示例

SM4的使用方式与AES非常相似。

import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.security.SecureRandom;

public class Sm4CbcDemo {
    private static final String ALGORITHM = "SM4/CBC/PKCS5Padding";
    
    public static SecretKey generateSm4Key() throws Exception {
        KeyGenerator kg = KeyGenerator.getInstance("SM4", "BC"); // 指定Provider
        kg.init(128); // SM4密钥长度固定为128位
        return kg.generateKey();
    }
    
    public static byte[] encrypt(byte[] data, SecretKey key) throws Exception {
        Cipher cipher = Cipher.getInstance(ALGORITHM, "BC");
        byte[] iv = new byte[16];
        new SecureRandom().nextBytes(iv);
        cipher.init(Cipher.ENCRYPT_MODE, key, new IvParameterSpec(iv));
        
        byte[] cipherText = cipher.doFinal(data);
        // 拼接IV和密文
        byte[] result = new byte[iv.length + cipherText.length];
        System.arraycopy(iv, 0, result, 0, iv.length);
        System.arraycopy(cipherText, 0, result, iv.length, cipherText.length);
        return result;
    }
    
    // 解密过程是加密的逆过程
}

注意事项

  • 使用国密算法时,务必确认项目合规性要求。
  • Bouncy Castle的算法名称和提供者名称( "BC" )需要正确指定。
  • SM4的密钥长度固定为128位,但通过增加轮数(与AES-128类似)来保证安全性。

7. 实战中的加密方案选型与最佳实践

了解了各种工具后,如何在项目中做出正确选择?以下是我总结的决策路径和实操守则。

7.1 场景化选型指南

场景 推荐方案 理由与关键点
用户密码存储 BCrypt / Argon2 / PBKDF2 慢哈希,抗彩虹表和暴力破解。工作因子根据硬件调整。
数据库字段加密 应用层AES-GCM 在应用层加密后存储,数据库层面是密文。密钥由KMS管理。避免使用数据库透明加密(TDE)应对所有威胁。
网络传输加密 TLS 1.2/1.3 (HTTPS) 绝对不要自己实现! 使用成熟的协议和库(如JDK内置SSLContext)。
API请求参数签名 HMAC-SHA256 使用双方共享的密钥,对请求参数生成签名,防止篡改和重放攻击。需加入时间戳和随机数防重放。
数字证书与签名 RSA (2048+) 或 ECDSA 代码签名、JWT令牌签名、PDF签名等。私钥安全存储是关键。
大规模数据加密 混合加密 使用RSA/EC加密一个随机的AES会话密钥,再用该会话密钥加密数据。
满足国密合规 SM2/SM3/SM4 集成Bouncy Castle,注意算法标识和模式选择。

7.2 密钥生命周期管理

加密系统的安全性,90%取决于密钥管理。代码中的算法是公开的,密钥才是秘密。

  • 生成 :使用密码学安全的随机数生成器( SecureRandom )。
  • 存储
    • 切忌硬编码 :不要将密钥写在代码、配置文件或环境变量中(除非是加密后的)。
    • 使用专用服务 :阿里云KMS、AWS KMS、HashiCorp Vault等。它们提供密钥的安全存储、访问审计和自动轮换。
    • 本地方案 :如果必须本地存储,考虑使用白盒密码学或硬件安全模块,至少要对密钥文件进行访问权限控制。
  • 轮换 :定期更换密钥。设计系统时,应支持多版本密钥共存,以便平滑轮换。例如,数据库密文字段可以存储一个密钥版本号。
  • 销毁 :安全地删除不再使用的密钥。

7.3 常见问题排查与调试技巧

  1. InvalidKeyException: Illegal key size

    • 原因 :默认的JRE策略文件限制了加密强度。历史上出于出口管制原因。
    • 解决 :下载并安装Java Cryptography Extension Unlimited Strength Jurisdiction Policy Files。或者,对于较新版本的JDK(如JDK 8u151+),这个限制已被放宽或移除。
  2. BadPaddingException AEADBadTagException

    • 原因 :解密时密钥错误、IV错误、密文被篡改、或加密/解密时使用的算法/模式/填充不匹配。
    • 排查 :首先检查加密和解密双方使用的算法字符串是否 完全一致 。然后确认密钥和IV是否正确传递。对于GCM模式,确保认证标签验证通过。
  3. 加密结果每次都不一样

    • 原因 :这是正常的,也是安全的体现!如果使用了随机IV(如CBC模式)或GCM模式,即使相同的明文和密钥,每次加密输出都不同。IV会与密文一起存储。
  4. 性能问题

    • 现象 :使用RSA加密大量数据时超时或CPU占用高。
    • 解决 :回顾5.3节,RSA不应直接加密大数据。采用混合加密,RSA只用于加密一个小的对称密钥。
  5. 国密算法找不到Provider

    • 原因 :未正确引入Bouncy Castle JAR包,或未在代码中注册Provider。
    • 解决 :确认依赖已添加,并在使用相关算法前执行 Security.addProvider(new BouncyCastleProvider())

7.4 安全编码红线

  1. 禁止使用不安全的算法 :DES、RC4、MD5、SHA-1在绝大多数新系统中都应禁止使用。
  2. 禁止使用ECB模式 :对于AES等分组加密,永远不要使用ECB模式。
  3. 禁止重复使用IV :对于CBC、GCM等需要IV的模式,每次加密必须使用新的、密码学安全的随机IV。
  4. 禁止密钥硬编码 :这是最低级也最危险的错误。
  5. 不要自己发明加密算法 :使用经过全球密码学家多年公开审查的标准算法和库。

加密是一个深水区,但也是构建可信赖系统的必备技能。从理解哈希、对称和非对称的基本原理开始,到熟练运用AES、RSA,再到根据场景合理选型并管好密钥,每一步都需要谨慎。希望这篇详解能成为你手边的一份实用指南,当遇到加密需求时,能帮你快速找到安全可靠的解决方案,避开那些隐藏的陷阱。记住,在安全问题上,“能用”和“用得对”之间,往往隔着巨大的风险鸿沟。

更多推荐