package com.kryptnostic.kodex.v1.crypto.ciphers; import java.security.NoSuchAlgorithmException; import java.security.spec.InvalidKeySpecException; import java.util.Arrays; import javax.crypto.SecretKey; import javax.crypto.SecretKeyFactory; import javax.crypto.spec.PBEKeySpec; import javax.crypto.spec.SecretKeySpec; import org.apache.commons.codec.binary.StringUtils; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonProperty; import com.kryptnostic.kodex.v1.constants.Names; import com.kryptnostic.kodex.v1.crypto.keys.SecretKeyFactoryType; import com.kryptnostic.kodex.v1.exceptions.types.SecurityConfigurationException; public class PasswordCryptoService extends AbstractCryptoService { private static final Cypher DEFAULT_CYPHER = Cypher.AES_CTR_128; private static final String FACTORY_TYPE_FIELD = "keyDerivationAlgorithm"; private static final String ITERATIONS_FIELD = "iterations"; private static final int DEFAULT_PASSWORD_ITERATIONS = 128; private final char[] password; private final int iterations; private final SecretKeyFactoryType secretKeyFactoryType; @JsonCreator public PasswordCryptoService( @JsonProperty( Names.CYPHER_FIELD ) CipherDescription description, @JsonProperty( Names.PASSWORD_FIELD ) char[] password, @JsonProperty( FACTORY_TYPE_FIELD ) SecretKeyFactoryType secretKeyFactoryType, @JsonProperty( ITERATIONS_FIELD ) int iterations ) { this( Cypher.createCipher( description ), iterations, password, secretKeyFactoryType ); } public PasswordCryptoService( String password ) { this( password.toCharArray() ); } public PasswordCryptoService( char[] password ) { this( DEFAULT_CYPHER, password ); } public PasswordCryptoService( Cypher cypher, char[] password ) { this( cypher, DEFAULT_PASSWORD_ITERATIONS, password, SecretKeyFactoryType.PBKDF2WithHmacSHA1 ); } public PasswordCryptoService( Cypher cypher, int iterations, char[] password, SecretKeyFactoryType secretKeyFactoryType ) { super( cypher ); this.password = password; this.iterations = iterations; this.secretKeyFactoryType = secretKeyFactoryType; } public BlockCiphertext encrypt( byte[] plaintext ) throws SecurityConfigurationException { return encrypt( plaintext, Cyphers.generateSalt() ); } /** * Note: plaintext String MUST be UTF_8 encoded. */ public BlockCiphertext encrypt( String plaintext ) throws SecurityConfigurationException { return encrypt( StringUtils.getBytesUtf8( plaintext ), Cyphers.generateSalt() ); } public String decrypt( BlockCiphertext ciphertext ) throws SecurityConfigurationException { return StringUtils.newStringUtf8( decryptBytes( ciphertext ) ); } @JsonProperty( ITERATIONS_FIELD ) public int getIterations() { return iterations; } @JsonProperty( FACTORY_TYPE_FIELD ) public SecretKeyFactoryType getSecretKeyFactoryType() { return secretKeyFactoryType; } @JsonProperty( Names.PASSWORD_FIELD ) public char[] getPassword() { return password; } @Override @JsonIgnore protected SecretKeySpec getSecretKeySpec( byte[] salt ) throws NoSuchAlgorithmException, InvalidKeySpecException { SecretKeyFactory factory = secretKeyFactoryType.getInstance(); PBEKeySpec spec = new PBEKeySpec( password, salt, iterations, cypher.getKeySize() ); SecretKey key = factory.generateSecret( spec ); SecretKeySpec secretKeySpec = new SecretKeySpec( key.getEncoded(), cypher.getCipherDescription().getAlgorithm() .getValue() ); return secretKeySpec; } @JsonIgnore protected SecretKeySpec getSecretKeySpec() throws NoSuchAlgorithmException, InvalidKeySpecException { return getSecretKeySpec( Cyphers.generateSalt() ); } @Override protected void finalize() throws Throwable { // Zero out the password when GC. Arrays.fill( password, (char) 0 ); super.finalize(); } @Override public byte[] getSecretKey() { throw new UnsupportedOperationException( "Password crypto service secret key requires salt." ); } }