-3

I use this code generate a private and a public certificate:

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

KeyPair keyPair = keyPairGenerator.generateKeyPair();

I tried to store the private and public key into DB using direct toString() like this keyPair.getPublic().toString(), but I get values into DB:

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

Which is not correct. What is the proper way to get the certificates as a String and store them in DB?

EDIT: I tried this:

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;
    }
}
16
  • 3
    With keyPair.getPublic().getEncoded() the public key is returned as DER encoded key in the common X.509/SPKI format (byte[]) which can be stored in binary or, if a string is preferred, Base64 encoded. Commented Aug 26, 2023 at 16:01
  • 3
    Similarly, keyPair.getPrivate().getEncoded() returns the private key DER encoded in the popular PKCS#8 format (byte[]). Note that keys and certificates are not the same thing. Commented Aug 26, 2023 at 16:05
  • 1
    As described above getEncoded() exports the keys as byte[]. You can store these in the DB either raw or you apply a binary-to-text encoding like Base64 and store the data as string. Base64 has an efficiency of 75% (three input bits become 4 output bits), so it increases the amount of data, which is why storing raw data is more efficient (see also e.g. binary data in database, blob vs compressed base64). Commented Sep 3, 2023 at 6:39
  • 2
    There are tens of posts on SO about importing a DER encoded private key in PKCS#8 format or a public key in X.509/SPKI format. Search for posts with PKCS8EncodedKeySpec and X509EncodedKeySpec. E.g. here. Commented Sep 3, 2023 at 10:50
  • 1
    Have you changed your requirement - you seem to be talking about certificates a lot now? Also certificate != public key. The former is a wrapper around the latter with a quite a bit of additional information. Even less are they to do with private keys Commented Sep 3, 2023 at 17:29

2 Answers 2

1
KeyPair kp = KeyPairGenerator.getInstance("DSA").generateKeyPair();
ByteArrayOutputStream bos = new ByteArrayOutputStream();
try (ObjectOutputStream out = new ObjectOutputStream(bos)){
  out.writeObject(kp);
}
String keyPair = Base64.getEncoder().encodeToString(bos.toByteArray());

You can now save keyPair in your db. This is more convenient as you'd have to separate the pair using getEncoded and that would be impractical with one db column. Obviously it would be more space-efficient as a 'binary' column type

A word of caution though: storing private keys in a database opens them to compromise. Secrets should almost never be stored directly - only as hash values that can be compared to human entries. Privileged operators should only be able to regenerate secrets, never to see them.

Sign up to request clarification or add additional context in comments.

2 Comments

Serializing a Java object in a database is not a good idea. What if deserialization fails with a future version of Java? What if you want to retriveve the data from another app not written in Java? You should just store the results of getEncoded().
Anything might fail for one future reason or another, but feel free to post an example using getEncoded() along with a procedure for putting both keys in the same column
1
+50

If at all you need to store it, it is easy to handle in textual PEM format. Be sure to employ all the best practices while storing sensitive data. Following is a code scribble that returns the key in a PEM format.

    public static void main(String[] args) throws NoSuchAlgorithmException, NoSuchProviderException, IOException {
    Security.addProvider(new BouncyCastleProvider());
    KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA", "BC");
    keyPairGenerator.initialize(2048, new SecureRandom());
    KeyPair keyPair = keyPairGenerator.generateKeyPair();
    System.out.println(getKeyPem(keyPair.getPublic(), "RSA PUBLIC KEY"));
}

private static String getKeyPem(Key key, String type) throws IOException {
    PemObject pemObject = new PemObject(type, key.getEncoded());
    ByteArrayOutputStream byteStream = new ByteArrayOutputStream();
    PemWriter pemWriter = new PemWriter(new OutputStreamWriter(byteStream));
    pemWriter.writeObject(pemObject);
    pemWriter.close();
    return new String(byteStream.toByteArray());
}

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.