Estoy experimentando con la firma ciega de Chaum, y lo que estoy tratando de hacer es cegar y descifrar en JavaScript, y firmar y verificar en Java (con castillo hinchable). Para el lado de Java, mi fuente es this , y para JavaScript, encontré firmas ciegas . He creado dos pequeños códigos para jugar, para el lado de Java:
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); } }Lo anterior generará un par de claves, imprimirá la parte pública en la salida estándar y leerá desde la entrada estándar el mensaje oculto y luego el mensaje no oculto y la firma. Y para el lado de JavaScript (Node.js), tengo esto:
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}`);Esto lee la clave pública, genera el mensaje oculto y lo descifra.
La parte de verificación del código Java falla y realmente no sé por qué. ¿Alguien tiene alguna idea?
La biblioteca de firma ciega utilizada en el código NodeJS para la firma ciega implementa el proceso que se describe aquí :
BlindSignature.blind() genera el hash SHA256 del mensaje y determina el mensaje ciego m' = m * r e mod N.BlindSignature.sign() calcula la firma ciega s' = (m') d mod N.BlindSignature.unblind() determina la firma no ciega s = s' * r -1 mod N.BlindSignature.verify() descifra la firma no ciega (s e ) y compara el resultado con el mensaje cifrado. Si ambos son iguales, la verificación es exitosa.No se produce relleno en este proceso.
En el código Java, la implementación de firmar el mensaje oculto en signConcealedMessage() es funcionalmente idéntica a BlindSignature.sign() .
Por el contrario, la verificación en el código Java es incompatible con el proceso anterior porque el código Java usa PSS como relleno durante la verificación.
Un código Java compatible sería, por ejemplo:
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 messageCon este código la verificación es exitosa.
Parece que hay un RFC en trámite para la firma ciega, que de hecho usa una extensión de PSS, consulte draft-irtf-cfrg-rsa-blind-signatures .
BouncyCastle también proporciona una implementación para la firma ciega, consulte, por ejemplo, RSABlindingEngine , que se aplica mediante la biblioteca de Java a la que se hace referencia .