Reputation: 78
I'm trying to make a java.security.PublicKey
from a byte[]
that is just a raw key—it's not in X.509 or ASN.1. In other languages, people are using libsodium, which just gives them a raw byte[]. I also need to be able to convert my java.security.PublicKey
to a byte[]
for them to consume. It seems that java.security
goes to great lengths to make that challenging, instead wanting me to rely on pre-encoded X509 SubjectPublicKeyInfo
. Am I missing a simple answer here?
Upvotes: 2
Views: 1744
Reputation: 49131
Libsodium, like all NaCl derivatives, uses Curve25519 for its crypto operations, i.e. X25519 for DH key exchange and Ed25519 for signing. Java supports X25519 since version 11, Ed25519 is supported since version 15. Both functionalities are provided by the SunEC provider.
For the import/export of raw keys it is easiest to use some BouncyCastle functions. The following code creates for X25519 (Java 11 and higher) a public test key in X.509 format and exports the raw 32 bytes key from it. Afterwards the raw key is imported into a X.509 key again:
import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.PublicKey;
import java.security.spec.X509EncodedKeySpec;
import java.util.Base64;
import org.bouncycastle.asn1.edec.EdECObjectIdentifiers;
import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
import org.bouncycastle.crypto.params.X25519PublicKeyParameters;
import org.bouncycastle.crypto.util.PublicKeyFactory;
import org.bouncycastle.util.encoders.Hex;
...
// Generate public test key in X.509 format
PublicKey publicKey = loadPublicKey("X25519");
byte[] publicKeyBytes = publicKey.getEncoded();
System.out.println(Base64.getEncoder().encodeToString(publicKeyBytes)); // X.509-key, check this in an ASN.1 Parser, e.g. https://lapo.it/asn1js/
// PublicKey to raw key
X25519PublicKeyParameters x25519PublicKeyParameters = (X25519PublicKeyParameters)PublicKeyFactory.createKey(publicKeyBytes);
byte[] rawKey = x25519PublicKeyParameters.getEncoded();
System.out.println(Hex.toHexString(rawKey)); // equals the raw 32 bytes key from the X.509 key
// Raw key to PublicKey
KeyFactory keyFactory = KeyFactory.getInstance("X25519");
SubjectPublicKeyInfo subjectPublicKeyInfo = new SubjectPublicKeyInfo(new AlgorithmIdentifier(EdECObjectIdentifiers.id_X25519), rawKey);
X509EncodedKeySpec x509EncodedKeySpec = new X509EncodedKeySpec(subjectPublicKeyInfo.getEncoded());
publicKey = keyFactory.generatePublic(x509EncodedKeySpec);
publicKeyBytes = publicKey.getEncoded();
System.out.println(Base64.getEncoder().encodeToString(publicKeyBytes)); // equals the X.509 key from above
where the X.509 test key is created with:
private static PublicKey loadPublicKey(String algorithm) throws Exception {
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(algorithm);
KeyPair keyPair = keyPairGenerator.generateKeyPair();
return keyPair.getPublic();
}
And analog for Ed25519 (Java 15 and higher).
X25519 and Ed25519 can also both be implemented solely with classes from BouncyCastle, e.g. org.bouncycastle.math.ec.rfc7748.X25519
(key agreement), org.bouncycastle.crypto.generators.X25519KeyPairGenerator
(key generation), org.bouncycastle.crypto.params.X25519PublicKeyParameters
(key container), and analog classes for Ed25519.
Upvotes: 7