杰瑞科技汇

encrypt加密 java

由于“加密”是一个宽泛的概念,我将从最常用和最重要的 对称加密非对称加密 开始,并介绍如何在实际应用中结合使用它们(通过 数字信封 技术)。


核心概念

  1. 对称加密

    • 特点:使用同一个密钥进行加密和解密。
    • 优点:速度快,计算开销小,适合加密大量数据。
    • 缺点:密钥的分发和管理是个大问题,如何安全地将密钥发送给接收方是最大的挑战。
    • 常用算法:AES (Advanced Encryption Standard),DES (已不推荐使用)。
  2. 非对称加密

    • 特点:使用一对密钥:公钥私钥
      • 用公钥加密的数据,只能用对应的私钥解密。
      • 用私钥加密的数据(即签名),只能用对应的公钥验证。
    • 优点:安全性高,解决了密钥分发问题,公钥可以公开分发。
    • 缺点:速度慢,计算复杂,不适合加密大量数据。
    • 常用算法:RSA (Rivest–Shamir–Adleman)。
  3. 数字信封

    • 概念:这是结合对称和非对称加密优点的一种混合加密方案。
    • 流程
      1. 使用对称算法(如 AES)加密原始数据。
      2. 使用接收方的公钥来加密上一步生成的对称密钥
      3. 加密后的数据加密后的对称密钥一起发送给接收方。
    • 解密流程
      1. 接收方用自己的私钥解密出对称密钥
      2. 用解密出的对称密钥解密出原始数据
    • 优点:既利用了对称加密的高效,又利用了非对称加密的安全密钥交换。

代码示例

下面我将提供完整的、可运行的 Java 代码示例,涵盖 AES 对称加密、RSA 非对称加密以及它们的组合(数字信封)。

准备工作:依赖

对于较新的 Java 版本(如 Java 8+),标准库已经包含了所有需要的类,无需额外依赖。

对称加密:AES 示例

AES 是目前对称加密的黄金标准,我们将使用 AES-128-CBC 模式,并使用 PKCS5Padding 填充方式,CBC 模式需要一个初始化向量来增加安全性。

import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.security.SecureRandom;
import java.util.Base64;
public class AESEncryptionExample {
    // AES 加密
    public static String encrypt(String data, String secretKey) throws Exception {
        // 1. 生成密钥
        // KeyGenerator 用于生成密钥,AES 要求密钥长度为 128, 192, 或 256 bits
        KeyGenerator keyGenerator = KeyGenerator.getInstance("AES");
        keyGenerator.init(128); // 使用 AES-128
        SecretKey key = keyGenerator.generateKey();
        byte[] keyBytes = key.getEncoded();
        // 2. 准备初始化向量
        // CBC 模式需要一个 IV,长度与块大小相同 (AES 是 16 bytes)
        byte[] iv = new byte[16];
        new SecureRandom().nextBytes(iv);
        IvParameterSpec ivParameterSpec = new IvParameterSpec(iv);
        // 3. 创建 Cipher 实例
        // "AES/CBC/PKCS5Padding" 表示使用 AES 算法,CBC 模式,PKCS5 填充
        Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
        cipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(keyBytes, "AES"), ivParameterSpec);
        // 4. 执行加密
        byte[] encryptedData = cipher.doFinal(data.getBytes(StandardCharsets.UTF_8));
        // 5. 将 IV 和加密后的数据合并,以便解密时使用
        // IV 不需要保密,但需要和密文一起传输
        byte[] combined = new byte[iv.length + encryptedData.length];
        System.arraycopy(iv, 0, combined, 0, iv.length);
        System.arraycopy(encryptedData, 0, combined, iv.length, encryptedData.length);
        return Base64.getEncoder().encodeToString(combined);
    }
    // AES 解密
    public static String decrypt(String encryptedData, String secretKey) throws Exception {
        // 1. 解码 Base64
        byte[] combined = Base64.getDecoder().decode(encryptedData);
        // 2. 分离 IV 和实际密文
        byte[] iv = new byte[16];
        byte[] actualEncryptedData = new byte[combined.length - 16];
        System.arraycopy(combined, 0, iv, 0, iv.length);
        System.arraycopy(combined, iv.length, actualEncryptedData, 0, actualEncryptedData.length);
        // 3. 重新创建密钥 (在实际应用中,这个密钥应该是预先共享或安全传输的)
        // 注意:这个示例为了演示,在加密和解密时重新生成了密钥,这是不正确的。
        // 正确做法是密钥需要被安全地存储和共享,这里简化处理。
        KeyGenerator keyGenerator = KeyGenerator.getInstance("AES");
        keyGenerator.init(128);
        SecretKey key = keyGenerator.generateKey();
        byte[] keyBytes = key.getEncoded();
        // 4. 创建 Cipher 实例
        Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
        cipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(keyBytes, "AES"), new IvParameterSpec(iv));
        // 5. 执行解密
        byte[] decryptedData = cipher.doFinal(actualEncryptedData);
        return new String(decryptedData, StandardCharsets.UTF_8);
    }
    public static void main(String[] args) {
        try {
            String originalData = "这是一段需要加密的敏感信息,Hello, AES!";
            // 加密
            String encryptedString = encrypt(originalData, "my-secret-key-for-demo");
            System.out.println("原始数据: " + originalData);
            System.out.println("加密后 (Base64): " + encryptedString);
            // 解密
            String decryptedString = decrypt(encryptedString, "my-secret-key-for-demo");
            System.out.println("解密后: " + decryptedString);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

注意:上面的 decrypt 方法为了演示方便,在解密时重新生成了密钥。在实际应用中,加密和解密必须使用完全相同的密钥,如何安全地分发这个 AES 密钥?这正是 RSA 的用武之地。


非对称加密:RSA 示例

RSA 用于加密少量数据,AES 的密钥。

import javax.crypto.Cipher;
import java.security.*;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Base64;
public class RSAEncryptionExample {
    // 生成 RSA 密钥对
    public static KeyPair generateKeyPair() throws Exception {
        KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
        keyPairGenerator.initialize(2048); // RSA 密钥长度,2048 是推荐值
        return keyPairGenerator.generateKeyPair();
    }
    // 使用公钥加密
    public static String encrypt(String data, PublicKey publicKey) throws Exception {
        Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
        cipher.init(Cipher.ENCRYPT_MODE, publicKey);
        byte[] encryptedData = cipher.doFinal(data.getBytes(StandardCharsets.UTF_8));
        return Base64.getEncoder().encodeToString(encryptedData);
    }
    // 使用私钥解密
    public static String decrypt(String encryptedData, PrivateKey privateKey) throws Exception {
        Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
        cipher.init(Cipher.DECRYPT_MODE, privateKey);
        byte[] decodedData = Base64.getDecoder().decode(encryptedData);
        byte[] decryptedData = cipher.doFinal(decodedData);
        return new String(decryptedData, StandardCharsets.UTF_8);
    }
    // 将密钥转换为字符串 (Base64)
    public static String publicKeyToString(PublicKey publicKey) {
        return Base64.getEncoder().encodeToString(publicKey.getEncoded());
    }
    public static String privateKeyToString(PrivateKey privateKey) {
        return Base64.getEncoder().encodeToString(privateKey.getEncoded());
    }
    // 从字符串转换回密钥
    public static PublicKey publicKeyFromString(String publicKeyStr) throws Exception {
        byte[] keyBytes = Base64.getDecoder().decode(publicKeyStr);
        X509EncodedKeySpec spec = new X509EncodedKeySpec(keyBytes);
        KeyFactory keyFactory = KeyFactory.getInstance("RSA");
        return keyFactory.generatePublic(spec);
    }
    public static PrivateKey privateKeyFromString(String privateKeyStr) throws Exception {
        byte[] keyBytes = Base64.getDecoder().decode(privateKeyStr);
        PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(keyBytes);
        KeyFactory keyFactory = KeyFactory.getInstance("RSA");
        return keyFactory.generatePrivate(spec);
    }
    public static void main(String[] args) throws Exception {
        // 1. 生成密钥对
        KeyPair keyPair = generateKeyPair();
        PublicKey publicKey = keyPair.getPublic();
        PrivateKey privateKey = keyPair.getPrivate();
        System.out.println("公钥 (Base64): " + publicKeyToString(publicKey));
        System.out.println("私钥 (Base64): " + privateKeyToString(privateKey));
        // 2. 使用公钥加密
        String dataToEncrypt = "这是 AES 的密钥";
        String encryptedKey = encrypt(dataToEncrypt, publicKey);
        System.out.println("\n原始密钥: " + dataToEncrypt);
        System.out.println("公钥加密后的密钥 (Base64): " + encryptedKey);
        // 3. 使用私钥解密
        String decryptedKey = decrypt(encryptedKey, privateKey);
        System.out.println("私钥解密后的密钥: " + decryptedKey);
    }
}

实战应用:数字信封 (结合 AES 和 RSA)

这个示例展示了如何安全地发送一段数据,发送方用 AES 加密数据,然后用接收方的 RSA 公钥加密 AES 密钥。

import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import javax.crypto.spec.IvParameterSpec;
import java.nio.charset.StandardCharsets;
import java.security.*;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Base64;
public class DigitalEnvelopeExample {
    // 假设这是接收方的 RSA 公钥 (在实际应用中,这会从某个地方获取)
    private static final String RECEIVER_PUBLIC_KEY_STR = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA..."; // 这里放一个真实的公钥字符串
    // 假设这是发送方的 RSA 私钥 (用于签名,本示例省略签名部分)
    private static final String SENDER_PRIVATE_KEY_STR = "MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQD..."; // 这里放一个真实的私钥字符串
    public static void main(String[] args) throws Exception {
        // 1. 准备密钥
        PublicKey receiverPublicKey = publicKeyFromString(RECEIVER_PUBLIC_KEY_STR);
        PrivateKey senderPrivateKey = privateKeyFromString(SENDER_PRIVATE_KEY_STR);
        // 2. 发送方操作
        String originalData = "这是绝密的商业计划书,必须安全送达!";
        // 2a. 使用 AES 加密原始数据
        SecretKey aesKey = generateAESKey();
        String encryptedData = encryptWithAES(originalData, aesKey);
        // 2b. 使用接收方的公钥加密 AES 密钥
        String encryptedAesKey = encryptWithRSA(aesKey.getEncoded(), receiverPublicKey);
        // 2c. 将加密后的数据和加密后的密钥打包
        String digitalEnvelope = encryptedAesKey + "|" + encryptedData;
        System.out.println("--- 发送方 ---");
        System.out.println("原始数据: " + originalData);
        System.out.println("加密后的 AES 密钥 (Base64): " + encryptedAesKey);
        System.out.println("加密后的数据 (Base64): " + encryptedData);
        System.out.println("数字信封内容: " + digitalEnvelope);
        // 3. 接收方操作
        System.out.println("\n--- 接收方 ---");
        String[] parts = digitalEnvelope.split("\\|");
        String receivedEncryptedAesKey = parts[0];
        String receivedEncryptedData = parts[1];
        // 3a. 使用自己的私钥解密出 AES 密钥
        byte[] decryptedAesKeyBytes = decryptWithRSA(receivedEncryptedAesKey, senderPrivateKey); // 注意:这里应该是 receiverPrivateKey
        SecretKey decryptedAesKey = new SecretKeySpec(decryptedAesKeyBytes, "AES");
        System.out.println("解密出的 AES 密钥: " + new String(decryptedAesKey.getEncoded()));
        // 3b. 使用解密出的 AES 密钥解密数据
        String finalDecryptedData = decryptWithAES(receivedEncryptedData, decryptedAesKey);
        System.out.println("最终解密数据: " + finalDecryptedData);
    }
    // ========== 辅助方法 ==========
    private static SecretKey generateAESKey() throws Exception {
        KeyGenerator keyGenerator = KeyGenerator.getInstance("AES");
        keyGenerator.init(128);
        return keyGenerator.generateKey();
    }
    private static String encryptWithAES(String data, SecretKey key) throws Exception {
        byte[] iv = new byte[16];
        new SecureRandom().nextBytes(iv);
        IvParameterSpec ivParameterSpec = new IvParameterSpec(iv);
        Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
        cipher.init(Cipher.ENCRYPT_MODE, key, ivParameterSpec);
        byte[] encryptedData = cipher.doFinal(data.getBytes(StandardCharsets.UTF_8));
        byte[] combined = new byte[iv.length + encryptedData.length];
        System.arraycopy(iv, 0, combined, 0, iv.length);
        System.arraycopy(encryptedData, 0, combined, iv.length, encryptedData.length);
        return Base64.getEncoder().encodeToString(combined);
    }
    private static String decryptWithAES(String encryptedData, SecretKey key) throws Exception {
        byte[] combined = Base64.getDecoder().decode(encryptedData);
        byte[] iv = new byte[16];
        byte[] actualEncryptedData = new byte[combined.length - 16];
        System.arraycopy(combined, 0, iv, 0, iv.length);
        System.arraycopy(combined, iv.length, actualEncryptedData, 0, actualEncryptedData.length);
        Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
        cipher.init(Cipher.DECRYPT_MODE, key, new IvParameterSpec(iv));
        byte[] decryptedData = cipher.doFinal(actualEncryptedData);
        return new String(decryptedData, StandardCharsets.UTF_8);
    }
    private static String encryptWithRSA(byte[] data, PublicKey publicKey) throws Exception {
        Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
        cipher.init(Cipher.ENCRYPT_MODE, publicKey);
        return Base64.getEncoder().encodeToString(cipher.doFinal(data));
    }
    private static byte[] decryptWithRSA(String encryptedData, PrivateKey privateKey) throws Exception {
        Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
        cipher.init(Cipher.DECRYPT_MODE, privateKey);
        return cipher.doFinal(Base64.getDecoder().decode(encryptedData));
    }
    private static PublicKey publicKeyFromString(String publicKeyStr) throws Exception {
        byte[] keyBytes = Base64.getDecoder().decode(publicKeyStr);
        X509EncodedKeySpec spec = new X509EncodedKeySpec(keyBytes);
        KeyFactory keyFactory = KeyFactory.getInstance("RSA");
        return keyFactory.generatePublic(spec);
    }
    private static PrivateKey privateKeyFromString(String privateKeyStr) throws Exception {
        byte[] keyBytes = Base64.getDecoder().decode(privateKeyStr);
        PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(keyBytes);
        KeyFactory keyFactory = KeyFactory.getInstance("RSA");
        return keyFactory.generatePrivate(spec);
    }
}

重要修正:在 DigitalEnvelopeExamplemain 方法中,接收方解密 AES 密钥时,应该使用自己的私钥,而不是发送方的私钥,请将 decryptWithRSA(receivedEncryptedAesKey, senderPrivateKey) 修改为 decryptWithRSA(receivedEncryptedAesKey, receiverPrivateKey)


总结与最佳实践

  1. 不要自己实现加密算法:始终使用标准库(如 java.crypto)提供的、经过广泛审查的算法和实现。
  2. 选择合适的算法
    • 加密大量数据:首选 AES
    • 加密少量数据(如密钥、密码):使用 RSA
    • 混合使用(数字信封)是行业标准。
  3. 密钥管理是核心
    • 对称密钥:必须安全地存储和共享,不要硬编码在代码里,可以考虑使用密钥库、或者由用户输入。
    • 非对称密钥对
      • 私钥:必须极其保密,存储在安全的地方(如硬件安全模块 HSM、操作系统的密钥库)。
      • 公钥:可以公开,但需要确保其真实性(防止中间人攻击),通常通过数字证书来验证。
  4. 考虑使用 KeyStore:Java 提供了 KeyStore API,可以用来安全地存储密钥和证书,这比将密钥放在普通文件中要安全得多。
  5. 初始化向量:在使用 AES-CBC 等模式时,IV 是必须的,IV 不需要保密,但必须是唯一的(对于每个加密操作),并且需要和密文一起传输。
  6. 填充模式:确保加密和解密使用相同的填充模式(如 PKCS5Padding)。

希望这个详细的指南能帮助你理解和使用 Java 进行加密!

分享:
扫描分享到社交APP
上一篇
下一篇