Business
Jobs
  • About Us
  • Solutions
    • Job Postings
      Post your job and receive qualified candidates in 48h.
    • Candidate Assessments
      500+ technical and psychological tests, plus anti-fraud.
    • Headhunting
      Tailor-made executive search from start to finish.
    • Payroll + EOR
      Payroll dispersal and EOR across 15+ LATAM countries.
  • Pricing
  • Jobs

0

225
Views
Chaum blind signature with blinding in JavaScript and verifying in Java

I'm experimenting with Chaum's blind signature, and what I'm trying to do is have the blinding and un-blinding done in JavaScript, and signing and verifying in Java (with bouncy castle). For the Java side, my source is this, and for JavaScript, I found blind-signatures. I've created two small codes to play with, for the Java side:

package crypto;

import org.bouncycastle.crypto.AsymmetricCipherKeyPair;
import org.bouncycastle.crypto.digests.SHA256Digest;
import org.bouncycastle.crypto.engines.RSAEngine;
import org.bouncycastle.crypto.generators.RSAKeyPairGenerator;
import org.bouncycastle.crypto.params.AsymmetricKeyParameter;
import org.bouncycastle.crypto.params.RSAKeyGenerationParameters;
import org.bouncycastle.crypto.params.RSAKeyParameters;
import org.bouncycastle.crypto.signers.PSSSigner;
import org.bouncycastle.crypto.util.PrivateKeyInfoFactory;
import org.bouncycastle.crypto.util.SubjectPublicKeyInfoFactory;
import org.bouncycastle.openssl.jcajce.JcaPEMWriter;
import org.bouncycastle.util.io.pem.PemObject;

import java.io.IOException;
import java.io.StringWriter;
import java.math.BigInteger;
import java.security.SecureRandom;
import java.util.Base64;
import java.util.Scanner;

public class RsaConcealedMessageTest {
    public static void main(String[] args) {
        AsymmetricCipherKeyPair rsaKeyPair = generateKeyPair();
        Scanner userInput = new Scanner(System.in);

        try {
            printKeyPairPems(rsaKeyPair);

            // Producing signature on concealed message
            System.out.print("Concealed message (in base64)?");
            String concealedMessageBase64 = userInput.nextLine();
            byte[] concealedMessageBytes = Base64.getDecoder().decode(concealedMessageBase64);
            byte[] signatureOnConcealedMessage = signConcealedMessage(concealedMessageBytes, rsaKeyPair.getPrivate());
            System.out.println("Signature on concealed message (base64): " + Base64.getEncoder().encodeToString(signatureOnConcealedMessage));


            // Verifying revealed signature on revealed message
            System.out.print("Revealed message (in base64)?");
            String revealedMessageBase64 = userInput.nextLine();
            System.out.print("Revealed signature (in base64)?");
            String revealedSignatureBase64 = userInput.nextLine();

            byte[] revealedMessageBytes = Base64.getDecoder().decode(revealedMessageBase64);
            System.out.println("Revealed message is: " + new String(revealedMessageBytes));
            byte[] revealedSignatureBytes = Base64.getDecoder().decode(revealedSignatureBase64);

            PSSSigner signer = new PSSSigner(new RSAEngine(), new SHA256Digest(), 0);
            signer.init(false, rsaKeyPair.getPublic());

            signer.update(revealedMessageBytes, 0, revealedMessageBytes.length);
            boolean isVerified = signer.verifySignature(revealedSignatureBytes);
            System.out.println("Revealed signature is verified on revealed message: " + isVerified);

        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private static AsymmetricCipherKeyPair generateKeyPair() {
        RSAKeyPairGenerator generator = new RSAKeyPairGenerator();

        BigInteger publicExponent = new BigInteger("10001", 16);
        SecureRandom random = new SecureRandom();
        RSAKeyGenerationParameters keyGenParams = new RSAKeyGenerationParameters(
                publicExponent, random, 4096, 80
        );

        generator.init(keyGenParams);
        return generator.generateKeyPair();
    }

    private static void printKeyPairPems(AsymmetricCipherKeyPair keyPair) throws IOException {
        RSAKeyParameters publicKey = (RSAKeyParameters) keyPair.getPublic();
        byte[] publicKeyBytes = SubjectPublicKeyInfoFactory.createSubjectPublicKeyInfo(publicKey).getEncoded();
        printKeyPem("PUBLIC KEY", publicKeyBytes);

        RSAKeyParameters privateKey = (RSAKeyParameters) keyPair.getPrivate();
        byte[] privateKeyBytes = PrivateKeyInfoFactory.createPrivateKeyInfo(privateKey).getEncoded();
        printKeyPem("PRIVATE KEY", privateKeyBytes);
    }

    private static void printKeyPem(String keyType, byte[] keyBytes) throws IOException {
        PemObject pemObject = new PemObject(keyType, keyBytes);
        StringWriter keyStringWriter = new StringWriter();
        JcaPEMWriter pemWriter = new JcaPEMWriter(keyStringWriter);
        pemWriter.writeObject(pemObject);
        pemWriter.close();

        System.out.println(keyType + ": " + keyStringWriter.toString().replace("\n", ""));
    }

    private static byte[] signConcealedMessage(byte[] concealedMessage, AsymmetricKeyParameter privateKey) {
        RSAEngine engine = new RSAEngine();
        engine.init(true, privateKey);

        return engine.processBlock(concealedMessage, 0, concealedMessage.length);
    }
}

The above will generate a keypair, prints the public part to stdout and reads from stdin the blinded message and then the un-blinded message and signature. And for the JavaScript (Node.js) side, I have this:

const BigInteger = require('jsbn').BigInteger;
const BlindSignature = require('blind-signatures');
const NodeRSA = require('node-rsa');
const prompt = require('prompt-sync')();

const publicKeyInput = prompt("Public key in PEM?");
const publicKey = new NodeRSA(publicKeyInput);

// Concealing message
const message = "The quick brown fox jumps over the lazy dog! Hello World!";
const concealingResult = BlindSignature.blind(
    {
        message: message,
        N: publicKey.keyPair.n.toString(),
        E: publicKey.keyPair.e.toString(),
    }
);

const blindedHexStr = concealingResult.blinded.toString(16);
const blindedBuffer = Buffer.from(blindedHexStr, 'hex');

console.log(`\nConcealed message (base64): ${blindedBuffer.toString('base64')}`);
console.log(`\nr: ${concealingResult.r.toString(16)}`);

// Getting signature on concealed message and producing revealed signature
const signatureOnConcealedMessageBase64 = prompt("Signature on concealed message (base64)?");
const signatureOnConcealedMessageBuffer = Buffer.from(signatureOnConcealedMessageBase64, 'base64');
const signatureOnConcealedMessageHex = signatureOnConcealedMessageBuffer.toString('hex');
const signatureOnConcealedMessage = new BigInteger(signatureOnConcealedMessageHex, 16)

const signatureOnRevealedMessage = BlindSignature.unblind({
    signed: signatureOnConcealedMessage,
    N: publicKey.keyPair.n.toString(),
    r: concealingResult.r,
});

const revealedMessageBuffer = Buffer.from(message, 'utf8');
const revealedMessageBase64 = revealedMessageBuffer.toString('base64');
console.log(`\nRevealed message (base64): ${revealedMessageBase64}`);

const signatureOnRevealedMessageHex = signatureOnRevealedMessage.toString(16);
const signatureOnRevealedMessageBuffer = Buffer.from(signatureOnRevealedMessageHex, 'hex');
const signatureOnRevealedMessageBase64 = signatureOnRevealedMessageBuffer.toString('base64');
console.log(`\nSignature on revealed message (base64): ${signatureOnRevealedMessageBase64}`);

This reads the public key, generates the blinded message, and does the un-blinding.

The verification part of the Java code fails, and don't really know why. Does anyone have any idea?

over 4 years ago · Santiago Trujillo
1 answers
Answer question

0

The blind-signature library used in the NodeJS code for blind signing implements the process described here:

  • BlindSignature.blind() generates the SHA256 hash of the message and determines the blind message m' = m * re mod N.
  • BlindSignature.sign() calculates the blind signature s' = (m')d mod N.
  • BlindSignature.unblind() determines the unblind signature s = s' * r-1 mod N.
  • BlindSignature.verify() decrypts the unblind signature (se) and compares the result with the hashed message. If both are the same, the verification is successful.

No padding takes place in this process.

In the Java code, the implementation of signing the blind message in signConcealedMessage() is functionally identical to BlindSignature.sign().
In contrast, the verification in the Java code is incompatible with the above process because the Java code uses PSS as padding during verification.
A compatible Java code would be for instance:

RSAEngine engine = new RSAEngine();
engine.init(false, rsaKeyPair.getPublic());
byte[] signatureDecrypted = engine.processBlock(revealedSignatureBytes, 0, revealedSignatureBytes.length);  // calculates s^e          
byte[] messageHashed = MessageDigest.getInstance("SHA-256").digest(revealedMessageBytes);
System.out.println(Arrays.equals(messageHashed, signatureDecrypted)); // verification successfull, if s^e identical with the SHA256 hash of the message

With this code verification is successful.


There seems to be an RFC in the pipeline for blinded signing, which indeed uses an extension of PSS, see draft-irtf-cfrg-rsa-blind-signatures.
BouncyCastle also provides an implementation for blind signing, see e.g. RSABlindingEngine, which is applied by the referenced Java library.

over 4 years ago · Santiago Trujillo Report
Answer question
Find remote jobs

Discover the new way to find a job!

Top jobs
Top job categories
Business
Post vacancy Pricing Sales
Legal
Terms and conditions Privacy policy
© 2026 PeakU Inc. All Rights Reserved.
Andres GPT
Show me some job opportunities
There's an error!