Search code examples
javacryptographykeystorelibsodiumed25519

Lazysodium keys in Java Keystore


I'm using the Lazysodium library (https://terl.gitbook.io/lazysodium/) to access libsodium from Java, specifically for Ed25519 digital signatures.

I'd also like to be able to store key pairs in a standard Java keystore. However, libsodium works with byte arrays rather than JCA Keypair instances so it isn't clear how to achieve this.

In particular, how can you:

  • Convert a byte array Ed25519 key as used with Libsodium into a JCA KeyPair?
  • Convert a JCA Keypair back into the appropriate byte array for libsodium?

Solution

  • The conversion between a raw private Ed25519 key and a java.security.PrivateKey is possible e.g. with BouncyCastle as follows:

    import java.security.KeyPair;
    import java.security.KeyPairGenerator;
    import java.security.PrivateKey;
    import java.security.KeyFactory;
    import java.security.spec.PKCS8EncodedKeySpec;
    import java.util.Base64;
    
    import org.bouncycastle.asn1.DEROctetString;
    import org.bouncycastle.asn1.edec.EdECObjectIdentifiers;
    import org.bouncycastle.asn1.pkcs.PrivateKeyInfo;
    import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
    import org.bouncycastle.crypto.params.Ed25519PrivateKeyParameters;
    import org.bouncycastle.crypto.util.PrivateKeyFactory;
    import org.bouncycastle.util.encoders.Hex;
    
    ...
    // Generate private test key
    PrivateKey privateKey = loadPrivateKey("Ed25519");
    byte[] privateKeyBytes = privateKey.getEncoded();
    System.out.println(Base64.getEncoder().encodeToString(privateKeyBytes)); // PKCS#8-key, check this in an ASN.1 Parser, e.g. https://lapo.it/asn1js/
    
    // java.security.PrivateKey to raw Ed25519 key
    Ed25519PrivateKeyParameters ed25519PrivateKeyParameters = (Ed25519PrivateKeyParameters)PrivateKeyFactory.createKey(privateKeyBytes);
    byte[] rawKey = ed25519PrivateKeyParameters.getEncoded();
    System.out.println(Hex.toHexString(rawKey)); // equals the raw 32 bytes key from the PKCS#8 key
    
    // Raw Ed25519 key to java.security.PrivateKey
    KeyFactory keyFactory = KeyFactory.getInstance("Ed25519");
    PrivateKeyInfo privateKeyInfo = new PrivateKeyInfo(new AlgorithmIdentifier(EdECObjectIdentifiers.id_Ed25519), new DEROctetString(rawKey));
    PKCS8EncodedKeySpec pkcs8EncodedKeySpec = new PKCS8EncodedKeySpec(privateKeyInfo.getEncoded());
    PrivateKey privateKeyReloaded = keyFactory.generatePrivate(pkcs8EncodedKeySpec);
    byte[] privateKeyBytesReloaded = privateKeyReloaded.getEncoded();
    System.out.println(Base64.getEncoder().encodeToString(privateKeyBytesReloaded)); // equals the PKCS#8 key from above
    

    where the private test key was generated with:

    private static PrivateKey loadPrivateKey(String algorithm) throws Exception {
        KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(algorithm);
        KeyPair keyPair = keyPairGenerator.generateKeyPair();
        return keyPair.getPrivate();
    }
    

    For X25519 it is analogous, replacing all Ed25519 with X25519.

    A conversion between a raw public X25519 key and a java.security.PublicKey (and vice versa) can be found here. For a raw public Ed25519 key, the procedure is analogous, where each X25519 has to be replaced by Ed25519.

    However, I have not tested whether Ed25519 or X25519 keys can be stored in the keystore.