Skip to content

Instantly share code, notes, and snippets.

@turekt
Last active March 24, 2020 19:51
Show Gist options
  • Save turekt/a75d6daec84e1cb1a8f673fa6e39861a to your computer and use it in GitHub Desktop.
Save turekt/a75d6daec84e1cb1a8f673fa6e39861a to your computer and use it in GitHub Desktop.
Guide on how to utilize AES encryption with openssl to communicate with java web service

Communicating with service that processes encrypted requests

Intro

Let's assume that there is a hypothetical java service you need to communicate with:

@RestController
@RequestMapping("/serve")
public class ServiceController implements ApplicationContextAware {

    private static final IvParameterSpec IV = new IvParameterSpec("Initialization01".getBytes());
    private static final SecretKeySpec SECRET_KEY_SPEC = new SecretKeySpec("AesDummyCryptKey".getBytes(), "AES");

    private ApplicationContext ctx;

    @PostMapping
    public ResponseEntity<String> serve(@RequestBody String val) {
        if(val == null || val.isEmpty()) {
            return ResponseEntity.badRequest().body("ERROR: data is empty");
        }

        try {
            Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5PADDING");
            cipher.init(Cipher.DECRYPT_MODE, SECRET_KEY_SPEC, IV);

            String request = new String(cipher.doFinal(Hex.toByteArray(val)));
            switch(request) {
                case "HELLO":
                    return ResponseEntity.ok("Hi there!");
                case "INFO":
                    return ResponseEntity.ok(System.getenv("os.name"));
                case "SHUTDOWN":
                    ((ConfigurableApplicationContext) ctx).close();
                    return ResponseEntity.ok("");
            }
        } catch (Exception ex) {
            return ResponseEntity.badRequest().body("ERROR: " + ex.getMessage());
        }

        return ResponseEntity.badRequest().body("ERROR: unknown request");
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.ctx = applicationContext;
    }
}

NOTE: hexadecimal conversion implementation is provided in the appendix.

This service decrypts each request with AES-128 in CBC mode. Therefore, there is additional effort required to interact with the service.

Problem

You need to communicate with this service and you do not have a compatible client program. Time is of the essence and writing a Java client program from scratch takes time and testing.

Analysis

Let's see what kind of values java outputs. Here is a simple Java program:

// Compile: javac Aes.java
// Run:     java Aes (encrypt|decrypt) VALUE
import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;

public class Aes {

    public static final IvParameterSpec IV = new IvParameterSpec("Initialization01".getBytes());
    public static final SecretKeySpec SECRET_KEY_SPEC = new SecretKeySpec("AesDummyCryptKey".getBytes(), "AES");

    public static String encrypt(String value) {
        try {
            Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5PADDING");
            cipher.init(Cipher.ENCRYPT_MODE, SECRET_KEY_SPEC, IV);
            
            byte[] encrypted = cipher.doFinal(value.getBytes());
            return Hex.toHex(encrypted);
        } catch (Exception ex) {
            ex.printStackTrace();
        }
        
        return null;
    }

    public static String decrypt(String encrypted) {
        try {
            Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5PADDING");
            cipher.init(Cipher.DECRYPT_MODE, SECRET_KEY_SPEC, IV);
            
            byte[] original = cipher.doFinal(Hex.toByteArray(encrypted));
            return new String(original);
        } catch (Exception ex) {
            ex.printStackTrace();
        }
        
        return null;
    }
    
    public static void usage() {
        System.err.println("usage: java Aes (encrypt|decrypt) VALUE");
    }

    public static void main(String[] args) {
        if(args.length != 2) {
            usage();
            return;
        }
        
        switch(args[0]) {
            case "encrypt":
                System.out.println(encrypt(args[1]));
                break;
            case "decrypt":
                System.out.println(decrypt(args[1]));
                break;
            default:
                usage();
        }
    }
}

Here is the test run:

$ java Aes encrypt plaintext
c9448105f80b0b386685cf29e44bf2e4
$ java Aes decrypt c9448105f80b0b386685cf29e44bf2e4
plaintext

Since using curl is the optimal solution, would this be compatible with openssl enc? Let's check openssl enc help:

$ openssl enc -help
Usage: enc [options]
Valid options are:
 -help               Display this summary
 -ciphers            List ciphers
...
 -e                  Encrypt
 -d                  Decrypt
...
 -K val              Raw key, in hex
 -S val              Salt, in hex
 -iv val             IV in hex
...

Do we support AES-128 in CBC mode?

$ openssl enc -ciphers
Supported ciphers:
-aes-128-cbc   ...

So the format of this command is:

openssl enc -aes-128-cbc -K (key-in-hex) -iv (iv-in-hex)
  • convert IV to hex:
$ echo -n "Initialization01" | xxd -p
496e697469616c697a6174696f6e3031
  • convert key to hex:
$ echo -n "AesDummyCryptKey" | xxd -p
41657344756d6d7943727970744b6579

We will also need to convert the openssl output bytes to hex, we can use xxd command for that.

To encrypt use:

$ echo -n "plaintext" | \
  openssl enc -aes-128-cbc -iv "496e697469616c697a6174696f6e3031" -K "41657344756d6d7943727970744b6579" | \
  xxd -p                                            
c9448105f80b0b386685cf29e44bf2e4

To decrypt reverse encryption process:

$ echo -n "c9448105f80b0b386685cf29e44bf2e4" | \
  xxd -p -r | \
  openssl enc -d -aes-128-cbc -iv "496e697469616c697a6174696f6e3031" -K "41657344756d6d7943727970744b6579"
plaintext

Solution

Now we can just pipe everything into curl and communicate with the service:

$ echo -n "HELLO" | \
  openssl enc -aes-128-cbc -iv "496e697469616c697a6174696f6e3031" -K "41657344756d6d7943727970744b6579" | \
  xxd -p | \
  tr -d '\n' | \
  curl -H "Content-Type: application/json" -X POST -d @- "http://localhost:8080/serve"
Hi there!
$ echo -n "SHUTDOWN" | \
  openssl enc -aes-128-cbc -iv "496e697469616c697a6174696f6e3031" -K "41657344756d6d7943727970744b6579" | \
  xxd -p | \
  tr -d '\n' | \
  curl -H "Content-Type: application/json" -X POST -d @- "http://localhost:8080/serve"
curl: (52) Empty reply from server

Appendix

Hex class

// Ref 1: https://stackoverflow.com/a/9855338
// Ref 2: https://stackoverflow.com/a/140861
class Hex {

    private static final byte[] HEX_ARRAY = "0123456789abcdef".getBytes();

    public static String toHex(byte[] bytes) {
        byte[] hexChars = new byte[bytes.length * 2];
        for (int j = 0; j < bytes.length; j++) {
            int v = bytes[j] & 0xFF;
            hexChars[j * 2] = HEX_ARRAY[v >>> 4];
            hexChars[j * 2 + 1] = HEX_ARRAY[v & 0x0F];
        }
        return new String(hexChars);
    }
    
    public static byte[] toByteArray(String s) {
        int len = s.length();
        byte[] data = new byte[len / 2];
        for (int i = 0; i < len; i += 2) {
            data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4)
                                 + Character.digit(s.charAt(i+1), 16));
        }
        return data;
    }
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment