-2

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.

ss_java
  • 1
  • 1
  • The information you posted is not enough to answer the question. Maybe you import the key wrong, `Ed25519PublicKeyParameters` expects the *raw* public key (32 bytes). Or there is a problem in the TypeScript code when generating the signature. – Topaco Jul 22 '23 at 16:47
  • 1
    So post the TypeScript/crypto code you use to generate the signature, including *full* (non-productive) test data: key pair, message, signature. Also complete the Java code and show how to import this data as `publicKeyBytes`, `message` and `decodedSign` into the Java code. – Topaco Jul 22 '23 at 16:51
  • Thank you for your prompt response. I have tried with raw public key too but I still get false. I will update my question with codes – ss_java Jul 24 '23 at 17:03
  • I can't reproduce the problem (using the raw public key for verification and the correct encodings for message, signature etc.). **Again**: Post **concrete** (non-productive) sample data for `signingKey`, `verifyKey`, `jsondata` and a suitably encoded `signature` for the TypeScript side, and likewise for ``, `` and `` for the Java side. That way the problem could be easily reproduced! Currently the problem is not reproducible and the post will probably be closed soon. The most likely candidates for a bug are encoding or key format issues. – Topaco Jul 24 '23 at 18:27

1 Answers1

0

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.

dave_thompson_085
  • 34,712
  • 6
  • 50
  • 70