JcaMacFactory.java
/*
* GordianKnot: Security Suite
* Copyright 2012-2026. Tony Washer
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy
* of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package io.github.tonywasher.joceanus.gordianknot.impl.jca;
import io.github.tonywasher.joceanus.gordianknot.api.base.GordianException;
import io.github.tonywasher.joceanus.gordianknot.api.base.GordianKeySpec;
import io.github.tonywasher.joceanus.gordianknot.api.base.GordianLength;
import io.github.tonywasher.joceanus.gordianknot.api.cipher.GordianSymKeySpec;
import io.github.tonywasher.joceanus.gordianknot.api.cipher.GordianSymKeyType;
import io.github.tonywasher.joceanus.gordianknot.api.digest.GordianDigestSpec;
import io.github.tonywasher.joceanus.gordianknot.api.key.GordianKeyGenerator;
import io.github.tonywasher.joceanus.gordianknot.api.mac.GordianMacSpec;
import io.github.tonywasher.joceanus.gordianknot.api.mac.GordianMacType;
import io.github.tonywasher.joceanus.gordianknot.impl.core.base.GordianBaseData;
import io.github.tonywasher.joceanus.gordianknot.impl.core.base.GordianBaseFactory;
import io.github.tonywasher.joceanus.gordianknot.impl.core.exc.GordianCryptoException;
import io.github.tonywasher.joceanus.gordianknot.impl.core.exc.GordianDataException;
import io.github.tonywasher.joceanus.gordianknot.impl.core.mac.GordianCoreMacFactory;
import javax.crypto.KeyGenerator;
import javax.crypto.Mac;
import java.security.NoSuchAlgorithmException;
import java.util.HashMap;
import java.util.Map;
/**
* Jca Cipher Factory.
*/
public class JcaMacFactory
extends GordianCoreMacFactory {
/**
* CMAC algorithm name.
*/
private static final String CMAC_ALGORITHM = "CMAC";
/**
* GOST algorithm name.
*/
private static final String GOST_ALGORITHM = "GOST28147";
/**
* ZUC-128 algorithm name.
*/
private static final String ZUC256_ALGORITHM = "ZUC-256";
/**
* KeyGenerator Cache.
*/
private final Map<GordianKeySpec, JcaKeyGenerator<? extends GordianKeySpec>> theCache;
/**
* Constructor.
*
* @param pFactory the factory
*/
JcaMacFactory(final GordianBaseFactory pFactory) {
/* Initialise underlying class */
super(pFactory);
/* Create the cache */
theCache = new HashMap<>();
}
@Override
@SuppressWarnings("unchecked")
public <T extends GordianKeySpec> GordianKeyGenerator<T> getKeyGenerator(final T pMacSpec) throws GordianException {
/* Look up in the cache */
JcaKeyGenerator<T> myGenerator = (JcaKeyGenerator<T>) theCache.get(pMacSpec);
if (myGenerator == null) {
/* Check validity of MacSpec */
checkMacSpec(pMacSpec);
/* Create the new generator */
final String myAlgorithm = getMacSpecAlgorithm((GordianMacSpec) pMacSpec);
final KeyGenerator myJavaGenerator = getJavaKeyGenerator(myAlgorithm);
myGenerator = new JcaKeyGenerator<>(getFactory(), pMacSpec, myJavaGenerator);
/* Add to cache */
theCache.put(pMacSpec, myGenerator);
}
return myGenerator;
}
@Override
public JcaMac createMac(final GordianMacSpec pMacSpec) throws GordianException {
/* Check validity of MacSpec */
checkMacSpec(pMacSpec);
/* Create Mac */
final Mac myJavaMac = getJavaMac(pMacSpec);
return new JcaMac(getFactory(), pMacSpec, myJavaMac);
}
@Override
protected boolean validMacType(final GordianMacType pMacType) {
switch (pMacType) {
case BLAKE2:
case BLAKE3:
case KALYNA:
case CBCMAC:
case CFBMAC:
return false;
default:
return super.validMacType(pMacType);
}
}
/**
* Create the BouncyCastle MAC via JCA.
*
* @param pMacSpec the MacSpec
* @return the MAC
* @throws GordianException on error
*/
private Mac getJavaMac(final GordianMacSpec pMacSpec) throws GordianException {
switch (pMacSpec.getMacType()) {
case HMAC:
case GMAC:
case CMAC:
case POLY1305:
case SKEIN:
case KUPYNA:
case SIPHASH:
case GOST:
case ZUC:
case KMAC:
return getJavaMac(getMacSpecAlgorithm(pMacSpec));
case VMPC:
return getJavaMac("VMPC-MAC");
default:
throw new GordianDataException(GordianBaseData.getInvalidText(pMacSpec));
}
}
/**
* Create the BouncyCastle MAC via JCA.
*
* @param pAlgorithm the Algorithm
* @return the MAC
* @throws GordianException on error
*/
private static Mac getJavaMac(final String pAlgorithm) throws GordianException {
/* Protect against exceptions */
try {
/* Return a MAC for the algorithm */
return Mac.getInstance(pAlgorithm, JcaProvider.BCPROV);
/* Catch exceptions */
} catch (NoSuchAlgorithmException e) {
/* Throw the exception */
throw new GordianCryptoException("Failed to create Mac", e);
}
}
/**
* Create the BouncyCastle KeyGenerator via JCA.
*
* @param pAlgorithm the Algorithm
* @return the KeyGenerator
* @throws GordianException on error
*/
private static KeyGenerator getJavaKeyGenerator(final String pAlgorithm) throws GordianException {
/* Protect against exceptions */
try {
/* Massage the keyGenerator name */
String myAlgorithm = pAlgorithm;
/* CMAC generators use the symKeyGenerator */
if (myAlgorithm.endsWith(CMAC_ALGORITHM)) {
myAlgorithm = myAlgorithm.substring(0, myAlgorithm.length() - CMAC_ALGORITHM.length());
}
/* GOST generators use the GOST28147 key generator */
if (myAlgorithm.startsWith(GOST_ALGORITHM)) {
myAlgorithm = GOST_ALGORITHM;
}
/* ZUC-256 generators use the ZUC-256 key generator */
if (myAlgorithm.startsWith(ZUC256_ALGORITHM)) {
myAlgorithm = ZUC256_ALGORITHM;
}
/* Return a KeyGenerator for the algorithm */
return KeyGenerator.getInstance(myAlgorithm, JcaProvider.BCPROV);
/* Catch exceptions */
} catch (NoSuchAlgorithmException e) {
/* Throw the exception */
throw new GordianCryptoException("Failed to create KeyGenerator", e);
}
}
/**
* Obtain the MacSpec Key algorithm.
*
* @param pMacSpec the MacSpec
* @return the Algorithm
* @throws GordianException on error
*/
private static String getMacSpecAlgorithm(final GordianMacSpec pMacSpec) throws GordianException {
switch (pMacSpec.getMacType()) {
case HMAC:
return getHMacAlgorithm(pMacSpec.getDigestSpec());
case GMAC:
return getGMacAlgorithm(pMacSpec.getSymKeySpec());
case CMAC:
return getCMacAlgorithm(pMacSpec.getSymKeySpec());
case POLY1305:
return getPoly1305Algorithm(pMacSpec.getSymKeySpec());
case SKEIN:
return getSkeinMacAlgorithm(pMacSpec.getDigestSpec());
case KUPYNA:
return getKupynaMacAlgorithm(pMacSpec.getDigestSpec());
case ZUC:
return getZucMacAlgorithm(pMacSpec);
case SIPHASH:
return pMacSpec.toString();
case KMAC:
return pMacSpec.getMacType().toString() + pMacSpec.getDigestSpec().getDigestState();
case GOST:
return "GOST28147MAC";
case VMPC:
return "VMPC-KSA3";
default:
throw new GordianDataException(GordianBaseData.getInvalidText(pMacSpec));
}
}
/**
* Return the associated HMac algorithm.
*
* @param pDigestSpec the digestSpec
* @return the algorithm
* @throws GordianException on error
*/
private static String getHMacAlgorithm(final GordianDigestSpec pDigestSpec) throws GordianException {
return "HMac" + JcaDigest.getHMacAlgorithm(pDigestSpec);
}
/**
* Obtain the GMAC algorithm.
*
* @param pKeySpec the symmetric keySpec
* @return the algorithm
* @throws GordianException on error
*/
private static String getGMacAlgorithm(final GordianSymKeySpec pKeySpec) throws GordianException {
/* Return algorithm name */
return JcaCipherFactory.getSymKeyAlgorithm(pKeySpec) + "GMAC";
}
/**
* Obtain the GMAC algorithm.
*
* @param pKeySpec the symmetric keySpec
* @return the algorithm
* @throws GordianException on error
*/
private static String getCMacAlgorithm(final GordianSymKeySpec pKeySpec) throws GordianException {
/* Return algorithm name */
return JcaCipherFactory.getSymKeyAlgorithm(pKeySpec) + CMAC_ALGORITHM;
}
/**
* Obtain the Poly1305 algorithm.
*
* @param pKeySpec the symmetric keySpec
* @return the algorithm
* @throws GordianException on error
*/
private static String getPoly1305Algorithm(final GordianSymKeySpec pKeySpec) throws GordianException {
/* Return algorithm name */
return pKeySpec == null
? "POLY1305"
: "POLY1305-" + JcaCipherFactory.getSymKeyAlgorithm(pKeySpec);
}
/**
* Obtain the Skein MAC algorithm.
*
* @param pSpec the digestSpec
* @return the algorithm
*/
private static String getSkeinMacAlgorithm(final GordianDigestSpec pSpec) {
return "Skein-MAC-"
+ pSpec.getDigestState()
+ '-'
+ pSpec.getDigestLength();
}
/**
* Obtain the Skein MAC algorithm.
*
* @param pSpec the digestSpec
* @return the algorithm
*/
private static String getZucMacAlgorithm(final GordianMacSpec pSpec) {
final String myKeyLen = Integer.toString(pSpec.getKeyLength().getLength());
final String myMacLen = Integer.toString(pSpec.getMacLength().getLength());
final StringBuilder myBuilder = new StringBuilder();
myBuilder.append("ZUC-")
.append(myKeyLen);
if (GordianLength.LEN_256 == pSpec.getKeyLength()) {
myBuilder.append('-')
.append(myMacLen);
}
return myBuilder.toString();
}
/**
* Obtain the Kupyna MAC algorithm.
*
* @param pSpec the digestSpec
* @return the algorithm
* @throws GordianException on error
*/
private static String getKupynaMacAlgorithm(final GordianDigestSpec pSpec) throws GordianException {
/* For some reason this is accessed as HMAC !!! */
return getHMacAlgorithm(pSpec);
}
@Override
protected boolean validCMacSymKeySpec(final GordianSymKeySpec pKeySpec) {
switch (pKeySpec.getSymKeyType()) {
case AES:
case DESEDE:
case BLOWFISH:
case SHACAL2:
case THREEFISH:
case SEED:
case SM4:
return super.validCMacSymKeySpec(pKeySpec);
default:
return false;
}
}
@Override
protected boolean validGMacSymKeySpec(final GordianSymKeySpec pKeySpec) {
switch (pKeySpec.getSymKeyType()) {
case KUZNYECHIK:
case KALYNA:
return false;
default:
return super.validGMacSymKeySpec(pKeySpec);
}
}
@Override
protected boolean validPoly1305SymKeySpec(final GordianSymKeySpec pKeySpec) {
if (GordianSymKeyType.KALYNA.equals(pKeySpec.getSymKeyType())) {
return false;
}
return super.validPoly1305SymKeySpec(pKeySpec);
}
}