Chaum 盲签名在 Java 脚本中盲化并在 Java 中验证
Chaum blind signature with blinding in JavaScript and verifying in Java
我正在试验 Chaum 的盲签名,我想做的是在 JavaScript 中完成致盲和解除致盲,并在 Java 中签名和验证(有充气城堡)。对于 Java 方,我的来源是 this, and for JavaScript, I found 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);
}
}
上面将生成一个密钥对,将 public 部分打印到标准输出并从标准输入读取盲化消息,然后是非盲化消息和签名。
对于 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}`);
这会读取 public 密钥,生成屏蔽消息,然后取消屏蔽。
Java代码的验证部分失败了,不知道为什么。有人知道吗?
blind-signature library used in the NodeJS code for blind signing implements the process described here:
BlindSignature.blind()
生成消息的SHA256哈希,确定盲消息m'=m*remodN.
BlindSignature.sign()
计算盲签名s'=(m')dmodN.
BlindSignature.unblind()
确定非盲签名s = s' * r-1 mod N.
BlindSignature.verify()
解密非盲签名(se)并将结果与哈希消息进行比较。如果两者相同,则验证成功。
在此过程中没有填充。
在Java代码中,signConcealedMessage()
中对盲消息签名的实现在功能上与BlindSignature.sign()
相同。
相比之下,Java代码中的验证与上述过程不兼容,因为Java代码在验证时使用了PSS作为padding。
例如,兼容的 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
, which is applied by the referenced Java library.
我正在试验 Chaum 的盲签名,我想做的是在 JavaScript 中完成致盲和解除致盲,并在 Java 中签名和验证(有充气城堡)。对于 Java 方,我的来源是 this, and for JavaScript, I found 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);
}
}
上面将生成一个密钥对,将 public 部分打印到标准输出并从标准输入读取盲化消息,然后是非盲化消息和签名。 对于 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}`);
这会读取 public 密钥,生成屏蔽消息,然后取消屏蔽。
Java代码的验证部分失败了,不知道为什么。有人知道吗?
blind-signature library used in the NodeJS code for blind signing implements the process described here:
BlindSignature.blind()
生成消息的SHA256哈希,确定盲消息m'=m*remodN.BlindSignature.sign()
计算盲签名s'=(m')dmodN.BlindSignature.unblind()
确定非盲签名s = s' * r-1 mod N.BlindSignature.verify()
解密非盲签名(se)并将结果与哈希消息进行比较。如果两者相同,则验证成功。
在此过程中没有填充。
在Java代码中,signConcealedMessage()
中对盲消息签名的实现在功能上与BlindSignature.sign()
相同。
相比之下,Java代码中的验证与上述过程不兼容,因为Java代码在验证时使用了PSS作为padding。
例如,兼容的 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
, which is applied by the referenced Java library.