Java Cryptography Introduction
by Nicklas EnvallCryptography is the art of secret writing or solving codes. Cryptography is most often used to deal with the main security concerns of applications.
Amongst other things in cryptography, we use methods like signatures and certificates to authenticate users so we can ensure they are who they actually say they are. Cryptography provides us with three main features which you should keep in mind when applying cryptography:
- Authentication: ensure users are who they say they are.
- Integrity: ensure data has not been tampered with, without your knowledge.
- Confidentiality: ensure only authorized people can view the data.
Luckily, Java provides us with a Java Security API, which makes our lives much easier. The Java Cryptography API provides us with packages which help us implement security. In this article, we will go through the basics of Java Cryptography.
Table of contents:
- Keys and Certificates
- Java Keystores
- Generating Keys
- Message Digest
- Java Digital Signatures
- Java Cipher
1. Keys and Certificates
1.1 Java Keys
The properties of a key in Java is defined by the java.security.Key
interface. In cryptography, there are three different types of keys we usually talk about, which are secret, public and private.
There are two general types of keys, asymmetric and symmetric. Asymmetric keys come in two different types, private and public (key pair). While symmetric keys are referred to as secret keys. Which type of keys you use depends on which cryptographic algorithm you’ve chosen.
1.2 Java Certificates
A certificate is used to prove ownership of a public key. A certificate does not prove that whoever sent it can be trusted, it proves that it’s the sender who owns the key. In order to prove ownership, certificates contain a signature, public key, and the subject information.
In Java, we use the java.security.cert.Certificate
abstract class when working with certificates. There are other classes for certificates, but these are subclasses of java.security.cert.Certificate
.
2. Java Keystores
A keystore is a container for keys and certificates. The keystore is by default put in a file called .keystore
.
We have three basic entries for our keystore. Which are KeyStore.PrivateKeyEntry
, KeyStore.SecretKeyEntry
, and KeyStore.TrustedCertificateEntry
. Entries can consist of keys and certificates. We use an alias to identify an entry.
2.1 Generate a Keystore and Keypairs with Java Keytool
You can use the keytool
command to administrative your keystore. The keytool
allows us to efficiently work via our terminal, by providing a command-line interface to the Keystore class.
When creating your key pair via the keytool
you have many options, but these are the most basic java keytool commands.
- -genkey, generate a key pair
- -alias <alias>
- -keysize <keysize>
- -keyalg <key_algorithm>
- -keypass <key_pass>
- -keystore <keystore_name>
- -storepass <keystore_password>
Note: if you do not use -keystore
it will use a default one. If you type a filename, but a keystore with that name does not exist, then it will create a new keystore for you, with that name.
Example:
In the following example, we create/use a keystore named my-keystore with the password storepass123. We gave our generated key pair the alias awesome and the password pass123 to access it. This will also create a self-signed certificate containing the newly generated public key.
$ keytool -genkey -alias awesome -keysize 1024 -keypass pass123 -keystore my-keystore -storepass storepass123
Control that everything went correctly by pasting this into your terminal:
$ keytool -list -keystore my-keystore -storepass storepass123 -alias awesome -keypass pass123
Problems with the latter command may occur, if you are using a system that has a “non-english system locale”, hopefully, that pointer will help you, if a problem occurs related to that.
For more information on how you can use the keytool a great source is the docs about keytool.
2.2 The KeyStore Class
The java.security.KeyStore
class represents a container for certificates and keys. Here are the most basic methods you’ll use:
public static final KeyStore getInstance(String type) public static final KeyStore getInstance(String type, String provider) public final void load(InputStream is, char[] password) public final void store(OutputStream os, char[] password) public final Key getKey(String alias, char[] password) public final Certificate getCertificate(String alias)
You can find more information and other methods at the docs for KeyStore.
Example:
Example of obtaining a keystore object, loading the keystore and then retrieving a key pair based on the alias and then the certificate.
// Get the instance KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType()); // Load it InputStream is = new FileInputStream(keyStorePath); ks.load(is, keystorePassword); // Get private key PrivateKey privateKey = (PrivateKey) ks.getKey(alias, keyPassword); // Get certificate Certificate cert = ks.getCertificate(alias);
3. Generating Keys
We can generate keys by utilizing one of the two standard engines that the Java security API provides. Generator classes create keys from nothing (except the state you set up when initialization it). The KeyGenerator class generates symmetric keys while the KeyPairGenerator generates asymmetric key pairs.
3.1 KeyPairGenerator Class
The java.security.KeyPairGenerator
class is used to generate both public and private keys.
// Get your instance public static KeyPairGenerator getInstance(String algorithm) public static KeyPairGenerator getInstance(String algorithm, String provider) // Initialize your object (you can specify key size) public void initialize(int strength) public abstract void initialize(int strength, SecureRandom random) // Generate your keys (methods does the same thing but has different names) public abstract KeyPair generateKeyPair( ) public final KeyPair genKeyPair( )
Example:
An example of creating KeyPairs and getting the public and private keys.
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("DSA"); keyPairGenerator.initialize(1024); KeyPair keyPair = keyPairGenerator.generateKeyPair( ); PrivateKey privateKey = keyPair.getPrivate(); PublicKey publicKey = keyPair.getPublic();
3.2 KeyGenerator Class
The javax.crypto.KeyGenerator
class generates secret keys. The main difference between the KeyGenerator and KeyPairGenerator is that the KeyGenerator creates secret keys, other than that they are very similar. More specifically it creates secret keys for symmetric encryption algorithms.
The Java Cryptography Extension (JCE) provides us with algorithms which we use depending on what we want to accomplish, for example:
- Data encryption: Blowfish, DES, DESede
- Calculating a MAC: HmacMD5 and HmacSHA1
// Get your instance public static final KeyGenerator getInstance(String algorithm) public static final KeyGenerator getInstance(String algorithm, String provider) // Initialize your object public final void init(SecureRandom sr) public final void init(AlgorithmParameterSpec aps) public final void init(AlgorithmParameterSpec aps, SecureRandom sr) public final void init(int strength) public final void init(int strength, SecureRandom sr) // Generate key public final SecretKey generateKey( )
4. Message Digests
With the java.security.MessageDigest
class we can implement message digests. We can use message digests to verify data integrity.
// get an instance public static MessageDigest getInstance(String algorithm) public static MessageDigest getInstance(String algorithm, String provider) // add the data to be digested public void update(byte input) public void update(byte[] input) public void update(byte[] input, int offset, int length)
After we used getInstance()
and update()
we will be able to generate the Message Digest by using the digest()
method. It will return our message digest in a byte array. After this method has completed it will also reset its internal state, which means we can reuse it if we would like to create a new message. You can also reset it manually any time you’d like by using the public void reset( )
method which will remove all the accumulated data.
public byte[] digest( ) public byte[] digest(byte[] input)
4.1 Secure Message Digests (MAC)
A Message Authentication Code (MAC) is a secure message digest. A MAC can’t be created only from input data, it needs a secret key that both the sender and receiver shares. This means that the receiver can see if the data has been tampered with (corrupted).
The javax.crypto.Mac
class (JCE) uses a secret key in order to calculate the message digest. The sender and the receiver agree which secret key they will use.
5. Java Digital Signatures
The java.security.Signature
class provides us with operations so we can handle digital signatures for authentication and integrity assurance of digital data.
Generating a digital signature is very similar to generating a message digest. The main difference is that a signed message cannot be changed without the key that was used during its creation. This makes the Digital Signature Algorithm (DSA) more appealing than a Message Authentication Code (MAC) because a public key must be shared between the sender and the receiver. This means that you as a developer will have to provide a key before you start working on a signature object.
// get your instance public static Signature getInstance(String algorithm) public static Signature getInstance(String algorithm, String provider) // prepare our object to verify a signature or to create a signature public void final initVerify(PublicKey publicKey) public final void initSign(PrivateKey privateKey) // give the object data public final void update(byte b) public final void update(byte[] b) public final void update(byte b[], int offset, int length) // sign or verify the data public final byte[] sign( ) public final int sign(byte[] outbuf, int offset, int len) public final boolean verify(byte[] signature)
When we have added the data to our signature object we can either sign or verify. In both cases, the object will reset once we have called one of the methods. We may then verify or sign new data with the same object.
5.1 Java Signature Example
In the following example, we firstly generate keys. After we have generated our keys we initialize our signature object with our private key. We later add the data we want to sign by giving the method a byte[]
containing the data. We then sign the data, which means we get a signature back in a byte[]
. Now our accumulated data in the signature object has been reset because we signed the previous data. Since the data has been reset we can do a similar approach but now with verification. As you see near the end of the code, we use the signature byte[]
when we verify the data.
The example demonstrates only how the flow would work, and therefore the code does not really meet production standards.
import java.security.InvalidKeyException; import java.security.KeyPair; import java.security.KeyPairGenerator; import java.security.NoSuchAlgorithmException; import java.security.PrivateKey; import java.security.PublicKey; import java.security.Signature; import java.security.SignatureException; public class SignatureExample { public static void main(String[] args) { // The message we want to sign String message = "our message we want to sign"; byte[] bytes = message.getBytes(); try { // Firstly we generate our pair keys (public and private) KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("DSA"); keyPairGenerator.initialize(1024); KeyPair keyPair = keyPairGenerator.generateKeyPair(); // Our private and public key PrivateKey privKey = keyPair.getPrivate(); PublicKey pubKey = keyPair.getPublic(); // Create our signature object Signature signatureObject = Signature.getInstance("SHA1withDSA"); // Initialize our object with our private key as preparation for creation of signature signatureObject.initSign(privKey); // We give the data we want to sign into our object signatureObject.update(bytes); // We sign it, we get the signature back as a byte[] byte[] signature = signatureObject.sign(); // NOTE: now you have signed a message // Now we will verify with our signature firstly we initialize our verification with // our public key and put in the data we want to verify signatureObject.initVerify(pubKey); signatureObject.update(bytes); if (signatureObject.verify(signature)) { System.out.println("The signature verifies!"); } else { System.out.println("The signature fails!"); } } catch (NoSuchAlgorithmException e) { e.printStackTrace(); } catch (InvalidKeyException e) { e.printStackTrace(); } catch (SignatureException e) { e.printStackTrace(); } } }
6. Java Cipher
The javax.crypto.Cipher
class helps us encrypt and decrypt data. In cryptography, encryption entails encoding data so only authorized parties can access it.
Unlike other security engines, the Cipher engine does not only declare the algorithm to be used when obtaining an instance. It can also use the following naming convention algorithm/mode/padding
. The reason why you can pick a mode or a padding scheme is that the Cipher classes implement Cipher blocks, meaning it operates on a fixed-size block of data.
Padding schemes are used to ensure that the data length is an essential number of blocks. Modes are used to increase the difficulty of breaking the encryption. Modes can also be used to process data in smaller quantities by allowing a block cipher to act as a stream cipher.
If you would, for example, decrypt data, then the mode and padding scheme must match what was used during the encryption.
6.1 How to use the Cipher Class in Java
Firstly you must get an instance of the Cipher Class. You can do this either by just providing a simple algorithm name or also by specifying the mode and padding scheme by using the following format algorithm/mode/padding
.
public static Cipher getInstance(String algorithmName) public static Cipher getInstance(String algorithmName, String provider)
Secondly, you will have to initialize the object with a key. A secret key may be used for a symmetric cipher or a public key for encryption and a private key for decryption. You can do this by using one of the following methods:
public final void init(int op, Key k) public final void init(int op, Certificate c) public final void init(int op, Key k, AlgorithmParameterSpec aps) public final void init(int op, Key k, AlgorithmParameterSpec aps, SecureRandom sr) public final void init(int op, Key k, SecureRandom sr) public final void init(int op, Certificate c, SecureRandom sr) public final void init(int op, Key k, AlgorithmParameters ap) public final void init(int op, Key k, AlgorithmParameters ap, SecureRandom sr)
The first parameter int op
will decide whether you initialize the cipher for encryption or decryption. Simply add Cipher.ENCRYPT_MODE
or Cipher.DECRYPT_MODE
when initializing.
Now when we have initialized our instance of the Cipher class, we can give it data to work with. To do this, we use the update()
method.
public final byte[] update(byte[] input) public final byte[] update(byte[] input, int offset, int length) public final int update(byte[] input, int offset, int length, byte[] output) public final int update(byte[] input, int offset, int length, byte[] output, int outOffset)
You can also give data to the engine by using the doFinal()
method. However, this also tells the engine that all the data has been given and to return a new buffer with the result.
public final byte[] doFinal( ) public final int doFinal(byte[] output, int offset) public final byte[] doFinal(byte[] input) public final byte[] doFinal(byte[] input, int offset, int length) public final int doFinal(byte[] input, int offset, int length, byte[] output) public final int doFinal(byte[] input, int offset, int length, byte[] output, int outOffset)
6.2 The Cipher Stream Classes
The Cipher Stream Classes can make our lives easier when we decrypt or encrypt via streams. The classes are javax.crypto.CipherInputStream
and javax.crypto.CipherOutputStream
.
The following code demonstrates how you can encrypt input stream data:
FileInputStream fis = new FileInputStream("/inputfile.txt"); CipherInputStream cis = new CipherInputStream(fis, cipher1); FileOutputStream fos = new FileOutputStream("/outputfile.txt"); byte[] b = new byte[8]; int i = cis.read(b); while (i != -1) { fos.write(b, 0, i); i = cis.read(b); }
Summarization
We can now see that fundamentally, all of the security classes are used very similarly. When working with the classes you will most often do the following steps:
- Use a
getInstance()
method to obtain an object, you will not use public constructors. - Initialize the object before you start working with it.
- Fill the object with the data you want to do something with.
- Pull the trigger, for example, generate a key or creating a signature.