/* See LICENSE for licensing and NOTICE for copyright. */
package org.cryptacular.spec;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.bouncycastle.crypto.BlockCipher;
import org.bouncycastle.crypto.BufferedBlockCipher;
import org.bouncycastle.crypto.modes.CBCBlockCipher;
import org.bouncycastle.crypto.modes.CFBBlockCipher;
import org.bouncycastle.crypto.modes.OFBBlockCipher;
import org.bouncycastle.crypto.paddings.BlockCipherPadding;
import org.bouncycastle.crypto.paddings.ISO10126d2Padding;
import org.bouncycastle.crypto.paddings.ISO7816d4Padding;
import org.bouncycastle.crypto.paddings.PKCS7Padding;
import org.bouncycastle.crypto.paddings.PaddedBufferedBlockCipher;
import org.bouncycastle.crypto.paddings.TBCPadding;
import org.bouncycastle.crypto.paddings.X923Padding;
import org.bouncycastle.crypto.paddings.ZeroBytePadding;
/**
* Describes a block cipher in terms of a (algorithm, mode, padding) tuple and provides a facility to create a new
* instance of the cipher via the {@link #newInstance()} method.
*
* @author Middleware Services
* @version $Revision: 2744 $
*/
public class BufferedBlockCipherSpec implements Spec<BufferedBlockCipher>
{
/** String specification format, <code>algorithm/mode/padding</code>. */
public static final Pattern FORMAT = Pattern.compile("(?<alg>[A-Za-z0-9_-]+)/(?<mode>\\w+)/(?<padding>\\w+)");
/** Cipher algorithm algorithm. */
private final String algorithm;
/** Cipher mode, e.g. CBC, OFB. */
private final String mode;
/** Cipher padding scheme, e.g. PKCS5Padding. */
private final String padding;
/**
* Creates a new instance from an algorithm name.
*
* @param algName Cipher algorithm name.
*/
public BufferedBlockCipherSpec(final String algName)
{
this(algName, null, null);
}
/**
* Creates a new instance from a cipher algorithm and mode.
*
* @param algName Cipher algorithm name.
* @param cipherMode Cipher mode.
*/
public BufferedBlockCipherSpec(final String algName, final String cipherMode)
{
this(algName, cipherMode, null);
}
/**
* Creates a new instance from the given cipher specifications.
*
* @param algName Cipher algorithm name.
* @param cipherMode Cipher mode.
* @param cipherPadding Cipher padding scheme algorithm.
*/
public BufferedBlockCipherSpec(final String algName, final String cipherMode, final String cipherPadding)
{
this.algorithm = algName;
this.mode = cipherMode;
this.padding = cipherPadding;
}
@Override
public String getAlgorithm()
{
return algorithm;
}
/**
* Gets the cipher mode.
*
* @return Cipher mode, e.g. CBC, OFB.
*/
public String getMode()
{
return mode;
}
/**
* Gets the cipher padding scheme.
*
* @return Padding scheme algorithm, e.g. PKCS5Padding. The following names are equivalent for no padding: NULL,
* Zero, None.
*/
public String getPadding()
{
return padding;
}
/**
* Gets the simple block cipher specification corresponding to this instance.
*
* @return Simple block cipher specification.
*/
public BlockCipherSpec getBlockCipherSpec()
{
return new BlockCipherSpec(this.algorithm);
}
/**
* Creates a new buffered block cipher from the specification in this instance.
*
* @return New buffered block cipher instance.
*/
@Override
public BufferedBlockCipher newInstance()
{
BlockCipher cipher = getBlockCipherSpec().newInstance();
switch (mode) {
case "CBC":
cipher = new CBCBlockCipher(cipher);
break;
case "OFB":
cipher = new OFBBlockCipher(cipher, cipher.getBlockSize());
break;
case "CFB":
cipher = new CFBBlockCipher(cipher, cipher.getBlockSize());
break;
default:
break;
}
if (padding != null) {
return new PaddedBufferedBlockCipher(cipher, getPadding(padding));
}
return new BufferedBlockCipher(cipher);
}
@Override
public String toString()
{
return algorithm + '/' + mode + '/' + padding;
}
/**
* Parses a string representation of a buffered block cipher specification into an instance of this class.
*
* @param specification Block cipher specification of the form <code>algorithm/mode/padding</code>.
*
* @return Buffered block cipher specification instance.
*/
public static BufferedBlockCipherSpec parse(final String specification)
{
final Matcher m = FORMAT.matcher(specification);
if (!m.matches()) {
throw new IllegalArgumentException("Invalid specification " + specification);
}
return new BufferedBlockCipherSpec(m.group("alg"), m.group("mode"), m.group("padding"));
}
/**
* Gets a instance of block cipher padding from a padding name string.
*
* @param padding Name of padding algorithm.
*
* @return Block cipher padding instance.
*/
private static BlockCipherPadding getPadding(final String padding)
{
final String name;
final int pIndex = padding.indexOf("Padding");
if (pIndex > -1) {
name = padding.substring(0, pIndex);
} else {
name = padding;
}
BlockCipherPadding blockCipherPadding;
if ("ISO7816d4".equalsIgnoreCase(name) | "ISO7816".equalsIgnoreCase(name)) {
blockCipherPadding = new ISO7816d4Padding();
} else if ("ISO10126".equalsIgnoreCase(name) || "ISO10126-2".equalsIgnoreCase(name)) {
blockCipherPadding = new ISO10126d2Padding();
} else if ("PKCS7".equalsIgnoreCase(name) || "PKCS5".equalsIgnoreCase(name)) {
blockCipherPadding = new PKCS7Padding();
} else if ("TBC".equalsIgnoreCase(name)) {
blockCipherPadding = new TBCPadding();
} else if ("X923".equalsIgnoreCase(name)) {
blockCipherPadding = new X923Padding();
} else if ("NULL".equalsIgnoreCase(name) || "Zero".equalsIgnoreCase(name) || "None".equalsIgnoreCase(name)) {
blockCipherPadding = new ZeroBytePadding();
} else {
throw new IllegalArgumentException("Invalid padding " + padding);
}
return blockCipherPadding;
}
}