Authenticate the digital signature created with the ED25519 algorithm

My current task involves verifying a digital signature that was signed using ED25519 with typescript Crypto, but I need to verify it in Java using BouncyCastle. The code snippet I am using for verification is as follows:

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

The information I have includes an encoded message, encoded signature, and encoded public key. Despite decoding all of these inputs and running the above code snippet, I consistently receive a 'false' verification result. Any assistance on potential missing elements or alternate approaches would be greatly appreciated.

After attempting the provided code snippet multiple times without success, I continue to receive a 'false' verification instead of the expected 'true' result.

Update - Below is the typescript code used to create a 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");

Generated Signature:

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

Typescript combines the encoded JSON data with the signature in one file and the public key in another file.

Answer №1

Your posted code includes the "spki" format, which is not a 'raw' or 'bare' public key. Whatever was referenced in your comment but not shown appears to be incorrect as well.

Below are correct ways to handle this using Bouncy LWAPI and 4 alternatives with JCA (utilizing either the Bouncy provider or the standard Oracle/OpenJDK provider in Java 15 and up). Even if you don't use typescript, your 'typescript' essentially translates to nodejs. I have presented all data in base64: traditional base64 for data signature, spki-format key, and base64url for raw key - the latter being natively used by JWK and easily handled by Java. You could opt for hex or any other encoding method as long as you maintain consistency.

# 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 utilizing

new Ed25519PublicKeyParameters(spki)
will throw an exception indicating it is the wrong length, serving as a warning sign. Instead, applying the (spki,0) constructor may suppress the exception, but the outcome remains inaccurate. This is akin to masking tape over a faulty brake light on your dashboard - it doesn't alleviate the issue, just blinds you to the impending danger.

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Using an Angular interface for an HTTP request: The statusText message reads as "Error: Unable to Determine."

I've been working on calling an API as an example in Angular using an interface. The API I'm trying to access is located at https://jsonplaceholder.typicode.com/posts. Unfortunately, I encountered the following error message: ERROR HttpErrorResp ...

Unable to incorporate node-vibrant into Angular 7 project

Currently facing some challenges while attempting to integrate node-vibrant into my Angular 7 project: -Successfully imported with import * as Vibrant from 'node-vibrant';, but encountering a warning in VS Code: Module '"/Users/xxxx/Docume ...

The object is not a valid function

Within this class object, I have an instance of a class that I am unable to call its functions within. Despite the IDE allowing me to call the getPoistionDiagram function: export class NodeW { childrenIds: string[]; diagram?: { coordinates: { ...

Can you explain the distinction between 'rxjs/operators' and 'rxjs/internal/operators'?

When working on an Angular project, I often need to import functionalities like the Observable or switchMap operator. In such cases, there are two options available: import { switchMap } from 'rxjs/operators'; or import { switchMap } from ' ...

Is it wise to refrain from retrieving the size of a collection within a loop?

After reading a post on geeksforgeeks, I came across tip number 3 which suggests defining size beforehand in a for loop and calling it in the comparator for better performance. Initially, this advice seemed reasonable to me, assuming that calling the .size ...

The journey of communication: uncovering the essence of @input between parent and

I'm diving into Angular and currently working on the @Input phase. Within my main app, there's a child component. Inside app.component.ts, I've declared a test variable that I wish to pass from app.component.ts to child.component.ts. // ap ...

Ways to resolve NullPointerException in an array when transitioning to another method

I am currently developing a chess game in Java that involves creating a 2D array of objects to represent the chessboard. Initially, when the array is created, it contains all the necessary objects. However, upon calling a different method from another obje ...

Set the value obtained from a resolved promise to a mutable reference object in a React component

I am in the process of developing a random movie generator. I am utilizing an external API to retrieve a list of movies and then selecting one randomly from the returned data. The current implementation is as follows: export default function Page() { con ...

Android NTLM is receiving an HTTP/1.1 401 Unauthorized status code

Attempting to retrieve data from a SharePoint server, Below is the code snippet: DefaultHttpClient httpclient = new DefaultHttpClient(); httpclient.getAuthSchemes().register("ntlm", new NTLMSchemeFactory()); httpclient.getCredentialsProvider( ...

Java Modelmapper: Converter remains unused

I am currently developing a Java program that syncs Active Directory users with users in my database. To achieve this, I am utilizing modelmapper which is proving to be efficient and fast. However, I have added a converter to my mapping configuration. The ...

FIREBASE_AUTHCHECK_DEBUG: Error - 'self' is undefined in the debug reference

I'm encountering an issue while trying to implement Firebase Appcheck in my Next.js Typescript project. firebase.ts const fbapp = initializeApp(firebaseConfig); if (process.env.NODE_ENV === "development") { // @ts-ignore self.FIREBASE_ ...

The Heroku deployment was a success, however, MongoDB failed to save any data

For the development of our app, we chose to use Heroku as our hosting platform and utilized Java and Dagger for building it. Despite successfully deploying the app on Heroku, we noticed that no data from the Java code for testing was being stored in MongoD ...

Unique loading animations are assigned to each individual page within the Next.js framework

Is there a way to have unique loading animations for each of my website pages during the loading process? How can I achieve this? I've attempted to put the loading component on the page component directly, but it doesn't seem to work: //Page com ...

PHP API encountering a syntax errororEncountering

I'm in the process of developing an Android app and I require a PHP API to establish communication with an SQL Database. Encountering an error while trying to parse JSON at getJarrayFromString(); Upon logging the error, this is what was uncovered: ...

What is the process for linking read-only methods to Redux object instances?

Let's say I have a "user" object stored in redux, with fields for first name and last name (interface User { firstName : string, lastName : string} if using typescript). After retrieving a user from redux, I want to obtain the full name of the user by ...

What is the process for destructuring an interface in TypeScript?

My request is as follows: const listCompartmentsRequest: identity.requests.ListCompartmentsRequest = { compartmentId: id, compartmentIdInSubtree: true, } I want to shorten the long line identity.requests.ListCompartmentsRequest. I'm looking for ...

The Java Optional class can be used to handle null JSON fields

Utilizing JsonNode objects from the Jackson library to handle json responses, the input could be structured as follows: { "a": "test", "b": true } However, there are instances where the b field might be absent, resulting in a structure like this: ...

Is it possible to identify unauthorized utilization of web APIs within TypeScript?

Recently, I encountered an issue while using the URLSearchParams.size in my code. To my surprise, it didn't work on Safari as expected. Checking MDN's browser compatibility table revealed that Safari version 16.6 does not support this feature, un ...

Issue with directive implementation of regex test as attribute - validations in typescript and angular

I am currently working on a project to create a module with custom validation directives. These validations are implemented using regular expressions (regex). The specific error I encountered is: Error: [$injector:unpr] Unknown provider: REG_EXPProvid ...

Adding items to the array is only effective when done within the loop

My approach involves retrieving data from an API using axios, organizing it within a function named "RefractorData()," and then pushing it onto an existing array. However, I have encountered a problem where the array gets populated within a forEach loop, a ...