Cryptography has always been one of the harder programming subjects. It’s hard (but important) to get right, mathematically complex and we (luckily) don’t need to implement it ourselves very often. 99% of the time battle tested implementations will do, but sometimes you just have to roll your own. Or in my case, just have an irresistible urge to do so.
We’ll go back 2000 years in time and implement the Caesar cipher, one of the oldest and best known symmetric encryption ciphers.
Nowadays it’s an extremely unsafe cipher but back in the day it was pretty safe, not in the last place because most people were illiterate anyways.
We’ll implement it using JCA and JCE*, specs which provide an SPI (Service Provider Interface) to implement stuff like keystores and hashing/signing/encryption algorithms. What we’ll be doing:
Implement Caesar cipher (extend javax.crypto.CipherSpi).
Implement custom keystore (extend java.security.KeyStoreSpi) to store our Caesar cipher keys.
Implement Security Provider (extend java.security.Provider) to register the cipher and keystore.
Install our custom Security Provider and test it by encrypting and decrypting “Hello World”!
The Caesar cipher was used by Julius Caesar to encrypt messages sent to his troops. It works by shifting all letters
in a message by a fixed number of positions. When shifting 1 position, A becomes B, C becomes D, etc.
For example, shifting “Hello World” by 1 position gives “Ifmmp Xpsme”.
It helps to write a transposition matrix to visualize it. The matrix below is shifting 1 position:
A B C D E F G H I J K L M N O P Q R S T U V W X Y Z
Z A B C D E F G H I J K L M N O P Q R S T U V W X Y
The table below shifts 2 positions:
A B C D E F G H I J K L M N O P Q R S T U V W X Y Z
Y Z A B C D E F G H I J K L M N O P Q R S T U V W X
Our encryption function looks like this:
charencrypt(charcharacter,intshift){varbase=findBase(character);returnbase==null?character:(char)((character-base+shift)%26+base);}CharacterfindBase(charcharacter){// For upper/lowercase supportif(character>='a'&&character<='z'){return'a';}elseif(character>='A'&&character<='Z'){return'A';}else{returnnull;}}
To implement this cipher you extend javax.crypto.CipherSpi:
CaesarCipher.java
To encrypt/decrypt using the cipher you need a secret key.
Implement the interface javax.crypto.SecretKey for this purpose, which simply holds the number of shifts:
CaesarKey.java
Custom Keystore
Next, we’ll need a keystore to store our Caesar cipher keys in. Since keystores are accessed using aliases and our secret keys are just integers, we’ll be persisting them as a property list.
Normally you would be encrypting the keystore’s content using the provided password, but for the sake of simplicity we won’t.
To implement a keystore you extends java.security.KeyStoreSpi:
CustomKeyStore.java
Setting up a Security Provider
When using a cryptographic service a Security Provider is used under the hood, which you can implement by extending java.security.Provider.
It defines what keystores and algorithms can be used. Ours looks like this:
importjava.security.AccessController;importjava.security.PrivilegedAction;importjava.security.Provider;publicclassCustomProviderextendsProvider{publicstaticfinalStringNAME="Custom";publicCustomProvider(){super(NAME,"1.0","Custom Java Security Provider");AccessController.doPrivileged((PrivilegedAction<?>)()->{// Install custom keystoreput("KeyStore.Custom",CustomKeyStore.class.getName());// Install Caesar cipherput("Cipher.Caesar",CaesarCipher.class.getName());returnnull;});}}
We’re registering both a keystore type and a cipher. Since that is a privileged action, we need to wrap it in a AccessController.doPrivileged call in case a Java Security Manager is installed.
Testing the keystore and cipher
Now our keystore and cipher are ready to use, but we still have to register the security provider.
There are a few ways to do this, but we’ll do it programmatically**:
Security.addProvider(newCustomProvider());
Then you can use the cipher to encrypt strings:
varkey=newCaesarKey(20);varcipher=Cipher.getInstance("Caesar");cipher.init(Cipher.ENCRYPT_MODE,key,newSecureRandom());// Should print: Byffi, Qilfx!System.out.println(newString(cipher.update("Hello, World!".getBytes())));
CaesarCipherTest.java
That’s it!
Footnotes
* JCA: Java Cryptography Architecture, everything in java.security. JCE: Java Cryptography Extension, everything in javax.crypto.
** When running in a controlled application server you might have to preconfigure your provider.