I wrote a command line application for encrypting/decrypting files to your local machine. The idea is you have an asymmetric key pair where the private key is stored encrypted and the public key is stored in plain text. This way you can encrypt data without having to "unlock" the application (decrypt your private key), but you must run the unlock command and provide your password to decrypt anything.
The encryption process is roughly as follows:
- You provide some plain text
- A (secure) random AES key is generated
- Your plain text is encrypted with the AES key
- The AES key is encrypted with your public key
- The encrypted AES key and ciphertext are written to a file
The decryption process then is roughly as follows:
- You provide a password
- That password is put into a key derivation algorithm to establish an AES key
- That AES is used to decrypt your private key
- The private key is used to decrypt the AES key for the file you are trying to decrypt
- The files AES is used to decrypt the file
My hope was that I could allow the user to specify which asymmetric cipher they want to use. For example you could choose to use RSA with 4096 bit keys or ECIES with Ed448 keys. I imagined this would be possible as the app is written in Java/Kotlin using Bouncy Castle and the JCA provides a generic interface for retrieving and initialising asymmetric key pair generators and ciphers. For example:
val keyGen = KeyPairGenerator.getInstance("ED448")
keyGen.initialize(448)
val keyPair = keyGen.genKeyPair()
val asymmetricCipher = Cipher.getInstance("XIES")
asymmetricCipher.init( Cipher.ENCRYPT_MODE, keyPair.public)
In this example the user can provide: "ED448" as the key pair generator algorithm, 448 as the key size (redundant in this case but not always), and "XIES" as the cipher name/transform. My hope is this flexibility will future proof the application a little more as you can use any cipher provided by your JCA and can therefore switch if a vulnerability is discovered etc. For example I was previously using the parameters: RSA, RSA/ECB/PKCS1Padding, 2048
In reality however I'm finding that Bouncy Castle's ECIES and XIES cipher implementations throw an exception if you do not provide algorithm specific parameters when you initialise the cipher i.e.
/**
* Set the IES engine parameters.
*
* @param derivation the optional derivation vector for the KDF.
* @param encoding the optional encoding vector for the KDF.
* @param macKeySize the key size (in bits) for the MAC.
* @param cipherKeySize the key size (in bits) for the block cipher.
* @param nonce an IV to use initialising the block cipher.
* @param usePointCompression whether to use EC point compression or not (false by default)
*/
public IESParameterSpec(
byte[] derivation,
byte[] encoding,
int macKeySize,
int cipherKeySize,
byte[] nonce,
boolean usePointCompression)
{
What I'm considering doing in order to keep the interface generic for the end user is just to have a hard coded parameter spec object that is used if the user provides ECIES/XIES or any variant there of and simply pass the parameters:
IESParameterSpec(
/* derivation = */ null,
/* encoding = */ null,
/* macKeySize = */ 256,
/* cipherKeySize = */ 256,
/* nonce = */ null,
/* usePointCompression = */ false
)
Bearing in mind that the only thing this cipher ever encrypts is randomly generated 256 bit AES keys, is this a safe/reasonable standard parameter spec or is it necessary to always use (and presumably then store) fresh parameters for each encryption operation?
Perhaps I am way off base and it is not reasonable to try to provide a generic interface for all asymmetric cipher/key pair specifications despite what the JCA interface would have you believe. Or perhaps there is a much simpler cipher one can use with an elliptic curve key pair that doesn't require such parameters. I don't possess a lot of experience with elliptic curve cryptography so please let me know.