Search code examples
javabouncycastlesignencryption-asymmetric

Sign/Verify Json using ED25519 keys with Bouncy Castle (Java)


I have generated following keys using ssh-keygen -t ed25519:

PRIVATE KEY:
-----BEGIN OPENSSH PRIVATE KEY-----
b3BlbnNzaC1r.....dFUQE=
-----END OPENSSH PRIVATE KEY-----

PUBLIC KEY:
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGWZbgFOWl97YZJ5Voljoz0f52jRO24jqLLcEhWtalo6 USER@LAPTOP

Iam trying to use the above keys to sign and verify json string, however, verifySignature always returning false.

    byte[] privateKeyBytes = Files.readAllBytes(Paths.get("C:\\workspace\\ssh\\ed25519"));
    Ed25519PrivateKeyParameters privateKey = new Ed25519PrivateKeyParameters(privateKeyBytes, 0);

    byte[] publicKeyBytes = Files.readAllBytes(Paths.get("C:\\workspace\\ssh\\ed25519.pub"));
    Ed25519PublicKeyParameters publicKey = new Ed25519PublicKeyParameters(publicKeyBytes, 0);

    byte[] message = "Json String".getBytes("utf-8");

    // create the signature
    Signer signer = new Ed25519Signer();
    signer.init(true, privateKey);
    signer.update(message, 0, message.length);
    byte[] signature = signer.generateSignature();

    // verify the signature
    Signer verifier = new Ed25519Signer();
    verifier.init(false, publicKey);
    verifier.update(message, 0, message.length);
    boolean verified = verifier.verifySignature(signature);

This verified is always false. Any idea...

Trying to follow Rebuild of ED25519 keys with Bouncy Castle (Java)


Solution

  • The private and public Ed25519 keys are each 32 bytes in size. They can be encapsulated in different formats. ssh-keygen generates both Ed25519 keys in OpenSSH format with the statement used. This format cannot be imported directly. However, BouncyCastle provides helper classes for this purpose.

    The private key can be loaded with a PemReader and imported into an Ed25519PrivateKeyParameters instance with OpenSSHPrivateKeyUtil.parsePrivateKeyBlob(). OpenSSHPublicKeyUtil.parsePublicKey() allows importing the Base64 decoded body of the public key into an Ed25519PublicKeyParameters instance.

    A key pair created with ssh-keygen -t ed25519 looks e.g. as follows:

    -----BEGIN OPENSSH PRIVATE KEY-----
    b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW
    QyNTUxOQAAACDbBP+5jmEtjh1JvhzVQsvvTC2IQrX6P68XzrV7ZbnGsQAAAKBgtw9/YLcP
    fwAAAAtzc2gtZWQyNTUxOQAAACDbBP+5jmEtjh1JvhzVQsvvTC2IQrX6P68XzrV7ZbnGsQ
    AAAEAaKYn22N1O78HfdG22C7hcG2HiezKMzlq4JTdgYG1DstsE/7mOYS2OHUm+HNVCy+9M
    LYhCtfo/rxfOtXtlucaxAAAAHHRmbG9yZXNfZHQwMUB0ZmxvcmVzX2R0MDEtUEMB
    -----END OPENSSH PRIVATE KEY-----
    

    and

    ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAINsE/7mOYS2OHUm+HNVCy+9MLYhCtfo/rxfOtXtlucax whatever
    

    Both keys can be imported and used for signing and verification as below:

    import java.io.FileReader;
    import java.nio.charset.StandardCharsets;
    import java.nio.file.Files;
    import java.nio.file.Paths;
    import java.util.Base64;
    import org.bouncycastle.crypto.Signer;
    import org.bouncycastle.crypto.params.AsymmetricKeyParameter;
    import org.bouncycastle.crypto.signers.Ed25519Signer;
    import org.bouncycastle.crypto.util.OpenSSHPrivateKeyUtil;
    import org.bouncycastle.crypto.util.OpenSSHPublicKeyUtil;
    import org.bouncycastle.util.io.pem.PemReader;
    
    ...
    
    byte[] message = "Json String".getBytes("utf-8");
    
    // Load private key
    AsymmetricKeyParameter privateKeyParameters = null;
    String pathPrivateKey = "<path to private key>";
    try (FileReader fileReader = new FileReader(pathPrivateKey);
         PemReader pemReader = new PemReader(fileReader)) {     
        byte[] privateKeyContent = pemReader.readPemObject().getContent();
        privateKeyParameters = OpenSSHPrivateKeyUtil.parsePrivateKeyBlob(privateKeyContent);
    }
        
    // Load public key  
    String pathPublicKey = "<path to public key>";      
    String publicKeyBody = new String(Files.readAllBytes(Paths.get(pathPublicKey)), StandardCharsets.UTF_8).split(" ")[1];
    AsymmetricKeyParameter publicKeyParameters = OpenSSHPublicKeyUtil.parsePublicKey(Base64.getDecoder().decode(publicKeyBody));
    
    // Sign
    Signer signer = new Ed25519Signer();
    signer.init(true, privateKeyParameters);
    signer.update(message, 0, message.length);
    byte[] signature = signer.generateSignature();
    
    // Verify
    Signer verifier = new Ed25519Signer();
    verifier.init(false, publicKeyParameters);
    verifier.update(message, 0, message.length);
    boolean verified = verifier.verifySignature(signature);
    
    System.out.println("Verification: " + verified); // Verification: true
    

    Note that you can import the raw keys, i.e. the 32 bytes keys, directly with Ed25519PrivateKeyParameters or Ed25519PublicKeyParameters. So an alternative solution would be to get the raw keys from the OpenSSH formatted keys and use those.