Skip to content

Add JDK21 KEM API to HQC #2061

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 45 additions & 0 deletions prov/src/main/jdk21/org/bouncycastle/pqc/jcajce/provider/HQC.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package org.bouncycastle.pqc.jcajce.provider;

import org.bouncycastle.asn1.bc.BCObjectIdentifiers;
import org.bouncycastle.jcajce.provider.config.ConfigurableProvider;
import org.bouncycastle.jcajce.provider.util.AsymmetricAlgorithmProvider;
import org.bouncycastle.jcajce.provider.util.AsymmetricKeyInfoConverter;
import org.bouncycastle.pqc.jcajce.provider.hqc.HQCKeyFactorySpi;

public class HQC
{
private static final String PREFIX = "org.bouncycastle.pqc.jcajce.provider" + ".hqc.";

public static class Mappings
extends AsymmetricAlgorithmProvider
{
public Mappings()
{
}

public void configure(ConfigurableProvider provider)
{
provider.addAlgorithm("KeyFactory.HQC", PREFIX + "HQCKeyFactorySpi");
provider.addAlgorithm("KeyPairGenerator.HQC", PREFIX + "HQCKeyPairGeneratorSpi");

provider.addAlgorithm("KeyGenerator.HQC", PREFIX + "HQCKeyGeneratorSpi");

AsymmetricKeyInfoConverter keyFact = new HQCKeyFactorySpi();

provider.addAlgorithm("Cipher.HQC", PREFIX + "HQCCipherSpi$Base");
provider.addAlgorithm("Alg.Alias.Cipher." + BCObjectIdentifiers.pqc_kem_hqc, "HQC");

addCipherAlgorithm(provider, "HQC128", PREFIX + "HQCCipherSpi$HQC128", BCObjectIdentifiers.hqc128);
addCipherAlgorithm(provider, "HQC192", PREFIX + "HQCCipherSpi$HQC192", BCObjectIdentifiers.hqc192);
addCipherAlgorithm(provider, "HQC256", PREFIX + "HQCCipherSpi$HQC256", BCObjectIdentifiers.hqc256);

registerOid(provider, BCObjectIdentifiers.pqc_kem_hqc, "HQC", keyFact);
registerOid(provider, BCObjectIdentifiers.hqc128, "HQC", keyFact);
registerOid(provider, BCObjectIdentifiers.hqc192, "HQC", keyFact);
registerOid(provider, BCObjectIdentifiers.hqc256, "HQC", keyFact);

provider.addAlgorithm("Kem.HQC", PREFIX + "HQCKEMSpi");
provider.addAlgorithm("Alg.Alias.Kem." + BCObjectIdentifiers.pqc_kem_hqc, "HQC");
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
package org.bouncycastle.pqc.jcajce.provider.hqc;

import org.bouncycastle.jcajce.spec.KTSParameterSpec;

import org.bouncycastle.pqc.crypto.hqc.HQCKEMExtractor;
import org.bouncycastle.pqc.jcajce.provider.util.KdfUtil;

import javax.crypto.DecapsulateException;
import javax.crypto.KEMSpi;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import java.security.InvalidKeyException;
import java.util.Arrays;
import java.util.Objects;

public class HQCDecapsulatorSpi
implements KEMSpi.DecapsulatorSpi
{
BCHQCPrivateKey privateKey;
KTSParameterSpec parameterSpec;
HQCKEMExtractor kemExt;

public HQCDecapsulatorSpi(BCHQCPrivateKey privateKey, KTSParameterSpec parameterSpec)
{
this.privateKey = privateKey;
this.parameterSpec = parameterSpec;

this.kemExt = new HQCKEMExtractor(privateKey.getKeyParams());
}

@Override
public SecretKey engineDecapsulate(byte[] encapsulation, int from, int to, String algorithm) throws DecapsulateException
{
Objects.checkFromToIndex(from, to, engineSecretSize());
Objects.requireNonNull(algorithm, "null algorithm");
Objects.requireNonNull(encapsulation, "null encapsulation");

if (encapsulation.length != engineEncapsulationSize())
{
throw new DecapsulateException("incorrect encapsulation size");
}

// if algorithm is Generic then use parameterSpec to wrap key
if (!parameterSpec.getKeyAlgorithmName().equals("Generic") &&
algorithm.equals("Generic"))
{
algorithm = parameterSpec.getKeyAlgorithmName();
}

// check spec algorithm mismatch provided algorithm
if (!parameterSpec.getKeyAlgorithmName().equals("Generic") &&
!parameterSpec.getKeyAlgorithmName().equals(algorithm))
{
throw new UnsupportedOperationException(parameterSpec.getKeyAlgorithmName() + " does not match " + algorithm);
}

// Only use KDF when ktsParameterSpec is provided
// Considering any ktsParameterSpec with "Generic" as ktsParameterSpec not provided
boolean useKDF = parameterSpec.getKdfAlgorithm() != null;

byte[] secret = kemExt.extractSecret(encapsulation);
byte[] secretKey = Arrays.copyOfRange(KdfUtil.makeKeyBytes(parameterSpec, secret), from, to);

return new SecretKeySpec(secretKey, algorithm);
}

@Override
public int engineSecretSize()
{
return parameterSpec.getKeySize() / 8;
}

@Override
public int engineEncapsulationSize()
{
return kemExt.getEncapsulationLength();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
package org.bouncycastle.pqc.jcajce.provider.hqc;

import org.bouncycastle.crypto.SecretWithEncapsulation;
import org.bouncycastle.jcajce.spec.KTSParameterSpec;
import org.bouncycastle.pqc.crypto.hqc.HQCKEMGenerator;
import org.bouncycastle.pqc.jcajce.provider.util.KdfUtil;

import javax.crypto.KEM;
import javax.crypto.KEMSpi;
import javax.crypto.spec.SecretKeySpec;
import java.security.InvalidKeyException;
import java.security.SecureRandom;
import java.util.Arrays;
import java.util.Objects;

public class HQCEncapsulatorSpi
implements KEMSpi.EncapsulatorSpi
{
private final BCHQCPublicKey publicKey;
private final KTSParameterSpec parameterSpec;
private final HQCKEMGenerator kemGen;

public HQCEncapsulatorSpi(BCHQCPublicKey publicKey, KTSParameterSpec parameterSpec, SecureRandom random)
{
this.publicKey = publicKey;
this.parameterSpec = parameterSpec;

this.kemGen = new HQCKEMGenerator(random);
}

@Override
public KEM.Encapsulated engineEncapsulate(int from, int to, String algorithm)
{
Objects.checkFromToIndex(from, to, engineSecretSize());
Objects.requireNonNull(algorithm, "null algorithm");

// if algorithm is Generic then use parameterSpec to wrap key
if (!parameterSpec.getKeyAlgorithmName().equals("Generic") &&
algorithm.equals("Generic"))
{
algorithm = parameterSpec.getKeyAlgorithmName();
}

// check spec algorithm mismatch provided algorithm
if (!parameterSpec.getKeyAlgorithmName().equals("Generic") &&
!parameterSpec.getKeyAlgorithmName().equals(algorithm))
{
throw new UnsupportedOperationException(parameterSpec.getKeyAlgorithmName() + " does not match " + algorithm);
}

// Only use KDF when ktsParameterSpec is provided
// Considering any ktsParameterSpec with "Generic" as ktsParameterSpec not provided
boolean useKDF = parameterSpec.getKdfAlgorithm() != null;

SecretWithEncapsulation secEnc = kemGen.generateEncapsulated(publicKey.getKeyParams());

byte[] encapsulation = secEnc.getEncapsulation();
byte[] secret = secEnc.getSecret();
byte[] secretKey = Arrays.copyOfRange(KdfUtil.makeKeyBytes(parameterSpec, secret), from, to);

return new KEM.Encapsulated(new SecretKeySpec(secretKey, algorithm), encapsulation, null); //TODO: DER encoding for params
}

@Override
public int engineSecretSize()
{
return parameterSpec.getKeySize() / 8;
}

@Override
public int engineEncapsulationSize()
{
//TODO: Maybe make parameterSet public or add getEncapsulationSize() in HQCKEMGenerator.java
switch (publicKey.getKeyParams().getParameters().getName())
{
case "HQC-128":
return 128;
case "HQC-192":
return 192;
case "HQC-256":
return 256;
default:
return -1;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package org.bouncycastle.pqc.jcajce.provider.hqc;

import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.SecureRandom;
import java.security.spec.AlgorithmParameterSpec;

import javax.crypto.KEMSpi;

import org.bouncycastle.jcajce.spec.KTSParameterSpec;

public class HQCKEMSpi
implements KEMSpi {

@Override
public EncapsulatorSpi engineNewEncapsulator(PublicKey publicKey, AlgorithmParameterSpec spec,
SecureRandom secureRandom)
throws InvalidAlgorithmParameterException, InvalidKeyException {
if (!(publicKey instanceof BCHQCPublicKey)) {
throw new InvalidKeyException("unsupported key");
}
if (spec == null) {
// Do not wrap key, no KDF
spec = new KTSParameterSpec.Builder("Generic", 256).withNoKdf().build();
}
if (!(spec instanceof KTSParameterSpec)) {
throw new InvalidAlgorithmParameterException("HQC can only accept KTSParameterSpec");
}
if (secureRandom == null) {
secureRandom = new SecureRandom();
}
return new HQCEncapsulatorSpi((BCHQCPublicKey) publicKey, (KTSParameterSpec) spec, secureRandom);
}

@Override
public DecapsulatorSpi engineNewDecapsulator(PrivateKey privateKey, AlgorithmParameterSpec spec)
throws InvalidAlgorithmParameterException, InvalidKeyException {
if (!(privateKey instanceof BCHQCPrivateKey)) {
throw new InvalidKeyException("unsupported key");
}
if (spec == null) {
// Do not unwrap key, no KDF
spec = new KTSParameterSpec.Builder("Generic", 256).withNoKdf().build();
}
if (!(spec instanceof KTSParameterSpec)) {
throw new InvalidAlgorithmParameterException("HQC can only accept KTSParameterSpec");
}
return new HQCDecapsulatorSpi((BCHQCPrivateKey) privateKey, (KTSParameterSpec) spec);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ public static Test suite()
suite.addTestSuite(NTRUKEMTest.class);
suite.addTestSuite(SNTRUPrimeKEMTest.class);
suite.addTestSuite(MLKEMTest.class);
suite.addTestSuite(HQCTest.class);
return suite;
}
}
Loading