Verify digital signature signed using ED25519

755 Views Asked by At

My task is to verify a digital signature signed using ED25519. The signature that was generated using typescript Crypto and I need to verify in Java. I am using BouncyCastle to verify and here is my code.

byte[] decodedSign = Base64.getDecoder().decode("<signature>");
byte[] message =  Base64.getDecoder().decode("<encoded message String">);
byte[] publicKeyBytes = Base64.getDecoder().decode("<public key>");

            Ed25519PublicKeyParameters publicKey = new Ed25519PublicKeyParameters(publicKeyBytes, 0);

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

            System.out.println("Verification: " + verified); // Verification: false

What I have is encoded message, encoded signature and encoded public key. I decoded all of these and used above code to verify the signature. I always get verified false. Please help if I am missing something or use different approach or this is not a right approach.

I tried the above code snippet and keep getting 'false'. I should get 'true'.

Update - Here is typescript to create key pair

    const { privateKey, publicKey } = crypto.generateKeyPairSync("ed25519");
    const signingKey = privateKey.export({ type: "pkcs8", format: "der"}).toString("hex");
    const verifyKey = publicKey.export({ type: "spki", format: "der" }).toString("hex");

Signature -

  const signature = crypto.sign(null, Buffer.from(JSON.stringify(jsondata)), privateKey);

Typescript dumps the encoded json data and the signature into one file and public key into another file.

1

There are 1 best solutions below

0
dave_thompson_085 On

The "spki" format in your posted code is not a 'raw' or 'bare' publickey, and whatever you referenced in a comment but didn't show is apparently wrong also.

Here is a correct way (using Bouncy LWAPI) plus 4 alternatives using JCA (with either the Bouncy provider, or the standard Oracle/OpenJDK provider in Java 15 up). I don't have/use typescript but your 'typescript' is really just nodejs. I output all the data from it in base64: classic base64 for data signature, and spki-format key, base64url for raw key because that's what JWK natively uses and Java can handle it just as easily. You could use hex or any other bit-preserving encoding as long as you are consistent.

# nodejs input
const crypto = require('crypto');
const { privateKey, publicKey } = crypto.generateKeyPairSync('ed25519');
const data = Buffer.from(JSON.stringify({example:'test data'}));
console.log( data.toString('base64') );
console.log( crypto.sign(null,data,privateKey).toString('base64') );
console.log( publicKey.export({format:'der',type:'spki'}).toString('base64') );
console.log( publicKey.export({format:'jwk'}).x ); // base64urlsafe
# output stored in file and input to java below
eyJleGFtcGxlIjoidGVzdCBkYXRhIn0=
g2L2cSrMskh+p62HJN48AGefLzaKf8TyN/6IzaaYyWUeGoBm3OvibHFjtAtXlD0pm/ldaQJq/LOhUtJcbhWYCQ==
MCowBQYDK2VwAyEA+XYOwM61UpixNFD89bo4OViD6HCm0G6DQnmSYbky5Hs=
-XYOwM61UpixNFD89bo4OViD6HCm0G6DQnmSYbky5Hs
// nopackage
import java.io.*;
import java.math.BigInteger;
import java.security.spec.*;
import java.security.*;
import java.util.Base64;
import org.bouncycastle.crypto.Signer;
import org.bouncycastle.crypto.params.Ed25519PublicKeyParameters;
import org.bouncycastle.crypto.signers.Ed25519Signer;
import org.bouncycastle.jcajce.spec.RawEncodedKeySpec;

public class SO76753558 {
    public static void main (String[] args) throws Exception {
        Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider());

        BufferedReader br = new BufferedReader (new InputStreamReader (System.in));
        byte[] data = Base64.getDecoder().decode(br.readLine()),
            sig = Base64.getDecoder().decode(br.readLine()),
            spki = Base64.getDecoder().decode(br.readLine()),
            bare = Base64.getUrlDecoder().decode(br.readLine());

        // Bouncy LWAPI
        {
            Signer v = new Ed25519Signer();
            v.init(false, new Ed25519PublicKeyParameters(bare));
            v.update(data, 0, data.length);
            System.out.println ("LWAPI:" + v.verifySignature(sig));
        }

        // standard algorithm-specific; requires Java 15 up and not very convenient
        {
            byte[] rev = new byte[bare.length];
            for( int i = 0; i<bare.length; i++ ){ rev[i] = bare[bare.length-1-i]; }
            boolean hibit = (rev[0]&0x80)>0; rev[0] &= ~0x80;
            EdECPublicKeySpec spec = new EdECPublicKeySpec(NamedParameterSpec.ED25519,
                new EdECPoint (hibit, new BigInteger(1,rev)) );
            KeyFactory f = KeyFactory.getInstance("Ed25519","SunEC");
            Signature v = Signature.getInstance("Ed25519","SunEC");
            v.initVerify(f.generatePublic(spec));
            v.update(data);
            System.out.println ("SunEC bare:"+ v.verify(sig));
        }
        // Bouncy algorithm-specific
        {
            KeyFactory f = KeyFactory.getInstance("Ed25519","BC");
            Signature v = Signature.getInstance("Ed25519","BC");
            v.initVerify(f.generatePublic(new RawEncodedKeySpec(bare)));
            v.update(data);
            System.out.println ("BC bare:"+ v.verify(sig));
        }
        // JCA generic; requires Java 15 up for SunEC
        for( String provider : new String[]{ "SunEC", "BC" } ){
            KeyFactory f = KeyFactory.getInstance("Ed25519",provider);
            Signature v = Signature.getInstance("Ed25519",provider);
            v.initVerify(f.generatePublic(new X509EncodedKeySpec(spki)));
            v.update(data);
            System.out.println (provider+" spki:"+ v.verify(sig));
        }
    }
}

Note that using new Ed25519PublicKeyParameters(spki) throws an exception telling you it is the wrong length, which if you know what you are doing is enough to identify the problem. Using the (spki,0) ctor instead suppresses the exception but the result is still completely and totally wrong; this is like putting masking tape over the light on your dashboard that indicates your brakes have failed -- it doesn't make anything better, it just prevents you from recognizing you are in danger.