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.
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.
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
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
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;
}
}