JcaEncryptor.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.GordianLength;
import io.github.tonywasher.joceanus.gordianknot.api.digest.GordianDigestSpec;
import io.github.tonywasher.joceanus.gordianknot.api.digest.GordianDigestType;
import io.github.tonywasher.joceanus.gordianknot.api.encrypt.GordianEncryptorSpec;
import io.github.tonywasher.joceanus.gordianknot.api.encrypt.GordianSM2EncryptionSpec;
import io.github.tonywasher.joceanus.gordianknot.api.keypair.GordianKeyPair;
import io.github.tonywasher.joceanus.gordianknot.impl.core.base.GordianBaseFactory;
import io.github.tonywasher.joceanus.gordianknot.impl.core.encrypt.GordianCoreEncryptor;
import io.github.tonywasher.joceanus.gordianknot.impl.core.exc.GordianCryptoException;
import io.github.tonywasher.joceanus.gordianknot.impl.jca.JcaKeyPair.JcaPrivateKey;
import io.github.tonywasher.joceanus.gordianknot.impl.jca.JcaKeyPair.JcaPublicKey;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;
/**
* JCA Encryptor classes.
*/
public final class JcaEncryptor {
/**
* Error string.
*/
private static final String ERROR_INIT = "Failed to initialise";
/**
* Private constructor.
*/
private JcaEncryptor() {
}
/**
* Block Encryptor.
*/
public static class JcaBlockEncryptor
extends GordianCoreEncryptor {
/**
* The underlying encryptor.
*/
private final Cipher theEncryptor;
/**
* Constructor.
*
* @param pFactory the factory
* @param pSpec the encryptorSpec
* @throws GordianException on error
*/
JcaBlockEncryptor(final GordianBaseFactory pFactory,
final GordianEncryptorSpec pSpec) throws GordianException {
/* Initialise underlying cipher */
super(pFactory, pSpec);
theEncryptor = getJavaEncryptor(getAlgorithmName(pSpec), false);
}
@Override
protected JcaPublicKey getPublicKey() {
return (JcaPublicKey) super.getPublicKey();
}
@Override
protected JcaPrivateKey getPrivateKey() {
return (JcaPrivateKey) super.getPrivateKey();
}
@Override
public void initForEncrypt(final GordianKeyPair pKeyPair) throws GordianException {
try {
/* Initialise underlying cipher */
JcaKeyPair.checkKeyPair(pKeyPair);
super.initForEncrypt(pKeyPair);
/* Initialise for encryption */
theEncryptor.init(Cipher.ENCRYPT_MODE, getPublicKey().getPublicKey(), getRandom());
} catch (InvalidKeyException e) {
throw new GordianCryptoException(ERROR_INIT, e);
}
}
@Override
public void initForDecrypt(final GordianKeyPair pKeyPair) throws GordianException {
try {
/* Initialise underlying cipher */
JcaKeyPair.checkKeyPair(pKeyPair);
super.initForDecrypt(pKeyPair);
/* Initialise for decryption */
theEncryptor.init(Cipher.DECRYPT_MODE, getPrivateKey().getPrivateKey());
} catch (InvalidKeyException e) {
throw new GordianCryptoException(ERROR_INIT, e);
}
}
@Override
public byte[] encrypt(final byte[] pBytes) throws GordianException {
/* Check that we are in encryption mode */
checkMode(GordianEncryptMode.ENCRYPT);
/* Encrypt the message */
return processData(pBytes);
}
@Override
public byte[] decrypt(final byte[] pBytes) throws GordianException {
/* Check that we are in decryption mode */
checkMode(GordianEncryptMode.DECRYPT);
/* Decrypt the message */
return processData(pBytes);
}
/**
* Process a data buffer.
*
* @param pData the buffer to process
* @return the processed buffer
* @throws GordianException on error
*/
private byte[] processData(final byte[] pData) throws GordianException {
try {
/* Access input block length */
int myInLen = pData.length;
final int myInBlockLength = theEncryptor.getBlockSize();
final int myNumBlocks = getNumBlocks(myInLen, myInBlockLength);
final int myOutBlockLength = theEncryptor.getOutputSize(myInBlockLength);
/* Create the output buffer */
final byte[] myOutput = new byte[myOutBlockLength * myNumBlocks];
/* Loop encrypting the blocks */
int myInOff = 0;
int myOutOff = 0;
while (myInLen > 0) {
/* Process the data */
final int myLen = Math.min(myInLen, myInBlockLength);
final byte[] myBlock = theEncryptor.doFinal(pData, myInOff, myLen);
/* Copy to the output buffer */
final int myOutLen = myBlock.length;
System.arraycopy(myBlock, 0, myOutput, myOutOff, myOutLen);
myOutOff += myOutLen;
/* Move to next block */
myInOff += myInBlockLength;
myInLen -= myInBlockLength;
}
/* Return full buffer if possible */
if (myOutOff == myOutput.length) {
return myOutput;
}
/* Cut down buffer */
final byte[] myReturn = Arrays.copyOf(myOutput, myOutOff);
Arrays.fill(myOutput, (byte) 0);
return myReturn;
} catch (IllegalBlockSizeException
| BadPaddingException e) {
throw new GordianCryptoException("Failed to process data", e);
}
}
/**
* Obtain the number of blocks required for the length in terms of blocks.
*
* @param pLength the length of clear data
* @param pBlockLength the blockLength
* @return the number of blocks.
*/
private static int getNumBlocks(final int pLength, final int pBlockLength) {
return (pLength + pBlockLength - 1) / pBlockLength;
}
/**
* Obtain the algorithmName.
*
* @param pSpec the Spec
* @return the algorithm name
*/
private static String getAlgorithmName(final GordianEncryptorSpec pSpec) {
/* Determine the base algorithm */
final String myBase = pSpec.getKeyPairType().name();
/* Switch on encryptor type */
switch (pSpec.getDigestSpec().getDigestLength()) {
case LEN_224:
return myBase + "/ECB/OAEPWITHSHA224ANDMGF1PADDING";
case LEN_256:
return myBase + "/ECB/OAEPWITHSHA256ANDMGF1PADDING";
case LEN_384:
return myBase + "/ECB/OAEPWITHSHA384ANDMGF1PADDING";
case LEN_512:
default:
return myBase + "/ECB/OAEPWITHSHA512ANDMGF1PADDING";
}
}
}
/**
* Hybrid Encryptor.
*/
public static class JcaHybridEncryptor
extends GordianCoreEncryptor {
/**
* The underlying encryptor.
*/
private final Cipher theEncryptor;
/**
* Constructor.
*
* @param pFactory the factory
* @param pSpec the encryptorSpec
* @throws GordianException on error
*/
JcaHybridEncryptor(final GordianBaseFactory pFactory,
final GordianEncryptorSpec pSpec) throws GordianException {
/* Initialise underlying cipher */
super(pFactory, pSpec);
theEncryptor = getJavaEncryptor(getAlgorithmName(pSpec), false);
}
@Override
protected JcaPublicKey getPublicKey() {
return (JcaPublicKey) super.getPublicKey();
}
@Override
protected JcaPrivateKey getPrivateKey() {
return (JcaPrivateKey) super.getPrivateKey();
}
@Override
public void initForEncrypt(final GordianKeyPair pKeyPair) throws GordianException {
try {
/* Initialise underlying cipher */
JcaKeyPair.checkKeyPair(pKeyPair);
super.initForEncrypt(pKeyPair);
/* Initialise for encryption */
theEncryptor.init(Cipher.ENCRYPT_MODE, getPublicKey().getPublicKey(), getRandom());
} catch (InvalidKeyException e) {
throw new GordianCryptoException(ERROR_INIT, e);
}
}
@Override
public void initForDecrypt(final GordianKeyPair pKeyPair) throws GordianException {
try {
/* Initialise underlying cipher */
JcaKeyPair.checkKeyPair(pKeyPair);
super.initForDecrypt(pKeyPair);
/* Initialise for decryption */
theEncryptor.init(Cipher.DECRYPT_MODE, getPrivateKey().getPrivateKey());
} catch (InvalidKeyException e) {
throw new GordianCryptoException(ERROR_INIT, e);
}
}
@Override
public byte[] encrypt(final byte[] pBytes) throws GordianException {
/* Check that we are in encryption mode */
checkMode(GordianEncryptMode.ENCRYPT);
/* Encrypt the message */
return processData(pBytes);
}
@Override
public byte[] decrypt(final byte[] pBytes) throws GordianException {
/* Check that we are in decryption mode */
checkMode(GordianEncryptMode.DECRYPT);
/* Decrypt the message */
return processData(pBytes);
}
/**
* Process a data buffer.
*
* @param pData the buffer to process
* @return the processed buffer
* @throws GordianException on error
*/
private byte[] processData(final byte[] pData) throws GordianException {
try {
return theEncryptor.doFinal(pData, 0, pData.length);
} catch (IllegalBlockSizeException
| BadPaddingException e) {
throw new GordianCryptoException("Failed to process data", e);
}
}
/**
* Obtain the algorithmName.
*
* @param pSpec the Spec
* @return the algorithm name
*/
private static String getAlgorithmName(final GordianEncryptorSpec pSpec) {
/* Switch on encryptor type */
final GordianSM2EncryptionSpec mySpec = pSpec.getSM2EncryptionSpec();
final GordianDigestSpec myDigestSpec = mySpec.getDigestSpec();
final GordianDigestType myDigestType = myDigestSpec.getDigestType();
switch (myDigestType) {
case SHA2:
return "SM2withSHA" + myDigestSpec.getDigestLength();
case BLAKE2:
return "SM2withBlake2" + (GordianLength.LEN_512.equals(myDigestSpec.getDigestLength()) ? "b" : "s");
case WHIRLPOOL:
case SM3:
default:
return "SM2with" + myDigestType;
}
}
}
/**
* Create the BouncyCastle KeyFactory via JCA.
*
* @param pAlgorithm the Algorithm
* @param postQuantum is this a postQuantum algorithm?
* @return the KeyFactory
* @throws GordianException on error
*/
private static Cipher getJavaEncryptor(final String pAlgorithm,
final boolean postQuantum) throws GordianException {
/* Protect against exceptions */
try {
/* Return a Cipher for the algorithm */
return Cipher.getInstance(pAlgorithm, postQuantum
? JcaProvider.BCPQPROV
: JcaProvider.BCPROV);
/* Catch exceptions */
} catch (NoSuchAlgorithmException
| NoSuchPaddingException e) {
/* Throw the exception */
throw new GordianCryptoException("Failed to create Cipher", e);
}
}
}