RsaUtils

加密和解密操作

生成密钥对

以下示例展示了如何使用 RsaUtils 生成 RSA 公钥和私钥,并将其编码为 Base64 字符串。

package com.litongjava.maxkb.dao;

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

import org.junit.Test;

import com.litongjava.tio.utils.crypto.RsaUtils;

public class SystemSettingDaoTest {

  @Test
  public void getPublicKeyAndPrivateKey() {
    // 生成 RSA 密钥对
    KeyPair pair = RsaUtils.generateKeyPair();
    PublicKey publicKey = pair.getPublic();
    PrivateKey privateKey = pair.getPrivate();

    // 将密钥编码为 Base64 字符串
    String publicKeyStr = Base64.getEncoder().encodeToString(publicKey.getEncoded());
    String privateKeyStr = Base64.getEncoder().encodeToString(privateKey.getEncoded());

    // 输出密钥
    System.out.println("Public Key:");
    System.out.println(publicKeyStr);
    System.out.println("\nPrivate Key:");
    System.out.println(privateKeyStr);
  }
}

示例输出:

Public Key:
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEApLt4COWWcLo5XrhgSqirNFIQ1CktDMMjHOboLvWd0wNYNM7+jNYUtAaFQ8YSoBPgxgWxYW0e/MmmK4idJYOAHOgdkjAvP3Rg2YRRhtHFeR+R3VmM2+Y8meDaQg8YwiNvTEShi19Sm2uQCtV2r3+cbFnqsR1/NbmXWkJBQQ0AuWOmuF7n9qeFFfpo7U0+nS02JKVQDkq2d28Ma6lkDOIZ5CFsyWAb+LvxqUQOGDsOFijzCb/XFbMKIcxTKj1UjBynd6YFEMX55AnRG/VmXXtY/mOZU1WofCT9rUZZpiAds/7UaGyPNotfJJY6thuYsVlJW97whAOFM5/7miO8TPzwBwIDAQAB

Private Key:
MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCku3gI5ZZwujleuGBKqKs0UhDUKS0MwyMc5ugu9Z3TA1g0zv6M1hS0BoVDxhKgE+DGBbFhbR78yaYriJ0lg4Ac6B2SMC8/dGDZhFGG0cV5H5HdWYzb5jyZ4NpCDxjCI29MRKGLX1Kba5AK1Xavf5xsWeqxHX81uZdaQkFBDQC5Y6a4Xuf2p4UV+mjtTT6dLTYkpVAOSrZ3bwxrqWQM4hnkIWzJYBv4u/GpRA4YOw4WKPMJv9cVswohzFMqPVSMHKd3pgUQxfnkCdEb9WZde1j+Y5lTVah8JP2tRlmmIB2z/tRobI82i18kljq2G5ixWUlb3vCEA4Uzn/uaI7xM/PAHAgMBAAECggEBAI4SiQHTUJ5EXfpNNe7t7UoghRcVtB7PpVbl5tWpS8aTmd1hsLQyZoSlIwZCrAmXW0It4r/N5u3J1CCzoCScdz93CRntqLInYuf2cIHAJXnDOoAXHZ8BwL5N6K8Uahv1h6XVgyW6vnoAmKhfVg8iFfx0yC6c6/uf5uMXRUQfPMKlw9OdZ6kj+FC85eAAlUZER+P13a7Xn7IClP1HC/qN5bOmB4RFJiBc3/I18y2Ymf7EbdhmWsbKdz9YDwBwx7Z8iNEgnr4KJ25H9zTECbTys91itoe8SkJxCed0+Nv6R+N/l+eBdiB4GjB0RZlfTtmh5LtOqZlWlOZbYPPBpJ476NECgYEA6uvvepq6+WDH7q+EAtsvUiaxVsgfGl0g2XfUTBvyIgRBh3Eu6P4mjsIzIsZDfdmvVdY6rSIqY1jCgx4fyC4Vo/ECSVmjtM5yXb49fiPUNfBjol9MV7r76SdA/V/OAgqI9Msx1xBLMfS/obmbyXKml9hW4LAe/PPLrbSwSXyYtpUCgYEAs4NPZA25bnkbcG1QQvJBzQTNYu/26h2TBTeW1p9pg1JmwuvQccGCzl3lGOwde9uOniYMpTQ7goQpR7KehXonFW+bSEfrahqoclIwwNyl8lQ8eaxcye9MVX9QUUOwSf8sbjdFSsvmpi/MyX7rFA62Uv4odtcNo2SmiQP1ABSk8SsCgYBQQTv14ahgi3yiARoTM7gN06Qf5owhoJEZWgOP4LgugM+2KqeA91pKyPNZPOxPsS9iHqa7AQIEII2K556p73x7HlnOny37FdAiUFyHnEviBXa0QOQ+0GVA+KUOKk2hiuHQ5x3mv+1AasGQyWz8PAvHsiOz7NcZSVxawnC7GjK9pQKBgDepUpeijcnMcY6lxopLlc2dXJX99CDZtLmcZ//6g/v9M/HirspnB0k34g6dP/MkvA8bmzYqgP1SP36EyxR2MOI7rfl2m29V/r8b7xQOFsKSDgJoSIkw0wl7pYrdIy9+mOc/6hNsMAN9tNERspCQk0YemqtmuUrdram0eWZiAFbrAoGAA5+l/Kyh191GdC2OHfFVpFw1P9t3zacpf62wYUMSCNxH45Sjg6tfj/6iSAwZGOxISkB99XZrV7eEZ5/e1F+2kr8a84SfZDwa750N2mosVwV1ESzQgWx8I9LA6NX1P1Hf2ryyYosqssW9yw3AWFzrhEWjEIADuKP6r6UOd3TFIZI=

加密和解密消息

以下示例展示了如何使用生成的 RSA 公钥加密消息,并使用私钥解密。

package com.litongjava.maxkb.dao;

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

import org.junit.Test;

import com.litongjava.tio.utils.crypto.RsaUtils;

public class SystemSettingDaoTest {

  @Test
  public void test() {
    // 生成 RSA 密钥对
    KeyPair pair = RsaUtils.generateKeyPair();
    PublicKey publicKey = pair.getPublic();
    PrivateKey privateKey = pair.getPrivate();

    // 将密钥编码为 Base64 字符串
    String publicKeyStr = Base64.getEncoder().encodeToString(publicKey.getEncoded());
    String privateKeyStr = Base64.getEncoder().encodeToString(privateKey.getEncoded());

    // 输出密钥
    System.out.println("Public Key:");
    System.out.println(publicKeyStr);
    System.out.println("\nPrivate Key:");
    System.out.println(privateKeyStr);

    // 原始消息
    String originalMessage = "Hello, RSA!";
    System.out.println("原始消息: " + originalMessage);

    // 使用公钥加密消息
    String encryptedMessage = RsaUtils.encrypt(originalMessage, publicKey);
    System.out.println("加密消息: " + encryptedMessage);

    // 使用私钥解密消息
    String decryptedMessage = RsaUtils.decrypt(encryptedMessage, privateKey);
    System.out.println("解密消息: " + decryptedMessage);
  }
}

示例输出:

Public Key:
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEApLt4COWWcLo5XrhgSqirNFIQ1CktDMMjHOboLvWd0wNYNM7+jNYUtAaFQ8YSoBPgxgWxYW0e/MmmK4idJYOAHOgdkjAvP3Rg2YRRhtHFeR+R3VmM2+Y8meDaQg8YwiNvTEShi19Sm2uQCtV2r3+cbFnqsR1/NbmXWkJBQQ0AuWOmuF7n9qeFFfpo7U0+nS02JKVQDkq2d28Ma6lkDOIZ5CFsyWAb+LvxqUQOGDsOFijzCb/XFbMKIcxTKj1UjBynd6YFEMX55AnRG/VmXXtY/mOZU1WofCT9rUZZpiAds/7UaGyPNotfJJY6thuYsVlJW97whAOFM5/7miO8TPzwBwIDAQAB

Private Key:
MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCku3gI5ZZwujleuGBKqKs0UhDUKS0MwyMc5ugu9Z3TA1g0zv6M1hS0BoVDxhKgE+DGBbFhbR78yaYriJ0lg4Ac6B2SMC8/dGDZhFGG0cV5H5HdWYzb5jyZ4NpCDxjCI29MRKGLX1Kba5AK1Xavf5xsWeqxHX81uZdaQkFBDQC5Y6a4Xuf2p4UV+mjtTT6dLTYkpVAOSrZ3bwxrqWQM4hnkIWzJYBv4u/GpRA4YOw4WKPMJv9cVswohzFMqPVSMHKd3pgUQxfnkCdEb9WZde1j+Y5lTVah8JP2tRlmmIB2z/tRobI82i18kljq2G5ixWUlb3vCEA4Uzn/uaI7xM/PAHAgMBAAECggEBAI4SiQHTUJ5EXfpNNe7t7UoghRcVtB7PpVbl5tWpS8aTmd1hsLQyZoSlIwZCrAmXW0It4r/N5u3J1CCzoCScdz93CRntqLInYuf2cIHAJXnDOoAXHZ8BwL5N6K8Uahv1h6XVgyW6vnoAmKhfVg8iFfx0yC6c6/uf5uMXRUQfPMKlw9OdZ6kj+FC85eAAlUZER+P13a7Xn7IClP1HC/qN5bOmB4RFJiBc3/I18y2Ymf7EbdhmWsbKdz9YDwBwx7Z8iNEgnr4KJ25H9zTECbTys91itoe8SkJxCed0+Nv6R+N/l+eBdiB4GjB0RZlfTtmh5LtOqZlWlOZbYPPBpJ476NECgYEA6uvvepq6+WDH7q+EAtsvUiaxVsgfGl0g2XfUTBvyIgRBh3Eu6P4mjsIzIsZDfdmvVdY6rSIqY1jCgx4fyC4Vo/ECSVmjtM5yXb49fiPUNfBjol9MV7r76SdA/V/OAgqI9Msx1xBLMfS/obmbyXKml9hW4LAe/PPLrbSwSXyYtpUCgYEAs4NPZA25bnkbcG1QQvJBzQTNYu/26h2TBTeW1p9pg1JmwuvQccGCzl3lGOwde9uOniYMpTQ7goQpR7KehXonFW+bSEfrahqoclIwwNyl8lQ8eaxcye9MVX9QUUOwSf8sbjdFSsvmpi/MyX7rFA62Uv4odtcNo2SmiQP1ABSk8SsCgYBQQTv14ahgi3yiARoTM7gN06Qf5owhoJEZWgOP4LgugM+2KqeA91pKyPNZPOxPsS9iHqa7AQIEII2K556p73x7HlnOny37FdAiUFyHnEviBXa0QOQ+0GVA+KUOKk2hiuHQ5x3mv+1AasGQyWz8PAvHsiOz7NcZSVxawnC7GjK9pQKBgDepUpeijcnMcY6lxopLlc2dXJX99CDZtLmcZ//6g/v9M/HirspnB0k34g6dP/MkvA8bmzYqgP1SP36EyxR2MOI7rfl2m29V/r8b7xQOFsKSDgJoSIkw0wl7pYrdIy9+mOc/6hNsMAN9tNERspCQk0YemqtmuUrdram0eWZiAFbrAoGAA5+l/Kyh191GdC2OHfFVpFw1P9t3zacpf62wYUMSCNxH45Sjg6tfj/6iSAwZGOxISkB99XZrV7eEZ5/e1F+2kr8a84SfZDwa750N2mosVwV1ESzQgWx8I9LA6NX1P1Hf2ryyYosqssW9yw3AWFzrhEWjEIADuKP6r6UOd3TFIZI=

将私钥和公钥存入数据库

为了安全地存储 RSA 密钥对,我们将私钥和公钥存入数据库中的 system_setting 表。以下步骤展示了如何创建数据表、存储密钥,并使用 JSON 格式进行存储。

创建数据表

首先,创建一个用于存储系统设置的表,包括 RSA 密钥对。

CREATE TABLE "public"."system_setting" (
  "type" "pg_catalog"."int4" NOT NULL PRIMARY KEY,
  "meta" "pg_catalog"."jsonb" NOT NULL,
  "create_time" "pg_catalog"."timestamptz" NOT NULL,
  "update_time" "pg_catalog"."timestamptz" NOT NULL
);

字段说明:

  • type: 标识系统设置的类型,这里 1 可以代表 RSA 密钥对。
  • meta: 使用 jsonb 类型存储密钥的详细信息,包括私钥和公钥。
  • create_time: 记录创建时间。
  • update_time: 记录最后更新时间。

存入数据库

以下 Java 代码展示了如何生成 RSA 密钥对,并将其存入 system_setting 表中。

package com.litongjava.maxkb.dao;

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

import org.junit.Test;

import com.litongjava.jfinal.aop.Aop;
import com.litongjava.maxkb.MaxKbApp;
import com.litongjava.tio.boot.testing.TioBootTest;
import com.litongjava.tio.utils.crypto.RsaUtils;

public class SystemSettingDaoTest {

  @Test
  public void test() {
    // 初始化测试环境
    TioBootTest.scan(MaxKbApp.class);

    // 生成 RSA 密钥对
    KeyPair pair = RsaUtils.generateKeyPair();
    PublicKey publicKey = pair.getPublic();
    PrivateKey privateKey = pair.getPrivate();

    // 将密钥编码为 Base64 字符串
    String privateKeyStr = Base64.getEncoder().encodeToString(privateKey.getEncoded());
    String publicKeyStr = Base64.getEncoder().encodeToString(publicKey.getEncoded());

    // 使用 AOP 获取 SystemSettingDao 实例并保存密钥
    Aop.get(SystemSettingDao.class).saveRsa(privateKeyStr, publicKeyStr);
  }
}

SystemSettingDao 类实现:

package com.litongjava.maxkb.dao;

import java.sql.SQLException;
import java.util.Date;

import org.postgresql.util.PGobject;

import com.jfinal.kit.Kv;
import com.litongjava.db.activerecord.Db;
import com.litongjava.db.activerecord.Record;
import com.litongjava.tio.utils.json.JsonUtils;

public class SystemSettingDao {

  /**
   * 获取 RSA 密钥对
   *
   * @return 包含 RSA 密钥对的 PGobject 对象
   */
  public PGobject getRsa() {
    String sql = "SELECT meta FROM system_setting WHERE type = 1";
    return Db.queryFirst(sql);
  }

  /**
   * 保存 RSA 密钥对到数据库
   *
   * @param privateKeyStr 私钥的 Base64 编码字符串
   * @param publicKeyStr  公钥的 Base64 编码字符串
   */
  public void saveRsa(String privateKeyStr, String publicKeyStr) {
    // 创建一个键值对,存储私钥和公钥
    Kv kv = Kv.by("key", privateKeyStr).set("value", publicKeyStr);

    // 将键值对转换为 JSONB 类型
    PGobject meta = new PGobject();
    try {
      meta.setType("jsonb");
      meta.setValue(JsonUtils.toJson(kv));
    } catch (SQLException e) {
      e.printStackTrace();
    }

    // 创建记录对象并设置各字段
    Record record = Record.by("meta", meta).set("type", 1);
    record.set("create_time", new Date());
    record.set("update_time", new Date());

    // 保存记录到 system_setting 表
    Db.save("system_setting", record);
  }
}

加密私钥

为了增强私钥的安全性,可以使用密码对私钥进行加密。以下示例使用 Bouncy Castle 库,采用 PKCS#8 格式、scrypt 密钥派生函数和 AES128-CBC 加密算法对私钥进行加密保护。

添加依赖

pom.xml 中添加 Bouncy Castle 的依赖:

<dependencies>
    <!-- Bouncy Castle Provider -->
    <dependency>
        <groupId>org.bouncycastle</groupId>
        <artifactId>bcprov-jdk15on</artifactId>
        <version>1.70</version> <!-- 请使用最新版本 -->
    </dependency>
    <!-- Bouncy Castle PKIX/CMS/EAC -->
    <dependency>
        <groupId>org.bouncycastle</groupId>
        <artifactId>bcpkix-jdk15on</artifactId>
        <version>1.70</version>
    </dependency>
</dependencies>

添加工具类

创建 BouncyCastleUtils 工具类,负责加密和解密私钥。

import java.io.IOException;
import java.io.StringReader;
import java.io.StringWriter;
import java.security.PrivateKey;
import java.security.Security;

import org.bouncycastle.asn1.pkcs.PrivateKeyInfo;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.openssl.PEMParser;
import org.bouncycastle.openssl.PKCS8Generator;
import org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter;
import org.bouncycastle.openssl.jcajce.JceOpenSSLPKCS8DecryptorProviderBuilder;
import org.bouncycastle.openssl.jcajce.JceOpenSSLPKCS8EncryptorBuilder;
import org.bouncycastle.operator.InputDecryptorProvider;
import org.bouncycastle.operator.OperatorCreationException;
import org.bouncycastle.operator.OutputEncryptor;
import org.bouncycastle.pkcs.PKCS8EncryptedPrivateKeyInfo;
import org.bouncycastle.pkcs.PKCSException;
import org.bouncycastle.util.io.pem.PemGenerationException;
import org.bouncycastle.util.io.pem.PemObject;
import org.bouncycastle.util.io.pem.PemWriter;

public class BouncyCastleUtils {

  // 静态块,确保 Bouncy Castle 提供者已添加
  static {
    if (Security.getProvider(BouncyCastleProvider.PROVIDER_NAME) == null) {
      Security.addProvider(new BouncyCastleProvider());
    }
  }

  /**
   * 使用 PKCS#8、scrypt 和 AES-128-CBC 加密私钥。
   *
   * @param privateKey 要加密的 PrivateKey 对象
   * @param password   用于加密的密码
   * @return 加密后的 PEM 格式字符串
   */
  public static String encryptPrivateKey(PrivateKey privateKey, String password) {
    // 将 PrivateKey 转换为 PrivateKeyInfo
    PrivateKeyInfo privateKeyInfo = PrivateKeyInfo.getInstance(privateKey.getEncoded());

    // 初始化 PKCS8 加密器构建器,使用 AES-128-CBC
    JceOpenSSLPKCS8EncryptorBuilder encryptorBuilder = new JceOpenSSLPKCS8EncryptorBuilder(PKCS8Generator.AES_128_CBC);
    encryptorBuilder.setProvider("BC");
    encryptorBuilder.setPassword(password.toCharArray());
    encryptorBuilder.setIterationCount(16384); // Scrypt 的 N 参数

    // 构建 OutputEncryptor
    OutputEncryptor encryptor;
    try {
      encryptor = encryptorBuilder.build();
    } catch (OperatorCreationException e) {
      throw new RuntimeException(e);
    }

    // 生成加密的 PKCS#8 私钥
    PKCS8Generator pkcs8Generator = new PKCS8Generator(privateKeyInfo, encryptor);
    PemObject pemObject;
    try {
      pemObject = pkcs8Generator.generate();
    } catch (PemGenerationException e) {
      throw new RuntimeException(e);
    }

    // 将 PemObject 写入 PEM 格式字符串
    StringWriter stringWriter = new StringWriter();
    try (PemWriter pemWriter = new PemWriter(stringWriter)) {
      pemWriter.writeObject(pemObject);
    } catch (IOException e) {
      throw new RuntimeException(e);
    }

    return stringWriter.toString();
  }

  /**
   * 使用提供的密码解密 PEM 格式的加密私钥。
   *
   * @param encryptedPrivateKeyPem 加密后的 PEM 格式私钥字符串
   * @param password               用于解密的密码
   * @return 解密后的 PrivateKey 对象
   */
  public static PrivateKey decryptPrivateKey(String encryptedPrivateKeyPem, String password) {
    try (StringReader stringReader = new StringReader(encryptedPrivateKeyPem);
         PEMParser pemParser = new PEMParser(stringReader)) {

      Object object = pemParser.readObject();

      if (!(object instanceof PKCS8EncryptedPrivateKeyInfo)) {
        throw new IllegalArgumentException("提供的 PEM 不包含加密的私钥。");
      }

      PKCS8EncryptedPrivateKeyInfo encryptedPrivateKeyInfo = (PKCS8EncryptedPrivateKeyInfo) object;

      // 初始化解密器提供者
      InputDecryptorProvider decryptorProvider = new JceOpenSSLPKCS8DecryptorProviderBuilder()
          .setProvider("BC")
          .build(password.toCharArray());

      // 解密获取 PrivateKeyInfo
      PrivateKeyInfo privateKeyInfo = encryptedPrivateKeyInfo.decryptPrivateKeyInfo(decryptorProvider);

      // 将 PrivateKeyInfo 转换为 PrivateKey
      JcaPEMKeyConverter keyConverter = new JcaPEMKeyConverter().setProvider("BC");
      PrivateKey privateKey = keyConverter.getPrivateKey(privateKeyInfo);

      return privateKey;

    } catch (IOException | PKCSException | OperatorCreationException e) {
      throw new RuntimeException("解密私钥失败", e);
    }
  }
}

工具类说明:

  • 静态块: 确保 Bouncy Castle 提供者已添加到 Java 安全提供者列表中。
  • encryptPrivateKey 方法:
    • PrivateKey 转换为 PrivateKeyInfo 对象。
    • 使用 JceOpenSSLPKCS8EncryptorBuilder 配置加密参数(AES-128-CBC、scrypt)。
    • 生成加密后的 PKCS#8 格式私钥,并将其写入 PEM 格式字符串。
  • decryptPrivateKey 方法:
    • 从 PEM 格式字符串中读取加密的私钥。
    • 使用提供的密码解密,获取 PrivateKeyInfo
    • PrivateKeyInfo 转换回 PrivateKey 对象。

使用工具类

以下示例展示了如何使用 BouncyCastleUtils 工具类对私钥进行加密和解密。

package com.litongjava.maxkb.utils;

import java.security.KeyPair;
import java.security.PrivateKey;

import org.junit.Test;

import com.litongjava.tio.utils.crypto.RsaUtils;

public class BouncyCastleUtilsTest {

  @Test
  public void test() {
    // 生成 RSA 密钥对
    KeyPair pair = RsaUtils.generateKeyPair();

    PrivateKey privateKey = pair.getPrivate();
    System.out.println("原始私钥(编码后): " + privateKey.getEncoded());

    // 使用密码加密私钥
    String encryptPrivateKey = BouncyCastleUtils.encryptPrivateKey(privateKey, "max_kb_password");
    System.out.println("加密后的私钥:\n" + encryptPrivateKey);

    // 使用密码解密私钥
    PrivateKey decryptPrivateKey = BouncyCastleUtils.decryptPrivateKey(encryptPrivateKey, "max_kb_password");
    System.out.println("解密后的私钥(编码后): " + decryptPrivateKey.getEncoded());
  }
}

示例输出:

原始私钥(编码后): [B@6d06d69c
加密后的私钥:
-----BEGIN ENCRYPTED PRIVATE KEY-----
MIIFHzBXBgkqhkiG9w0BBQ0wSjApBgkqhkiG9w0BBQwwHAQIipG1NbTwYNCAggA
...
-----END ENCRYPTED PRIVATE KEY-----

解密后的私钥(编码后): [B@6d06d69c

注意事项:

  • 密码安全: 请确保密码的强度和安全性,以防止私钥被未授权访问。
  • 加密算法: 本示例使用了 AES-128-CBC 加密算法和 scrypt 密钥派生函数。根据需求,您可以选择更强的加密算法。
  • Bouncy Castle 版本: 请确保使用最新版本的 Bouncy Castle 库,以获得最新的安全修复和功能增强。

总结

本文档详细介绍了如何生成 RSA 密钥对、加密解密消息、将密钥存储到数据库,以及使用 Bouncy Castle 库对私钥进行加密保护的完整流程。通过这些步骤,您可以确保在 Java 应用程序中安全地生成、存储和使用 RSA 密钥对。