Bouncy Castle PQC XMSS 签名:从 KeyStore 检索 SecretKey 后出现 NullPointerException
Bouncy Castle PQC XMSS signing: NullPointerException after retrieving SecretKey from KeyStore
重建时 JarSigner in order to work with PQC Signature Schemes such as XMSS provided by the JCA/JCE Provider BouncyCastle 我遇到了一个问题:似乎 XMSS PrivateKeys 在从磁盘存储和检索后不能再用于签署内容。但是 XMSSMT 和 SPHINCS PrivateKeys 可以。为什么?
最下面是供大家自行测试的源码。
您将需要 2 个外部库才能工作:
- 充气城堡Provider
- 用于生成 X.509 证书的 Bouncy Castle utility library
N.B.: 源代码是关于保存和检索密钥 in/from a KeyStore. But I also tried saving it simply to a file 这也不起作用。
我得到的错误:
java.security.SignatureException: java.lang.NullPointerException
at org.bouncycastle.pqc.jcajce.provider.xmss.XMSSSignatureSpi.engineSign(Unknown Source)
at java.base/java.security.Signature.sign(Signature.java:598)
at Main.sign(Main.java:91)
at Main.run(Main.java:71)
at Main.main(Main.java:22)
[主要]
public class Main {
public static String keyStorePath = "myKeyStore.keystore";
public static void main(String[] args) throws InterruptedException {
String[] algs = {"XMSS", "XMSS", "XMSSMT", "XMSSMT", "SPHINCS256"},
digests = {"SHA256", "SHA512", "SHA256", "SHA512", "SHA512"};
for (int i = 0; i < algs.length; i++){
try {
run(algs[i], digests[i]);
}catch (Exception ignore){
ignore.printStackTrace();
Thread.sleep(60); // Wait for print
}
}
}
public static void run(String alg, String digest) throws Exception{
String sigAlg = digest + "with" + alg,
provider = "BCPQC",
keyStoreProvider = "BC",
keyStoreAlias = sigAlg, // for readability
keyStorePassword = "password";
System.out.println("Running " + sigAlg + ".");
// Add providers
addProvider(new String[] {provider, keyStoreProvider});
// Generate KeyPairs
KeyPairGenerator kpg = KeyPairGenerator.getInstance(alg, provider);
initialize(kpg, alg, digest);
KeyPair myKp = kpg.generateKeyPair();
// Sign
sign(myKp.getPrivate(), sigAlg, provider, "Hello World!");
// Generate a self-signed certificate
X509Certificate cert = BCCertGen.generate(myKp.getPrivate(), myKp.getPublic(), 365, sigAlg, true);
// Load a KeyStore
KeyStore keyStore = KeyStore.getInstance("PKCS12", keyStoreProvider);
try {
keyStore.load(new FileInputStream(keyStorePath), keyStorePassword.toCharArray());
}catch (Exception ingore){
// If there is no KeyStore at @keyStorePath
// create an empty one
keyStore.load(null, keyStorePassword.toCharArray());
}
// Store the generated KeyPair with the Certificate in the KeyStore
keyStore.setKeyEntry(keyStoreAlias, myKp.getPrivate(), keyStorePassword.toCharArray(), new X509Certificate[] {cert});
keyStore.store(new FileOutputStream(keyStorePath), keyStorePassword.toCharArray());
// Load the stored PrivateKey from the KeyStore
keyStore.load(new FileInputStream(keyStorePath), keyStorePassword.toCharArray());
PrivateKey privateKey = (PrivateKey) keyStore.getKey(keyStoreAlias, keyStorePassword.toCharArray());
// Sign again
sign(privateKey, sigAlg, provider, "Hello World!");
}
public static void addProvider(String[] providers){
for (String provider : providers) {
switch (provider) {
case "BCPQC":
Security.addProvider(new BouncyCastlePQCProvider());
break;
case "BC":
Security.addProvider(new BouncyCastleProvider());
break;
}
}
}
public static void sign(PrivateKey pk, String sigAlg, String provider , String payload) throws Exception{
Signature signer = Signature.getInstance(sigAlg, provider);
signer.initSign(pk);
signer.update(payload.getBytes());
signer.sign();
System.out.println("Successfully signed");
}
public static void initialize(KeyPairGenerator kpg, String alg, String digest) throws Exception {
switch (alg) {
case "XMSS":
kpg.initialize(new XMSSParameterSpec(4, digest));
break;
case "XMSSMT":
kpg.initialize(new XMSSMTParameterSpec(4, 2, digest));
break;
case "SPHINCS256":
kpg.initialize(new SPHINCS256KeyGenParameterSpec());
break;
case "RSA":
kpg.initialize(new RSAKeyGenParameterSpec(2048, new BigInteger("5")));
break;
}
}
}
[BCCertGen]
public class BCCertGen {
public static String _country = "Westeros",
_organisation = "Targaryen",
_location = "Valyria",
_state = "Essos",
_issuer = "Some Trusted CA";
public BCCertGen(String country, String organisation, String location, String state, String issuer){
_country = country;
_organisation = organisation;
_location = location;
_state = state;
_issuer = issuer;
}
public static X509Certificate generate(PrivateKey privKey, PublicKey pubKey, int duration, String signAlg, boolean isSelfSigned) throws Exception{
Provider BC = new BouncyCastleProvider();
// distinguished name table.
X500NameBuilder builder = createStdBuilder();
// create the certificate
ContentSigner sigGen = new JcaContentSignerBuilder(signAlg).build(privKey);
X509v3CertificateBuilder certGen = new JcaX509v3CertificateBuilder(
new X500Name("cn="+_issuer), //Issuer
BigInteger.valueOf(1), //Serial
new Date(System.currentTimeMillis() - 50000), //Valid from
new Date((long)(System.currentTimeMillis() + duration*8.65*Math.pow(10,7))), //Valid to
builder.build(), //Subject
pubKey //Publickey to be associated with the certificate
);
X509Certificate cert = new JcaX509CertificateConverter().setProvider(BC).getCertificate(certGen.build(sigGen));
cert.checkValidity(new Date());
if (isSelfSigned) {
// check verifies in general
cert.verify(pubKey);
// check verifies with contained key
cert.verify(cert.getPublicKey());
}
ByteArrayInputStream bIn = new ByteArrayInputStream(cert.getEncoded());
CertificateFactory fact = CertificateFactory.getInstance("X.509", BC);
return (X509Certificate) fact.generateCertificate(bIn);
}
private static X500NameBuilder createStdBuilder() {
X500NameBuilder builder = new X500NameBuilder(RFC4519Style.INSTANCE);
builder.addRDN(RFC4519Style.c, _country);
builder.addRDN(RFC4519Style.o, _organisation);
builder.addRDN(RFC4519Style.l, _location);
builder.addRDN(RFC4519Style.st, _state);
return builder;
}
}
这是一个已知问题:https://github.com/bcgit/bc-java/issues/380
按照建议使用高于 161b01 的 beta 版本即可。
重建时 JarSigner in order to work with PQC Signature Schemes such as XMSS provided by the JCA/JCE Provider BouncyCastle 我遇到了一个问题:似乎 XMSS PrivateKeys 在从磁盘存储和检索后不能再用于签署内容。但是 XMSSMT 和 SPHINCS PrivateKeys 可以。为什么?
最下面是供大家自行测试的源码。 您将需要 2 个外部库才能工作:
- 充气城堡Provider
- 用于生成 X.509 证书的 Bouncy Castle utility library
N.B.: 源代码是关于保存和检索密钥 in/from a KeyStore. But I also tried saving it simply to a file 这也不起作用。
我得到的错误:
java.security.SignatureException: java.lang.NullPointerException
at org.bouncycastle.pqc.jcajce.provider.xmss.XMSSSignatureSpi.engineSign(Unknown Source)
at java.base/java.security.Signature.sign(Signature.java:598)
at Main.sign(Main.java:91)
at Main.run(Main.java:71)
at Main.main(Main.java:22)
[主要]
public class Main {
public static String keyStorePath = "myKeyStore.keystore";
public static void main(String[] args) throws InterruptedException {
String[] algs = {"XMSS", "XMSS", "XMSSMT", "XMSSMT", "SPHINCS256"},
digests = {"SHA256", "SHA512", "SHA256", "SHA512", "SHA512"};
for (int i = 0; i < algs.length; i++){
try {
run(algs[i], digests[i]);
}catch (Exception ignore){
ignore.printStackTrace();
Thread.sleep(60); // Wait for print
}
}
}
public static void run(String alg, String digest) throws Exception{
String sigAlg = digest + "with" + alg,
provider = "BCPQC",
keyStoreProvider = "BC",
keyStoreAlias = sigAlg, // for readability
keyStorePassword = "password";
System.out.println("Running " + sigAlg + ".");
// Add providers
addProvider(new String[] {provider, keyStoreProvider});
// Generate KeyPairs
KeyPairGenerator kpg = KeyPairGenerator.getInstance(alg, provider);
initialize(kpg, alg, digest);
KeyPair myKp = kpg.generateKeyPair();
// Sign
sign(myKp.getPrivate(), sigAlg, provider, "Hello World!");
// Generate a self-signed certificate
X509Certificate cert = BCCertGen.generate(myKp.getPrivate(), myKp.getPublic(), 365, sigAlg, true);
// Load a KeyStore
KeyStore keyStore = KeyStore.getInstance("PKCS12", keyStoreProvider);
try {
keyStore.load(new FileInputStream(keyStorePath), keyStorePassword.toCharArray());
}catch (Exception ingore){
// If there is no KeyStore at @keyStorePath
// create an empty one
keyStore.load(null, keyStorePassword.toCharArray());
}
// Store the generated KeyPair with the Certificate in the KeyStore
keyStore.setKeyEntry(keyStoreAlias, myKp.getPrivate(), keyStorePassword.toCharArray(), new X509Certificate[] {cert});
keyStore.store(new FileOutputStream(keyStorePath), keyStorePassword.toCharArray());
// Load the stored PrivateKey from the KeyStore
keyStore.load(new FileInputStream(keyStorePath), keyStorePassword.toCharArray());
PrivateKey privateKey = (PrivateKey) keyStore.getKey(keyStoreAlias, keyStorePassword.toCharArray());
// Sign again
sign(privateKey, sigAlg, provider, "Hello World!");
}
public static void addProvider(String[] providers){
for (String provider : providers) {
switch (provider) {
case "BCPQC":
Security.addProvider(new BouncyCastlePQCProvider());
break;
case "BC":
Security.addProvider(new BouncyCastleProvider());
break;
}
}
}
public static void sign(PrivateKey pk, String sigAlg, String provider , String payload) throws Exception{
Signature signer = Signature.getInstance(sigAlg, provider);
signer.initSign(pk);
signer.update(payload.getBytes());
signer.sign();
System.out.println("Successfully signed");
}
public static void initialize(KeyPairGenerator kpg, String alg, String digest) throws Exception {
switch (alg) {
case "XMSS":
kpg.initialize(new XMSSParameterSpec(4, digest));
break;
case "XMSSMT":
kpg.initialize(new XMSSMTParameterSpec(4, 2, digest));
break;
case "SPHINCS256":
kpg.initialize(new SPHINCS256KeyGenParameterSpec());
break;
case "RSA":
kpg.initialize(new RSAKeyGenParameterSpec(2048, new BigInteger("5")));
break;
}
}
}
[BCCertGen]
public class BCCertGen {
public static String _country = "Westeros",
_organisation = "Targaryen",
_location = "Valyria",
_state = "Essos",
_issuer = "Some Trusted CA";
public BCCertGen(String country, String organisation, String location, String state, String issuer){
_country = country;
_organisation = organisation;
_location = location;
_state = state;
_issuer = issuer;
}
public static X509Certificate generate(PrivateKey privKey, PublicKey pubKey, int duration, String signAlg, boolean isSelfSigned) throws Exception{
Provider BC = new BouncyCastleProvider();
// distinguished name table.
X500NameBuilder builder = createStdBuilder();
// create the certificate
ContentSigner sigGen = new JcaContentSignerBuilder(signAlg).build(privKey);
X509v3CertificateBuilder certGen = new JcaX509v3CertificateBuilder(
new X500Name("cn="+_issuer), //Issuer
BigInteger.valueOf(1), //Serial
new Date(System.currentTimeMillis() - 50000), //Valid from
new Date((long)(System.currentTimeMillis() + duration*8.65*Math.pow(10,7))), //Valid to
builder.build(), //Subject
pubKey //Publickey to be associated with the certificate
);
X509Certificate cert = new JcaX509CertificateConverter().setProvider(BC).getCertificate(certGen.build(sigGen));
cert.checkValidity(new Date());
if (isSelfSigned) {
// check verifies in general
cert.verify(pubKey);
// check verifies with contained key
cert.verify(cert.getPublicKey());
}
ByteArrayInputStream bIn = new ByteArrayInputStream(cert.getEncoded());
CertificateFactory fact = CertificateFactory.getInstance("X.509", BC);
return (X509Certificate) fact.generateCertificate(bIn);
}
private static X500NameBuilder createStdBuilder() {
X500NameBuilder builder = new X500NameBuilder(RFC4519Style.INSTANCE);
builder.addRDN(RFC4519Style.c, _country);
builder.addRDN(RFC4519Style.o, _organisation);
builder.addRDN(RFC4519Style.l, _location);
builder.addRDN(RFC4519Style.st, _state);
return builder;
}
}
这是一个已知问题:https://github.com/bcgit/bc-java/issues/380 按照建议使用高于 161b01 的 beta 版本即可。