Simplified encryption API wrapper for RSA and AES I'm working on an encryption library to simplify how my team uses encryption for api credentials and receiving encrypted messages from other teams/projects.

This code is wrapping java.security and BouncyCastle implementations. These classes are just simplifying the calls to those libraries. But I'm struggling with designing the appropriate abstraction for my top-level classes.

I started with these top-level interfaces to define the domain:

public interface Key {
    byte[] getBytes();
}

public interface Message {
    byte[] getBytes();
}

public interface Secret {
    byte[] getBytes();
}

public interface Decrypter {
    Message decrypt(Secret secret, Key key);
}

public interface Encrypter {
    Secret encrypt(Message message, Key key);
}


This worked well to wrap RSA encryption:

public class KeyPair {
    private final Key publicKey;
    private final Key privateKey;

    public Key getPublicKey() {
        return publicKey;
    }

    public Key getPrivateKey() {
        return privateKey;
    }
}

public class RsaEncrypter implements Encrypter {

    @Override
    public Secret encrypt(Message message, Key publicKey) {
        // Perform Encryption
    }
}

public class RsaDecrypter implements Decrypter {
    @Override
    public Message decrypt(Secret secret, Key privateKey) {
        // Perform the decryption
    }
}


But now that I'm applying it to our AES Encryption use cases I've encountered a problem. The Secret contains an InitializationVector because we're using AES in CBC mode.

So I've got this:

public class AesSecret implements Secret {
    private byte[] cipherText;
    private byte[] initilizationVector;

    @Override
    public byte[] getBytes() {
        return cipherText;
    }

    public byte[] getInitilizationVector() {
        return initilizationVector;
    }
}

public class AesDecrypter implements Decrypter {
    @Override
    public Message decrypt(Secret secret, Key key) {
        try {
            return decrypt((AesSecret) secret, key);
        } catch (ClassCastException e) {
            throw new IllegalArgumentException("AesDecrypter only supports subclasses of AesSecret", e);
        }
    }

    public Message decrypt(AesSecret secret, Key key) {
        // Do the AES Decryption
    }
}


The ClassCastException makes me think that I'm violating the Liskov Substitution Principle, and introducing the parallel hierarchy code-smell. I've read the Visitor pattern is a common solution to this code smell, but I haven't figured out how it would apply to my situation.

Any suggestions? Or am I over-thinking this?
I don't think you are over-thinking it, casting is always a code-smell. The fundamental problem is that you separate logic from data, the key (data) and the actual usage of that key (data). This is a problem in object-orientation.

Here is an alternative possible design, which does not do that:

public interface SecretKey {
    PublicKey generatePublicKey();

    CipherText encrypt(byte[] plaintext); // Symmetric encrypt
}

public interface PublicKey {
    CipherText encrypt(byte[] plaintext); // Asymmetric encrypt
} 

public interface CipherText {
    byte[] decrypt();
}


It all depends of course on your requirements, this may not exactly match what you are looking for, but the key idea here is that the data and function are not separated.