In this post I’ll show you how to use Keyczar and Java to exchange messages between two parties in a secure way. Keyczar is a great library, but it lacks some documentation.
In this tutorial we use a SignedSession
, that means: the sender and the receiver each has a key pair consisting of private and public “crypt” keys and a key pair of private and
public “sign” keys. The crypt keys are used to encrypt/decrypt the message, the sign keys are used to sign/verify the message.
Let’s start!
First, we need to get Keyczar. Unfortunately Keyczar isn’t on Maven Central, so we’re going to download it from GitHub and build it ourself:
git clone https://github.com/google/keyczar.git
- Open the Keyczar release page on GitHub and lookup the commit of the last stable version. Obviously we use the Java version.
In case of 0.71g, the commit is
1c245d193c04f2bd7a56389be03e2dcfa1362cac
. cd keyczar/java/code
git checkout [commit]
, in my casegit checkout 1c245d193c04f2bd7a56389be03e2dcfa1362cac
mvn clean install
Now we have the latest stable Keyczar version in our local maven repository.
Next, we create the needed keys. Keyczar stores the keys in a repository. In the end, we need eight (!) repositories:
- Senders private crypt keys
- Senders public crypt keys
- Senders private sign keys
- Senders public sign keys
- Receivers private crypt keys
- Receivers public crypt keys
- Receivers private sign keys
- Receivers public sign keys
If the sender wants to send a message to the receiver, she will need receivers public crypt key and senders private sign key. When the receiver wants to read the message, she will need receivers private crypt key and senders public sign key. If the receiver wants to answer the message, the same applies only the other way round.
First, lets create some directories in which the keys are stored. I created an environment variable which points to the root of the key directory: DEMO_PATH="/home/moe/.../src/main/resources"
.
Create the directories for the sender: mkdir -p $DEMO_PATH/sender/secret/sign $DEMO_PATH/sender/secret/crypt $DEMO_PATH/sender/public/sign $DEMO_PATH/sender/public/crypt
.
Now create the directories for the receiver: mkdir -p $DEMO_PATH/receiver/secret/sign $DEMO_PATH/receiver/secret/crypt $DEMO_PATH/receiver/public/sign $DEMO_PATH/receiver/public/crypt
Okay, directories are there. How do we create the keys? Turns out, the Keyczar team has a tool for that, it’s called the KeyczarTool
. If you take a look in your checked out
Keyczar repository in the directory java/code/target
, there is a JAR named KeyczarTool-0.71g-*.jar
. That’s the one.
Now let’s create some keys! But first, we need to create the repositories:
java -jar KeyczarTool-*.jar create --location="$DEMO_PATH/sender/secret/crypt" --purpose=crypt --asymmetric=rsa
. This creates the repository for senders private crypt keys. We want to use asymmetric crypto with RSA.java -jar KeyczarTool-*.jar create --location="$DEMO_PATH/sender/secret/sign" --purpose=sign --asymmetric=rsa
. This creates the repository for senders private sign keys.java -jar KeyczarTool-*.jar create --location="$DEMO_PATH/receiver/secret/crypt" --purpose=crypt --asymmetric=rsa
. This creates the repository for receivers private crypt keys.java -jar KeyczarTool-*.jar create --location="$DEMO_PATH/receiver/secret/sign" --purpose=sign --asymmetric=rsa
. This creates the repository for receivers private sign keys.java -jar KeyczarTool-*.jar create --location="$DEMO_PATH/sender/public/crypt" --purpose=crypt --asymmetric=rsa
. This creates the repository for senders public crypt keys.java -jar KeyczarTool-*.jar create --location="$DEMO_PATH/sender/public/sign" --purpose=sign --asymmetric=rsa
. This creates the repository for senders public sign keys.java -jar KeyczarTool-*.jar create --location="$DEMO_PATH/receiver/public/crypt" --purpose=crypt --asymmetric=rsa
. This creates the repository for receivers public crypt keys.java -jar KeyczarTool-*.jar create --location="$DEMO_PATH/receiver/public/sign" --purpose=sign --asymmetric=rsa
. This creates the repository for receivers public sign keys.
The repositories are in place. Now create some keys:
java -jar KeyczarTool-*.jar addkey --location="$DEMO_PATH/sender/secret/crypt"
. This creates the senders private crypt key.java -jar KeyczarTool-*.jar addkey --location="$DEMO_PATH/sender/secret/sign"
. This creates the senders private sign key.java -jar KeyczarTool-*.jar addkey --location="$DEMO_PATH/receiver/secret/crypt"
. This creates the receivers private crypt key.java -jar KeyczarTool-*.jar addkey --location="$DEMO_PATH/receiver/secret/sign"
. This creates the receivers private sign key.
All private keys have been created. Keyczar implements a key versioning system, the key to use has to be promoted to the primary key. Now lets do that for all private keys:
java -jar KeyczarTool-*.jar promote --location="$DEMO_PATH/sender/secret/crypt" --version=1
java -jar KeyczarTool-*.jar promote --location="$DEMO_PATH/sender/secret/sign" --version=1
java -jar KeyczarTool-*.jar promote --location="$DEMO_PATH/receiver/secret/crypt" --version=1
java -jar KeyczarTool-*.jar promote --location="$DEMO_PATH/receiver/secret/sign" --version=1
Now we need to extract the corresponding public keys from the private keys and store them in the public repositories:
java -jar KeyczarTool-*.jar pubkey --location="$DEMO_PATH/sender/secret/crypt" --destination="$DEMO_PATH/sender/public/crypt"
java -jar KeyczarTool-*.jar pubkey --location="$DEMO_PATH/sender/secret/sign" --destination="$DEMO_PATH/sender/public/sign"
java -jar KeyczarTool-*.jar pubkey --location="$DEMO_PATH/receiver/secret/crypt" --destination="$DEMO_PATH/receiver/public/crypt"
java -jar KeyczarTool-*.jar pubkey --location="$DEMO_PATH/receiver/secret/sign" --destination="$DEMO_PATH/receiver/public/sign"
Phew. That was a lot of work for some secrecy and authenticity ;-) Now we have all the repositories with the necessary keys in it. Time to hack!
Create a new Maven project (or Gradle, or Ivy, or whatever you use) and add the following dependency:
<dependency>
<groupId>org.keyczar</groupId>
<artifactId>keyczar</artifactId>
<version>0.71g</version>
</dependency>
Use the version which you have installed in your local maven repository.
First, let’s implement the sender. The most important class is SignedSessionEncrypter
from Keyczar. The constructor takes two dependencies: an encrypter and a signer.
The encrypter needs a path to a repository. Question for you: which one of the eight repositories is the right one?
The answer is: we need to give the encrypter the path to the repository which contains the public key from the receiver. So let’s create the encrypter:
/**
* Path to the public crypt keys from receiver.
*/
private static final String CRYPT_KEYS = "src/main/resources/receiver/public/crypt";
Encrypter encrypter = new Encrypter(CRYPT_KEYS);
The signer also wants a path to a repository. We need to give him the repository which contains the private sign key from the sender:
/**
* Path to the private sign keys from sender.
*/
private static final String SIGN_KEYS = "src/main/resources/sender/secret/sign";
Signer signer = new Signer(SIGN_KEYS);
Now we can create our SignedSessionEncrypter
:
SignedSessionEncrypter signedSessionEncrypter = new SignedSessionEncrypter(encrypter, signer);
A session has so-called session material. This material contains a symmetric session key, which is encrypted with the receivers public key. But don’t worry, we don’t need to create more keys! Keyczar handles that for us. All we need is to take the opaque session material and give it to the receiver:
String session = signedSessionEncrypter.newSession();
The session is now created. We can use the session to encrypt data:
byte[] data = signedSessionEncrypter.encrypt("Hello Keyczar!".getBytes(UTF_8));
You can encrypt multiple chunks of data using the encrypt
method, as long as the session is established. The data in session
and data
can now be sent over an insecure wire to the receiver, both contain no sensitive material.
That’s all the code you need for an encrypted and signed session. How cool is that? Let’s take a look at the receiver side.
The counterpart of SignedSessionEncrypter
is the SignedSessionDecrypter
. It takes crypter, a verifier and the session material.
We already have received the session material from the sender. The crypter takes a path to a repository, again. This time, we need
to give him the path to the receivers private crypt keys. The verifier takes a repository path too. That path must point to the
senders public sign repository.
Now we can create the SignedSessionDecrypter
and decrypt the data received from the sender:
/**
* Path to the private crypt keys from receiver.
*/
private static final String CRYPT_KEYS = "src/main/resources/receiver/secret/crypt";
/**
* Path to the public sign keys from sender.
*/
private static final String VERIFY_KEYS = "src/main/resources/sender/public/sign";
Crypter crypter = new Crypter(CRYPT_KEYS);
Verifier verifier = new Verifier(VERIFY_KEYS);
SignedSessionDecrypter signedSessionDecrypter = new SignedSessionDecrypter(crypter, verifier, sessionMaterial);
// data contains the data received from sender
byte[] decrypt = signedSessionDecrypter.decrypt(data);
System.out.println("Message from sender: " + new String(decrypt, UTF_8));
The variable decrypt
contains the data created by the sender. If someone tampered with the message, an exception will been thrown.
And that’s all for today. If you want to see the whole thing in action, check out the code on GitHub.
Happy encrypting!