package com.kryptnostic.kodex.v1.crypto.ciphers;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidParameterException;
import java.security.NoSuchAlgorithmException;
import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.NoSuchPaddingException;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonValue;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableSet;
/**
* Purposefully named type-safe wrapper for working with Java's insane crypto API.
*
* @author Matthew Tamayo-Rios <matthew@kryptnostic.com>
*/
public enum Cypher {
AES_GCM_128( CryptoAlgorithm.AES, Mode.GCM, Padding.NONE, 128 ), AES_CTR_128( CryptoAlgorithm.AES, Mode.CTR,
Padding.NONE, 128 ), AES_CTR_256( CryptoAlgorithm.AES, Mode.CTR, Padding.NONE, 256 ), AES_CBC_PKCS5_128(
CryptoAlgorithm.AES, Mode.CBC, Padding.PKCS5, 128 ), AES_CBC_PKCS5_256( CryptoAlgorithm.AES, Mode.CBC,
Padding.PKCS5, 256 ), RSA_OAEP_SHA1_1024( CryptoAlgorithm.RSA, Mode.ECB,
Padding.OAEPWithSHA1AndMGF1Padding, 1024 ), RSA_OAEP_SHA1_2048( CryptoAlgorithm.RSA, Mode.ECB,
Padding.OAEPWithSHA1AndMGF1Padding, 2048 ), RSA_OAEP_SHA1_4096( CryptoAlgorithm.RSA, Mode.ECB,
Padding.OAEPWithSHA1AndMGF1Padding, 4096 ), RSA_OAEP_SHA256_1024( CryptoAlgorithm.RSA, Mode.ECB,
Padding.OAEPWithSHA256AndMGF1Padding, 1024 ), RSA_OAEP_SHA256_2048( CryptoAlgorithm.RSA, Mode.ECB,
Padding.OAEPWithSHA256AndMGF1Padding, 2048 ), RSA_OAEP_SHA256_4096( CryptoAlgorithm.RSA, Mode.ECB,
Padding.OAEPWithSHA256AndMGF1Padding, 4096 );
public static final Cypher DEFAULT = AES_CTR_128;
private static final String CIPHER_ENCODING = "%s/%s/%s";
private final CipherDescription description;
private Cypher( CryptoAlgorithm algorithm, Mode mode, Padding padding, int keySize ) {
description = new CipherDescription( algorithm, mode, padding, keySize );
}
@JsonValue
public CipherDescription getCipherDescription() {
return description;
}
public Cipher getInstance() throws NoSuchAlgorithmException, NoSuchPaddingException {
return Cipher.getInstance( toString() );
}
@Override
public String toString() {
return String.format(
CIPHER_ENCODING,
description.getAlgorithm().getValue(),
description.getMode().getValue(),
description.getPadding().getValue() );
}
public int getKeySize() {
return description.getKeySize();
}
public CryptoAlgorithm getAlgorithm() {
return description.getAlgorithm();
}
public String getName() {
return getAlgorithm().getValue();
}
public KeyGenerator getKeyGenerator() throws NoSuchAlgorithmException, InvalidAlgorithmParameterException {
if ( description.getAlgorithm() == CryptoAlgorithm.AES ) {
return KeyGenerator.getInstance( description.getAlgorithm().getValue() );
}
throw new InvalidAlgorithmParameterException( "Key generators are only supported for AES algorithm." );
}
// @JsonCreator
public static Cypher createCipher( String name ) {
return valueOf( name );
}
@JsonCreator
public static Cypher createCipher( CipherDescription description ) {
Preconditions.checkArgument(
ImmutableSet.of( 128, 256, 1024, 2048, 4096 ).contains( description.getKeySize() ),
"Only 128 bit and 256 key sizes are supported." );
if ( description.getAlgorithm().equals( CryptoAlgorithm.AES ) ) {
if ( description.getMode().equals( Mode.CTR ) ) {
if ( description.getPadding().equals( Padding.NONE ) ) {
if ( description.getKeySize() == 128 ) {
return AES_CTR_128;
} else if ( description.getKeySize() == 256 ) {
return AES_CTR_256;
}
} else if ( description.getPadding().equals( Padding.PKCS5 ) ) {
if ( description.getKeySize() == 128 ) {
return AES_CBC_PKCS5_128;
} else if ( description.getKeySize() == 256 ) {
return AES_CBC_PKCS5_256;
} else {
return unrecognizedCipher( "An unsupported key size was specified." );
}
}
}
} else if ( description.getAlgorithm().equals( CryptoAlgorithm.RSA ) ) {
if ( description.getMode().equals( Mode.ECB ) ) {
switch ( description.getPadding() ) {
case OAEPWithSHA1AndMGF1Padding:
switch ( description.getKeySize() ) {
case 1024:
return RSA_OAEP_SHA1_1024;
case 2048:
return RSA_OAEP_SHA1_2048;
case 4096:
return RSA_OAEP_SHA1_4096;
default:
return unrecognizedCipher();
}
case OAEPWithSHA256AndMGF1Padding:
switch ( description.getKeySize() ) {
case 1024:
return RSA_OAEP_SHA256_1024;
case 2048:
return RSA_OAEP_SHA256_2048;
case 4096:
return RSA_OAEP_SHA256_4096;
default:
return unrecognizedCipher();
}
default:
return unrecognizedCipher();
}
} else {
return unrecognizedCipher();
}
}
return unrecognizedCipher();
}
private static final Cypher unrecognizedCipher() throws InvalidParameterException {
return unrecognizedCipher( "An unsupported CipherDescription was received." );
}
private static final Cypher unrecognizedCipher( String additionalInfo ) {
throw new InvalidParameterException( "Unrecognized cipher description: " + additionalInfo );
}
}