Click here to Skip to main content
15,881,859 members
Articles / Programming Languages / Java

Symmetric Key Encryption by AES

Rate me:
Please Sign up or sign in to vote.
4.80/5 (7 votes)
15 Mar 2016CPOL8 min read 29.6K   933   16   1
This note has an example and some observations on symmetric key encryption by the Advanced Encryption Standard, which is also called AES.

Introduction

This note has an example and some observations on symmetric key encryption by the Advanced Encryption Standard, which is also called AES.

Background

Some time ago, I kept a note on data encryption by asymmetric encryption algorithm RSA. RSA has its advantages. But when the data volume gets large, its performance becomes a problem. In practice, it is common to encrypt a large chunk of data by symmetric algorithms. The following sentences are copied from Wikipedia regarding to AES.

  • AES has been adopted by the U.S. government and is now used worldwide. It supersedes the Data Encryption Standard (DES), which was published in 1977. The algorithm described by AES is a symmetric-key algorithm, meaning the same key is used for both encrypting and decrypting the data;
  • In the United States, AES was announced by the NIST as U.S. FIPS PUB 197 (FIPS 197) on November 26, 2001. This announcement followed a five-year standardization process in which fifteen competing designs were presented and evaluated, before the Rijndael cipher was selected as the most suitable;
  • AES became effective as a federal government standard on May 26, 2002 after approval by the Secretary of Commerce. AES is included in the ISO/IEC 18033-3 standard. AES is available in many different encryption packages, and is the first (and only) publicly accessible cipher approved by the National Security Agency (NSA) for top secret information when used in an NSA approved cryptographic module.

Image 1

The attached is a Java Maven project. I used Eclipse Java IDE for Web Developers Version: Mars.2 Release (4.5.2) and Java 8 for the example. If you are not sure how to configure Eclipse to use your desired Java version, you can take a look at this link. If you are not sure how to import Maven projects into Eclipse, you can take a look at this link.

  • Java has a built-in support for AES in the "javax.crypto.*" packages, which come with any recent versions of JRE or JDK;
  • The "AESHelper.java" class in the project wraps the "javax.crypto.Cipher" class to provide an easy to use interface for encryption and decryption operations. The "AESKeystore.java" class in the project is the place where we can generate and keep the encryption key;
  • The "Test1Correctness.java", "Test2Perfornace.java", and "Test3Symmetric.java" classes are 3 unit test classes. We can use the unit test classes to check out how the "javax.crypto.Cipher" class performs.

This note is intended to answer the following questions. If you do not have time to look at the code and the unit tests, you can directly go to the summary section to check out the answers.

  • Is it easy to use AES to encrypt and decrypt data?
  • How to generate a symmetric key and an IV for the AES algorithm?
  • How fast is AES when encrypting data?
  • Is there a speed difference between encrypting and decrypting the same data?
  • How big is the encrypted data compared to the original data?
  • How secure is AES encryption?

The "AESKeystore" and "AESHelper" Classes

To generate and keep the encryption key and the initialization vector (IV), I created the "AESKeystore" class.

Java
package com.song.example.aes;
    
import java.security.SecureRandom;
    
public class AESKeystore {
    private byte[] key;
    private byte[] iv;
    
    public byte[] getKey() { return key; }
    public byte[] getIV() { return iv; }
    
    private AESKeystore(byte[] key, byte[] iv) {
        this.key = key;
        this.iv = iv;
    }
    
    public static AESKeystore create() {
        // Hard-coded to 16 * 8 = 128 bit
        // To use 32 * 8 = 256 bit, you need to download
        // Java Cryptography Extension (JCE) Unlimited Strength
        // for the java version
        byte[] key = new byte[16];
        byte[] iv = new byte[16];
        
        SecureRandom rnd = new SecureRandom();
        rnd.nextBytes(key);
        rnd.nextBytes(iv);
        
        return create(key, iv);
    }
    
    public static AESKeystore create(byte[] key, byte[] iv) {
        return new AESKeystore(key, iv);
    }
}
  • We can create an instance of the "AESKeystore" class by calling either of the overloaded "create()" methods. If the key and the IV is passed in as parameters, the "create()" method will use them, otherwise it will generate them using the "java.security.SecureRandom" class;
  • We can use any random byte arrays as the key and the IV. To make them random enough, we should use the "java.security.SecureRandom" class to generate the random bytes. The "java.util.Random" class cannot generate the bytes that are random enough for cryptographic purposes;
  • The AES standard specifies that the key size can be 128, 192 or 256 bits. But if we want to use a key size larger than 128 bits, we need to install the "Java Cryptography Extension (JCE) Unlimited Strength" for the JRE/JDK;
  • The size of the IV matches that AES encryption block size, which is always 128 bits/16 bytes.

The "AESHelper" class wraps the "javax.crypto.Cipher" class to provide an easy to use interface for encryption and decryption operations.

Java
package com.song.example.aes;
    
import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
    
public class AESHelper {
    private final String ALGORITHM = "AES/CBC/PKCS5PADDING";
    private final String STR_ENCODE = "UTF-8";
        
    private SecretKeySpec key;
    private IvParameterSpec iv;
        
    public AESHelper(AESKeystore keystore) throws Exception {
        key = new SecretKeySpec(keystore.getKey(), "AES");
        iv = new IvParameterSpec(keystore.getIV());
    }
        
    private Cipher getCipherInstance(int mode) throws Exception {
        Cipher aesCipher = Cipher.getInstance(ALGORITHM);
        aesCipher.init(mode, key, iv);
        
        return aesCipher;
    }
    
    public byte[] encryptBytes(byte[] data) throws Exception {
        return getCipherInstance(Cipher.ENCRYPT_MODE).doFinal(data);
    }
    
    public byte[] decryptBytes(byte[] encrypted) throws Exception {
        return getCipherInstance(Cipher.DECRYPT_MODE).doFinal(encrypted);
    }
    
    public byte[] encryptString(String data) throws Exception {
        return encryptBytes(data.getBytes(STR_ENCODE));
    }
    
    public String decryptString(byte[] encrypted) throws Exception {
        return new String(decryptBytes(encrypted), STR_ENCODE);
    }
}
  • The "encryptBytes" and "decryptBytes" methods can be used to encrypt and decrypt byte arrays;
  • The "encryptString" and "decryptString" methods can be used to encrypt and decrypt strings;
  • The "javax.crypto.Cipher" class can be used to encrypt and decrypt data with many different algorithms. But this wrapper class hard-coded the algorithm to "AES/CBC/PKCS5PADDING", which means AES algorithm with "CBC" blocking mode.

Unit Test 1 - Does AES Work?

This may sound like a stupid question. AES should of course work. But let us at least test it once anyway. All the unit tests in this example are written on top of "TestNG". You can issue an "mvn clean install" to run all the unit tests in the project. If you want to run the unit tests one by one in Eclipse, you can go though "Help" -> "Eclipse Marketplace" and search "TestNG" to install the "TestNG for Eclipse" plugin.

Image 2

Java
package com.song.example.aes;
    
import java.util.Arrays;
import java.util.Random;
    
import org.testng.Assert;
import org.testng.annotations.BeforeTest;
import org.testng.annotations.Test;
    
@Test
public class Test1Correctness {
    private AESKeystore keystore;
    
    @BeforeTest
    public void init() { keystore = AESKeystore.create(); }
    
    @Test(description = "testBinaryEncryption()")
    public void testBinaryEncryption() {
        byte[] data = new byte[1024];
        new Random().nextBytes(data);
    
        byte[] encrypted = null;
        byte[] decrypted = null;
        
        try {
            encrypted = new AESHelper(keystore).encryptBytes(data);
        } catch(Exception e) {
            Assert.fail("Failed to encrypt the data");
        }
        
        try {
            decrypted = new AESHelper(keystore).decryptBytes(encrypted);
        } catch(Exception e) {
            Assert.fail("Failed to decrypt the data");
        }
        
        Assert.assertEquals(decrypted.length, data.length);
        Assert.assertTrue(Arrays.equals(data, decrypted));        
    }
    
    @Test(description = "testStringEncryption()")
    public void testStringEncryption() {
        final String data = "This is the test string";
        
        byte[] encrypted = null;
        String decrypted = null;
        
        try {
            encrypted = new AESHelper(keystore).encryptString(data);
        } catch(Exception e) {
            Assert.fail("Failed to encrypt the data");
        }
        
        try {
            decrypted = new AESHelper(keystore).decryptString(encrypted);
        } catch(Exception e) {
            Assert.fail("Failed to decrypt the data");
        }
        
        Assert.assertEquals(decrypted.length(), data.length());
        Assert.assertEquals(decrypted, data);
    }
}
  • The test method "testBinaryEncryption()" uses a randomly generated byte array to test the "encryptBytes()" and "decryptBytes()" methods. At the end, it compares the decrypted data and the original byte array to verify that the AES algorithm works correctly on byte arrays;
  • The test method "testStringEncryption()" works the same way as the "testBinaryEncryption()" method. It uses a string for the test and check if the decrypted string matches the original one.

Image 3

Run the unit test, we can see that both tests pass. It should be able to convince us that the AES algorithm can encrypt the data, and reliably decrypt it back to the original state.

Unit Test 2 - Encryption Performance

To get an idea on the encryption performance of the AES algorithm, let us take a look at the "Test2Performance" class.

Java
package com.song.example.aes;
    
import java.util.Random;
import java.util.concurrent.TimeUnit;
    
import org.testng.Assert;
import org.testng.annotations.BeforeTest;
import org.testng.annotations.Test;
    
import com.google.common.base.Stopwatch;
    
@Test
public class Test2Perfornace {
    private AESKeystore keystore;
    
    @BeforeTest
    public void init() { keystore = AESKeystore.create(); }
    
    @Test(description = "testEncryptionPerformance()")
    public void testEncryptionPerformance() {
        byte[] data0 = new byte[1 * 1024];
        byte[] data1 = new byte[10 * 1024 * 1024];
        
        Random rnd = new Random();
        rnd.nextBytes(data0);
        rnd.nextBytes(data1);
        
        AESHelper helper = null;
        try {
            helper = new AESHelper(keystore);
        } catch (Exception e) {
            Assert.fail("Failed to create an AESHelper");
        }
        
        try {
            Stopwatch sw = Stopwatch.createStarted();
            helper.encryptBytes(data0);
            sw.stop();
            System.out.println(String.format("%1$d bytes - %2$dms",
                    data0.length, sw.elapsed(TimeUnit.MILLISECONDS)));
            
            sw.reset().start();
            helper.encryptBytes(data1);
            sw.stop();
            System.out.println(String.format("%1$d bytes - %2$dms",
                    data1.length, sw.elapsed(TimeUnit.MILLISECONDS)));
            
        } catch(Exception e) {
            Assert.fail("Failed to encrypt the data");
        }
    }
}
  • In the test method, "testEncryptionPerformance()", we have two randomly generated byte arrays, one is of length 1K, the other is of length 10M.
  • We will encrypt both the byte arrays to see how fast the algorithm can finish the work.
  • The time used by the encryption operation is measured by the "com.google.common.base.Stopwatch" class.

Image 4

  • Running the test, we can find that encrypting 1K of data takes 173ms, but encrypting 10M of data takes 58ms.
  • You may be surprised by this result. But it makes sense because the "javax.crypto.Cipher" class needs a little "warm up" when it is first used in the program. After the short warm up, the encryption performance is very impressive. You should be convinced that you can actually store a decent sized novel with 10M bytes of data.

Unit Test 3 - Decryption Performance

The unit test class "testPerormanceSymmetricity" is used to give us some idea on how the decryption performance compares with the encryption performance.

Java
package com.song.example.aes;
    
import java.util.Arrays;
import java.util.Random;
import java.util.concurrent.TimeUnit;
    
import org.testng.Assert;
import org.testng.annotations.BeforeTest;
import org.testng.annotations.Test;
    
import com.google.common.base.Stopwatch;
    
@Test
public class Test3Symmetric {
    private AESKeystore keystore;
    
    @BeforeTest
    public void init() { keystore = AESKeystore.create(); }
    
    @Test(description = "testPerormanceSymmetricity()")
    public void testPerormanceSymmetricity() {
        byte[] dummy = new byte[1 * 1024];
        byte[] data = new byte[10 * 1024 * 1024];
        
        Random rnd = new Random();
        rnd.nextBytes(data);
        
        AESHelper helper = null;
        try {
            helper = new AESHelper(keystore);
        } catch (Exception e) {
            Assert.fail("Failed to create an AESHelper");
        }
        
        try {
            // Warm up the "javax.crypto.Cipher"
            helper.encryptBytes(dummy);
            
            Stopwatch sw = Stopwatch.createStarted();
            byte[] encrypted = helper.encryptBytes(data);
            sw.stop();
            System.out.println(String.format("Encryption: %1$d bytes - %2$dms",
                    data.length, sw.elapsed(TimeUnit.MILLISECONDS)));
            
            sw.reset().start();
            byte[] decrypted = helper.decryptBytes(encrypted);
            System.out.println(String.format("Decryption: %1$d bytes - %2$dms",
                    encrypted.length, sw.elapsed(TimeUnit.MILLISECONDS)));
            
            System.out.println(String.format("Encrypted length = %1$d, decrypted length = %2$d",
                    encrypted.length, decrypted.length));
            
            Assert.assertTrue(Arrays.equals(data, decrypted));
            
        } catch(Exception e) {
            Assert.fail("Failed to encrypt the data");
        }
    }    
}
  • The test method "testPerormanceSymmetricity()" first warms up the "javax.crypto.Cipher" class.
  • It then performs both encryption and decryption on 10M of data.
  • It also prints out the length of the encrypted and decrypted byte array length.

Image 5

  • Running the test, we can find that the time spent on encrypting and decrypting the same amount of data is comparable after the "javax.crypto.Cipher" class is warmed up.
  • We can also find that the size of the encrypted data is almost the same as the decrypted data, but 16 bytes longer.

How Secure is AES?

There are two ways to crack an AES key, namely the brutal force approach and the systematic approach. We can take a look at this link to get an idea how difficult it is to crack a 128 bit key by brutal force.

Image 6

Regarding to the systematic approach, we can take a look at the Wikipedia link, which has the following sentence.

As for now, there are no known practical attacks that would allow anyone to read correctly implemented AES encrypted data.

Summary

  • Is it easy to use AES to encrypt and decrypt data?
    • Yes, Java has a built-in support for AES by its "javax.crypto.Cipher" class. In Microsoft .NET, it has a similar support.
  • How to generate a symmetric key and an IV for the AES algorithm?
    • We can use any random byte arrays as the key and the IV.
    • To make them random enough, we should use the "java.security.SecureRandom" class to generate the random bytes. The "java.util.Random" class cannot generate the bytes that are random enough for cryptographic purposes;
    • The AES standard specifies that the key size can be 128, 192 or 256 bits. But if we want to use a key size larger than 128 bits, we need to install the "Java Cryptography Extension (JCE) Unlimited Strength" for the JRE/JDK;
    • The size of the IV matches that AES encryption block size, which is always 128 bits/16 bytes.
  • How fast is AES when encrypting data?
    • The AES algorithm is very efficient, which can encrypt 1M of data in just a few milliseconds on my computer.
  • Is there a speed difference between encrypting and decrypting the same data?
    • The speed to encrypt and decrypt the same data is roughly the same.
  • How big is the encrypted data compared to the original data?
    • The encrypted data is roughly the same size as the original data. It can be a little longer (16 bytes), but not much.
  • How secure is AES encryption?
    • AES algorithm is very secure;
    • As any encryption algorithm, if you lose your key, you lose everything. So the key should be tightly protected.

Points of Interest

  • This note has an example and some observations on symmetric encryption by the Advanced Encryption Standard, which is also called AES.
  • I hope you like my postings and I hope this note can help you one way or the other.

History

  • 3/15/2016- First revision

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
United States United States
I have been working in the IT industry for some time. It is still exciting and I am still learning. I am a happy and honest person, and I want to be your friend.

Comments and Discussions

 
Questionmessage encryption decryption Pin
Member 1325627212-Jun-17 22:11
Member 1325627212-Jun-17 22:11 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.