So you have managed to
create your own cipher. Great! Now lets go for the real thing and implement a cipher.
Basically ciphers are implemented similar to digests. But while digests can be used without any security restrictions, ciphers need to be signed when used with JCA. In more detail your cipher implementation must reside in a signed jar file. To sign you need a code signing certificate from Oracle. Application for such a certificate is free of charge, but will take approximately 2 weeks to be processed. You will not get it for fun either as you have to provide your use case.
For the exact procedure follow
Oracles step by step guide. To follow the tutorial below I expect you to have your certificate ready. Otherwise you might do the coding but will not be able to use your provider afterwards.
Source code for this tutorial is available on googlecode as a
single zip archive, as a
Team Project Set or you can
checkout the SVN projects directly.
Step 1: Create key classes
We wil create a
Caesar cipher implementation. You could also do
ROT13 with it. As ciphers are about encryption/decryption we need some keys. Our key class will be very simple as we just need to store an offset for shifting our letters later on.
Start by creating a new
Java Project named
com.example.jce.cipher. Then implement the key class
com.example.jce.provider.CaesarKey:
package com.example.jce.cipher;
import javax.crypto.SecretKey;
public class CaesarKey implements SecretKey {
private static final long serialVersionUID = -542084808194990775L;
private byte mValue;
public CaesarKey(byte value) {
// converts any value to [0, 26), even negative ones
mValue = (byte) (((value % 26) + 26) % 26);
}
@Override
public String getAlgorithm() {
return "Caesar";
}
@Override
public String getFormat() {
return getAlgorithm() + " format";
}
@Override
public byte[] getEncoded() {
return new byte[] { mValue };
}
}
You may also implement a key generator that creates random keys on demand. This is not mandatory for our example but may be useful sometimes. The generator is implemented in com.example.jce.cipher.CaesarkeyGenerator:
package com.example.jce.cipher;
import java.security.InvalidAlgorithmParameterException;
import java.security.SecureRandom;
import java.security.spec.AlgorithmParameterSpec;
import javax.crypto.KeyGeneratorSpi;
import javax.crypto.SecretKey;
public class CaesarKeyGenerator extends KeyGeneratorSpi {
SecureRandom mRandomSource;
public void engineInit(SecureRandom sr) {
mRandomSource = sr;
}
public void engineInit(AlgorithmParameterSpec ap, SecureRandom sr) throws InvalidAlgorithmParameterException {
throw new InvalidAlgorithmParameterException("No parameters supported in this class");
}
public SecretKey engineGenerateKey() {
if (mRandomSource == null)
mRandomSource = new SecureRandom();
return new CaesarKey((byte) Math.abs(mRandomSource.nextInt()));
}
@Override
protected void engineInit(int arg0, SecureRandom sr) {
engineInit(sr);
}
}
Step 2: Implement the cipher
Ciphers are implemented extending CipherSpi (as all JCA extensions extend a WhateverSpi class). Implement
com.example.jce.cipher.CaesarCipher as follows:
package com.example.jce.cipher;
import java.security.AlgorithmParameters;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.Key;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.spec.AlgorithmParameterSpec;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.CipherSpi;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.ShortBufferException;
public class CaesarCipher extends CipherSpi {
private Key mKey;
private int mOPMode;
@Override
protected byte[] engineDoFinal(byte[] input, int inputOffset, int inputLen) throws IllegalBlockSizeException, BadPaddingException {
return engineUpdate(input, inputOffset, inputLen);
}
@Override
protected int engineDoFinal(byte[] input, int inputOffset, int inputLen, byte[] output, int outputOffset) throws ShortBufferException,
IllegalBlockSizeException, BadPaddingException {
return engineUpdate(input, inputOffset, inputLen, output, outputOffset);
}
@Override
protected int engineGetBlockSize() {
return 1;
}
@Override
protected byte[] engineGetIV() {
return null;
}
@Override
protected int engineGetOutputSize(int inputLen) {
return 0;
}
@Override
protected AlgorithmParameters engineGetParameters() {
return null;
}
@Override
protected void engineInit(int opmode, Key key, SecureRandom random) throws InvalidKeyException {
mOPMode = opmode;
if (key instanceof CaesarKey)
mKey = key;
else
throw new InvalidKeyException("Caesar cipher needs a Caesar key");
}
@Override
protected void engineInit(int opmode, Key key, AlgorithmParameterSpec params, SecureRandom random) throws InvalidKeyException,
InvalidAlgorithmParameterException {
engineInit(opmode, key, random);
}
@Override
protected void engineInit(int opmode, Key key, AlgorithmParameters params, SecureRandom random) throws InvalidKeyException,
InvalidAlgorithmParameterException {
engineInit(opmode, key, random);
}
@Override
protected void engineSetMode(String mode) throws NoSuchAlgorithmException {
throw new NoSuchAlgorithmException();
}
@Override
protected void engineSetPadding(String pading) throws NoSuchPaddingException {
throw new NoSuchPaddingException();
}
@Override
protected byte[] engineUpdate(byte[] input, int inputOffset, int inputLen) {
byte[] output = new byte[inputLen];
try {
engineUpdate(input, inputOffset, inputLen, output, 0);
} catch (ShortBufferException e) {
// output buffer is always big enough
}
return output;
}
@Override
protected int engineUpdate(byte[] input, int inputOffset, int inputLen, byte[] output, int outputOffset) throws ShortBufferException {
if (output.length - outputOffset < inputLen)
throw new ShortBufferException("Buffer too short to hold encrypted data");
for (int index = inputOffset; index < inputOffset + inputLen; index++)
output[outputOffset + index - inputOffset] = twistCharacter(input[index], getKey());
return inputLen;
}
private byte getKey() {
// calculate key depending on encryption/decryption
if (mOPMode == Cipher.ENCRYPT_MODE)
return mKey.getEncoded()[0];
// decode using inverse key
return (byte) (26 - mKey.getEncoded()[0]);
}
private byte twistCharacter(byte data, byte key) {
if (Character.isLetter(data)) {
// only operate on characters, leave everything else as-is
if (Character.isLowerCase(data))
return (byte) (((data - 'a' + key) % 26) + 'a');
else
return (byte) (((data - 'A' + key) % 26) + 'A');
}
return data;
}
@Override
protected int engineGetKeySize(Key key) throws InvalidKeyException {
if (key instanceof CaesarKey)
return 1;
throw new InvalidKeyException("Caesar cipher needs a Caesar key");
}
}
This is a straight forward implementation that will encode characters only. Everything else like digits or punctuation will remain unchanged.
Step 3: Create a JCE provider
As for the digest, we need to usa a provider to register our digest in the JRE. Create a new class
com.example.jce.cipher.CaesarProvider:
package com.example.jce.cipher;
import java.security.Provider;
public class CaesarProvider extends Provider {
private static final long serialVersionUID = -67123468605911408L;
public CaesarProvider() {
super("Caesar Cipher", 1.0, "Caesar cipher v1.0");
put("KeyGenerator.Caesar", CaesarKeyGenerator.class.getName());
put("Cipher.Caesar", CaesarCipher.class.getName());
}
}
At this point we need the certificate requested from Oracle, because a cipher class needs to be nested in a signed jar file. If you do not have such a certificate your quest ends here...
For all the others: export the project as a
JAR file and sign it using
jarsigner along with your certificate. The commandline looks something like this:
jarsigner -keystore <store location> -storepass <store master password> <JAR file> <keystore alias>
Step 4: Using our provider
Create a new
Java project com.example.jce.cipher.consumer. Create a subfolder
libs and store your signed JAR file there. Then add this JAR to the
Java Build Path.
Now everything is in place to use our cipher:
public static void main(String[] args) {
// adding provider dynamically
Security.addProvider(new CaesarProvider());
try {
String message = "Hello World";
Cipher c = Cipher.getInstance("Caesar");
SecretKey key = new CaesarKey((byte) 2);
System.out.println("Message: " + message);
c.init(Cipher.ENCRYPT_MODE, key);
byte[] encrypted = c.doFinal(message.getBytes());
System.out.println("Encrypted: " + new String(encrypted));
c.init(Cipher.DECRYPT_MODE, key);
byte[] decrypted = c.doFinal(encrypted);
System.out.println("Decrypted: " + new String(decrypted));
} catch (Exception e) {
e.printStackTrace();
}
}
Further reading
If you intend to implement your own stuff you should have a look at these links: