Chaum 盲签名,在 JavaScript 中使用盲法并在 Java 中进行验证

Posted

技术标签:

【中文标题】Chaum 盲签名,在 JavaScript 中使用盲法并在 Java 中进行验证【英文标题】:Chaum blind signature with blinding in JavaScript and verifying in Java 【发布时间】:2022-01-16 08:25:30 【问题描述】:

我正在试验 Chaum 的盲签名,我想做的是在 javascript 中完成盲和非盲,并在 Java 中签名和验证(使用充气城堡)。对于 Java 方面,我的来源是 this,对于 JavaScript,我找到了 blind-signatures。我为 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);
    

上面将生成一个密钥对,将公共部分打印到标准输出,并从标准输入读取盲消息,然后读取非盲消息和签名。 对于 JavaScript (Node.js) 方面,我有这个:

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`);

这会读取公钥,生成盲消息,然后进行解盲。

Java 代码的验证部分失败,不知道为什么。有人知道吗?

【问题讨论】:

【参考方案1】:

NodeJS代码中用于盲签名的blind-signature库实现了here描述的过程:

BlindSignature.blind() 生成消息的 SHA256 哈希并确定盲消息 m' = m * re mod N. BlindSignature.sign() 计算盲签名 s' = (m')d mod N. BlindSignature.unblind() 确定非盲签名 s = s' * r-1 mod N. BlindSignature.verify() 解密非盲签名 (se) 并将结果与​​散列消息进行比较。如果两者相同,则验证成功。

在此过程中不发生填充。

在Java代码中,signConcealedMessage()中签名盲消息的实现在功能上与BlindSignature.sign()相同。 相比之下,Java 代码中的验证与上述过程不兼容,因为 Java 代码在验证过程中使用 PSS 作为填充。 例如,兼容的 Java 代码如下:

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

有了这个验证码就成功了。


似乎有一个用于盲签名的 RFC,它确实使用了 PSS 的扩展,请参阅draft-irtf-cfrg-rsa-blind-signatures。 BouncyCastle 还提供了盲签名的实现,参见例如RSABlindingEngine,由referenced Java library申请。

【讨论】:

以上是关于Chaum 盲签名,在 JavaScript 中使用盲法并在 Java 中进行验证的主要内容,如果未能解决你的问题,请参考以下文章

盲签名 blind signature

第7-5讲:盲签名算法在区块链中的应用

区块链与密码学第7-2讲:经典盲签名算法

区块链与密码学第7-4讲:经典盲签名算法

区块链与密码学第7-3讲:经典盲签名算法

JavaScript 的双盲进入(两次验证)?