You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何将RSA公私钥转为字符串并正确存储到PostgreSQL数据库?

问题:RSA密钥存储与签名验证的核心问题

一、RSA公私钥转字符串存储的问题

我用以下代码生成RSA密钥对:

Security.addProvider(new BouncyCastleProvider());
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA", "BC");
keyPairGenerator.initialize(2048, new SecureRandom());

KeyPair keyPair = keyPairGenerator.generateKeyPair();

直接调用keyPair.getPublic().toString()转字符串存PostgreSQL时,得到的是无法使用的调试格式:

RSA Private CRT Key [5e:cf:06:fc:ff:52:cd:16:3a:b4:28:42:dc:c2:5b:9c:e3:fc:a8:c1],[56:66:d1:a4]
             modulus: b3b9ae25337bedf971aa2767412eec0f2d7792873e9c500eb0cff747e5f80b182f3f29ed97b28471de8c68346354bb4fdbe8f8f3ba1b09e745e3235ffdd2ff11efe379cca27914a6a6754e6f967f21358c05462bc20426f7f8c4df0f6632673897b17d8a0592b6291c9281dd3e94b0ed9c8f02dc02df9d8ae8dc2688dfadcb2a94840b2b22a4528e1169b79056288b111a70ebebc8e838e17943c0b88b103c27bbaa6c40624babde1e97e86a42e532cb58c0948705b8943a0f203d648828848e20e5d4ea0aaab8b06d804732352c8eae01cc9672e5140e34dc5054d073d8e617b211fa80ccac810045d4def198e587ff0b7ba858dfa11fd3682cdfeb88d360df
     public exponent: 10001

请问正确的转换存储方法是什么?

二、签名验证代码的两个问题

我写的验证代码存在两个问题:无法从公钥填充data变量,无法从公钥填充signature变量,代码如下:

import io.jsonwebtoken.lang.Assert;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.junit.jupiter.api.Test;

import javax.xml.bind.DatatypeConverter;
import java.io.ByteArrayInputStream;
import java.io.UnsupportedEncodingException;
import java.nio.charset.StandardCharsets;
import java.security.InvalidKeyException;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.PublicKey;
import java.security.SecureRandom;
import java.security.Security;
import java.security.Signature;
import java.security.SignatureException;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.util.Base64;

public class CertificateGenerateTestApiTest {

    String data; // complete certificate and metadata. We need to get of when we generate a certificate
    String signature; // signature of the certificate

    @Test
    public void certificateGenerationTest()
    {
        // Generate private and public keys
        KeyPair keyPair = generateCertificate();

        PublicKey publicCertificate = keyPair.getPublic();

        // Problem 1 - think how to populate "data" variable from public certificate
        // Problem 2 - think how to populate "signature" variable from public certificate

        // Get public key as byte[] in order later on to be stored into DB
        byte[] originalPublicCertificate = privateCertificateToByte(publicCertificate);

        // Let's say we received some certificate by some web client, and we want to verify it against our original certificate
        byte[] secondPublicCertificateReceivedByClient = cloneByteArrayData(originalPublicCertificate);

        // Verify certificate
        Assert.isTrue(verifyCertificate(secondPublicCertificateReceivedByClient));
    }

    private KeyPair generateCertificate()
    {
        try {
            Security.addProvider(new BouncyCastleProvider());
            KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA", "BC");
            keyPairGenerator.initialize(2048, new SecureRandom());

            KeyPair keyPair = keyPairGenerator.generateKeyPair();

            return keyPair;
        } catch (NoSuchAlgorithmException | NoSuchProviderException e) {
            e.printStackTrace();
        }
        return null;
    }

    private boolean verifyCertificate(byte[] secondPublicCertificateReceivedByClient)
    {
        PublicKey publicKey = getKeyPayload(byteToString(secondPublicCertificateReceivedByClient));
        boolean verified = false;

        try {
            verified = verifySignature(data.getBytes("UTF8"), publicKey,
                    DatatypeConverter.parseHexBinary(signature));
        } catch (SignatureException | UnsupportedEncodingException e) {

        } catch (NoSuchAlgorithmException | InvalidKeyException e) {
            e.printStackTrace();
        }
        return verified;
    }

    private boolean verifySignature(byte[] data, PublicKey key, byte[] signature)
            throws NoSuchAlgorithmException, InvalidKeyException, SignatureException {
        Signature signer = Signature.getInstance("SHA1withDSA");
        signer.initVerify(key);
        signer.update(data);
        try {
            if (signer.verify(signature)) {
                return true;
            }
        } catch (SignatureException e) {
            e.printStackTrace();
        }
        return false;
    }

    private PublicKey getKeyPayload(String certificateString) {
        try {
            CertificateFactory cf = CertificateFactory.getInstance("X.509");
            ByteArrayInputStream bis =
                    new ByteArrayInputStream(certificateString.getBytes(StandardCharsets.UTF_8));

            Certificate certificate = null;
            while (bis.available() > 0) {
                certificate = cf.generateCertificate(bis);
            }

            return (certificate == null) ? null : certificate.getPublicKey();
        } catch (CertificateException e) {
            e.printStackTrace();
        }
        return null;
    }

    private String byteToString(byte[] publicKeyBytes)
    {
        return Base64.getEncoder().encodeToString(publicKeyBytes);
    }

    private byte[] privateCertificateToByte(PublicKey publicCertificate)
    {
        return publicCertificate.getEncoded();
    }

    /**
     * Clone byte[] data
     */
    private byte[] cloneByteArrayData(byte[] sourceArray)
    {
        int sourceOffset = 0; /* Starting index in the source array */;
        int destinationOffset = 0; // Starting index in the destination array
        byte[] destinationArray = new byte[sourceArray.length];

        for (int i = 0; i < sourceArray.length; i++) {
            destinationArray[destinationOffset + i] = sourceArray[sourceOffset + i];
        }

        return destinationArray;
    }
}

解决方案

一、RSA公私钥转字符串存储的正确方式

toString()返回的是调试用的可读信息,不是可序列化的标准格式。正确做法是用密钥的标准编码格式转Base64字符串:

1. 密钥转字符串(存储到DB)

// 公钥转Base64字符串(X.509标准格式)
PublicKey publicKey = keyPair.getPublic();
String publicKeyStr = Base64.getEncoder().encodeToString(publicKey.getEncoded());

// 私钥转Base64字符串(PKCS#8标准格式)
PrivateKey privateKey = keyPair.getPrivate();
String privateKeyStr = Base64.getEncoder().encodeToString(privateKey.getEncoded());

直接将Base64字符串存入数据库的TEXT类型字段即可。

2. 从字符串还原密钥(从DB读取后)

// 还原公钥
byte[] publicKeyBytes = Base64.getDecoder().decode(publicKeyStr);
KeyFactory keyFactory = KeyFactory.getInstance("RSA", "BC");
PublicKey restoredPublicKey = keyFactory.generatePublic(new X509EncodedKeySpec(publicKeyBytes));

// 还原私钥
byte[] privateKeyBytes = Base64.getDecoder().decode(privateKeyStr);
PrivateKey restoredPrivateKey = keyFactory.generatePrivate(new PKCS8EncodedKeySpec(privateKeyBytes));

二、签名验证代码的问题修正

你的代码存在三个核心错误:混淆了公钥与证书、签名算法不匹配、对签名逻辑的误解。以下是针对性修复:

核心逻辑澄清

公钥本身不包含data和signature:

  • data是你要签名的业务数据/元数据(比如用户信息、交易内容),需要你主动定义。
  • signature是用私钥对data签名后生成的结果,不是从公钥提取的。

问题1:填充data变量

直接定义你需要签名的内容,比如:

// 示例:签名用户ID和有效期
data = "userId:123,expire:2025-12-31";

问题2:填充signature变量

用私钥对data生成签名:

private void generateSignature(PrivateKey privateKey) {
    try {
        // 用RSA签名算法(避免SHA1,推荐SHA256)
        Signature signer = Signature.getInstance("SHA256withRSA", "BC");
        signer.initSign(privateKey, new SecureRandom());
        signer.update(data.getBytes(StandardCharsets.UTF_8));
        byte[] signatureBytes = signer.sign();
        // 转十六进制字符串存储(也可以用Base64)
        signature = DatatypeConverter.printHexBinary(signatureBytes);
    } catch (NoSuchAlgorithmException | InvalidKeyException | SignatureException e) {
        e.printStackTrace();
    }
}

完整修正后的代码

import io.jsonwebtoken.lang.Assert;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.junit.jupiter.api.Test;

import javax.xml.bind.DatatypeConverter;
import java.nio.charset.StandardCharsets;
import java.security.*;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.X509EncodedKeySpec;

public class CertificateGenerateTestApiTest {

    String data; // 要签名的业务数据/元数据
    String signature; // 私钥对data生成的签名

    @Test
    public void certificateGenerationTest() {
        // 生成密钥对
        KeyPair keyPair = generateKeyPair();
        assert keyPair != null;

        // 填充data:定义签名内容
        data = "userId:123,expire:2025-12-31";
        // 填充signature:用私钥生成签名
        generateSignature(keyPair.getPrivate());

        // 公钥转字节数组存DB
        byte[] originalPublicKeyBytes = keyPair.getPublic().getEncoded();
        // 模拟客户端传来的公钥
        byte[] receivedPublicKeyBytes = cloneByteArrayData(originalPublicKeyBytes);

        // 验证签名
        Assert.isTrue(verifySignatureWithReceivedPublicKey(receivedPublicKeyBytes));
    }

    private KeyPair generateKeyPair() {
        try {
            Security.addProvider(new BouncyCastleProvider());
            KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA", "BC");
            keyPairGenerator.initialize(2048, new SecureRandom());
            return keyPairGenerator.generateKeyPair();
        } catch (NoSuchAlgorithmException | NoSuchProviderException e) {
            e.printStackTrace();
        }
        return null;
    }

    private void generateSignature(PrivateKey privateKey) {
        try {
            Signature signer = Signature.getInstance("SHA256withRSA", "BC");
            signer.initSign(privateKey, new SecureRandom());
            signer.update(data.getBytes(StandardCharsets.UTF_8));
            byte[] signatureBytes = signer.sign();
            signature = DatatypeConverter.printHexBinary(signatureBytes);
        } catch (NoSuchAlgorithmException | InvalidKeyException | SignatureException e) {
            e.printStackTrace();
        }
    }

    private boolean verifySignatureWithReceivedPublicKey(byte[] receivedPublicKeyBytes) {
        try {
            // 还原公钥(无需解析证书,直接用X509EncodedKeySpec)
            KeyFactory keyFactory = KeyFactory.getInstance("RSA", "BC");
            PublicKey receivedPublicKey = keyFactory.generatePublic(new X509EncodedKeySpec(receivedPublicKeyBytes));

            // 验证签名
            return verifySignature(data.getBytes(StandardCharsets.UTF_8), receivedPublicKey,
                    DatatypeConverter.parseHexBinary(signature));
        } catch (NoSuchAlgorithmException | InvalidKeyException | SignatureException | InvalidKeySpecException e) {
            e.printStackTrace();
        }
        return false;
    }

    private boolean verifySignature(byte[] data, PublicKey key, byte[] signature)
            throws NoSuchAlgorithmException, InvalidKeyException, SignatureException {
        Signature verifier = Signature.getInstance("SHA256withRSA", "BC");
        verifier.initVerify(key);
        verifier.update(data);
        return verifier.verify(signature);
    }

    private byte[] cloneByteArrayData(byte[] sourceArray) {
        byte[] destinationArray = new byte[sourceArray.length];
        System.arraycopy(sourceArray, 0, destinationArray, 0, sourceArray.length);
        return destinationArray;
    }
}

额外说明

  1. 如果你需要的是X.509证书(而非单纯密钥对),需要用CertificateGenerator生成证书,证书本身包含公钥、签名和元数据,此时可从证书中提取相关内容。
  2. 避免使用SHA1算法,安全性较低,推荐使用SHA256及以上的哈希算法。

内容的提问来源于stack exchange,提问作者Peter Penzov

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.12 14:35:53