Tuesday, July 2, 2013

Writing your own JCA extensions - a full cipher

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:

10 comments:

  1. I've followed the steps here, have signed my jar, but keep getting :

    java.security.NoSuchAlgorithmException: Cannot find any provider supporting Caesar
    at javax.crypto.Cipher.getInstance(Cipher.java:535)

    Any tips ?

    ReplyDelete
    Replies
    1. Did you use your code signing certificate you got from oracle? If I remember correctly you get this error when the jar is not signed correctly

      Delete
  2. Thanks for your reply Christian !. I am using my own key pair for signing, no complaints from jarsigner nor from jce/jca API. on the singing AFAIK. How do I get an official Oracle certificate for testing a provider ?

    ReplyDelete
    Replies
    1. There is a link in the 2nd paragraph, describing the procedure.

      Delete
    2. request to Oracle is sent

      Delete
  3. Darn, even for open source projects it's around $300 for a code signing cert. So a self-signed certificate won't work - would https://letsencrypt.org/ help here ?

    ReplyDelete
    Replies
    1. No, you need a cert signed by oracle, no one else. You get it for free, click on the link 'Oacle step by step guide' in the tutorial

      Delete
  4. Got reply from Oracle - they only give a certificate to enterprise entities. Bummer.

    ReplyDelete
  5. With OpenJDK, providers need not be signed to work. But with the code from this blog, I still get

    java.security.NoSuchAlgorithmException: Cannot find any provider supporting Caesar.

    ReplyDelete
  6. If I turn on debugging with -Djava.security.debug=all I get an additional stack trace :

    java.lang.Exception: Call trace
    at sun.security.jca.ProviderList.loadAll(ProviderList.java:278)
    at sun.security.jca.ProviderList.removeInvalid(ProviderList.java:299)
    at sun.security.jca.Providers.getFullProviderList(Providers.java:173)
    at java.security.Security.insertProviderAt(Security.java:360)
    at java.security.Security.addProvider(Security.java:403)


    Any tips before i give up ?

    ReplyDelete