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();
    }
}

@@ -4351,11 +4351,171 @@
 );%0A    %7D
+%0A%0A    @Override%0A    public byte%5B%5D getSecretKey() %7B%0A        throw new UnsupportedOperationException( %22Password crypto service secret key requires salt.%22 );%0A    %7D
 %0A%7D%0A

Fix compile error in PasswordCryptoService.