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?
Advertisement
Answer
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.