Tuesday, September 10, 2013

Encrypt URL parameters using AES in Java using Spring MVC

Why on earth would you want to do this...  Well, this is one of those crazy requirements that come up with limited resources.

Well.. I do charity work for a 5013C, which has limited resources and one of the requirements they asked of me was to be able to embed data in a static url..  Normally, this would just be a request parameter and my day would proceed on in a rather bland fashion.

This time, it was unique in that some of the data was sensitive (It applied a discount code to the price of a few items) - so manipulating that data would be impactful to the operational integrity of the application. So, how do you secure data when all you have is a url string (SSL and Post options were off the table as this was being embedded as a link in numerous other websites).

So, I decided to use a json payload and encrypt it using AES.. base64 encode it, urlEncode it... and dump that value along with the initial vector into the url as params. It was a rather small payload with only a few sensitive fields, so it was effective, yet crude..

I would be interested in knowing how others would handle this using only a url String making a RESTful call to a server.

The basic requirements:

1. It must be a RESTful url..  All the data is contained in the url.
2. Flat files and database stores are out of the question.. no way to persist data (This was the kicker for me)
3. It has to be embeddable and a direct call.. no middle man url's, etc..

I hope someone reads this and presents a graceful answer.. I don't feel that mine was the best answer, but given my tools in my toolbox.. I didn't see a better way.

Below is the code that does the lifting..  It's fairly straight forward:

Encrypting:
1. The object is serialized to a byte[]
2. Create a Cipher and a random initial vector
3. Hash the key using SHA-256 then trim it to 128-bit for the key
4. Base64 encode the encrypted value
5. Encode the Base64 value to a url friendly format
6. Return the encoded values to include as request parameters in the link

The link will look something like:
http://www.foo.com/view?a=PM90%2B0mzlDLpK5NKlG7CP5lgafMzn5qsrHulCRLR9ZkXnq9L5v7SMuEp%2BPWFtdNR1UxHGw%2Fxh7NzZSPtwSKNRCmQe8fNEMkJJuI4lJ3uvg0DrbI9xXALFZpUs6gAbO7SW5UCeHd6SYWMSWnM9agH9Mw13yfiCKNdiWPiW9j%2BhJa0J5lPraqF1x8srdfm48JaMEg1IaHtRLXukH3VjPTN8sGRUE8nzsu%2BGIyRLaHlXNKt5nZ4wAKtd9Dtfmwe4bgX4k7aEUpaYr90nCU67a%2BxX0NtDB6z7n5lkCQ7bQTNzSA%3D&b=Jux7I3tv%2Ff6HXXFrGOozZA%3D%3D

Decrypting:
1. We decode the request param from Base64 into the raw format. (The container takes care of the url encoding conversion.. so this is not a factor in TC Server)
2. Decode the intial vector value from Base64 into the raw format.
3. Configure the cipher using the secret and the initial vector that was passed in the url
4. Decrypt the encrypted data
5. Serialize the decrypted bytes back into the expected object

All this work.. due to a few limitations. Bleh.. But, it was a fun little traversal in cryptography in Java.

Below is a code sample setup to run in Spring MVC:

package com.test.web.controllers;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInput;
import java.io.ObjectInputStream;
import java.io.ObjectOutput;
import java.io.ObjectOutputStream;
import java.net.URLEncoder;
import java.security.MessageDigest;
import java.security.SecureRandom;
import java.util.HashMap;
import java.util.Map;
import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.codec.binary.Base64;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
@Controller
public class EncryptedViewController {
private String keyString = "adkj@#$02#@adflkj)(*jlj@#$#@LKjasdjlkj<.,mo@#$@#kljlkdsu343";
protected final Logger logger = LoggerFactory.getLogger(this.getClass());
@RequestMapping(value = "/view", method = RequestMethod.GET)
public String getDencryptedUrlView(@RequestParam String d, @RequestParam String v) throws Exception {
// The data goes out urlEncoded.. but comes in decoded (No Need to url decode on the request)
Object obj = decryptObject(d, v);
System.out.println("Object Decrypted: "+obj.toString());
return "INDEX";
}
@RequestMapping(value = "/{id}", method = RequestMethod.GET)
public String getEncryptedUrl(@PathVariable String id) {
Map<String,String> map = new HashMap<String,String>();
map.put("id",id);
map.put("topSecret","Waffles are tasty");
try {
String[] encrypted = encryptObject(map);
// url may differ.. based upon project initial context
System.out.println("http://localhost:8080/view?d="+encrypted[0]+"&v="+encrypted[1]);
}catch(Exception e) {
//logger.debug("Unable to encrypt view id: "+id, e);
}
return "INDEX";
}
/**
* Encrypts and encodes the Object and IV for url inclusion
* @param input
* @return
* @throws Exception
*/
private String[] encryptObject(Object obj) throws Exception {
ByteArrayOutputStream stream = new ByteArrayOutputStream();
ObjectOutput out = new ObjectOutputStream(stream);
try {
// Serialize the object
out.writeObject(obj);
byte[] serialized = stream.toByteArray();
// Setup the cipher and Init Vector
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
byte[] iv = new byte[cipher.getBlockSize()];
new SecureRandom().nextBytes(iv);
IvParameterSpec ivSpec = new IvParameterSpec(iv);
// Hash the key with SHA-256 and trim the output to 128-bit for the key
MessageDigest digest = MessageDigest.getInstance("SHA-256");
digest.update(keyString.getBytes());
byte[] key = new byte[16];
System.arraycopy(digest.digest(), 0, key, 0, key.length);
SecretKeySpec keySpec = new SecretKeySpec(key, "AES");
// encrypt
cipher.init(Cipher.ENCRYPT_MODE, keySpec, ivSpec);
// Encrypt & Encode the input
byte[] encrypted = cipher.doFinal(serialized);
byte[] base64Encoded = Base64.encodeBase64(encrypted);
String base64String = new String(base64Encoded);
String urlEncodedData = URLEncoder.encode(base64String,"UTF-8");
// Encode the Init Vector
byte[] base64IV = Base64.encodeBase64(iv);
String base64IVString = new String(base64IV);
String urlEncodedIV = URLEncoder.encode(base64IVString, "UTF-8");
return new String[] {urlEncodedData, urlEncodedIV};
}finally {
stream.close();
out.close();
}
}
/**
* Decrypts the String and serializes the object
* @param base64Data
* @param base64IV
* @return
* @throws Exception
*/
public Object decryptObject(String base64Data, String base64IV) throws Exception {
// Decode the data
byte[] encryptedData = Base64.decodeBase64(base64Data.getBytes());
// Decode the Init Vector
byte[] rawIV = Base64.decodeBase64(base64IV.getBytes());
// Configure the Cipher
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
IvParameterSpec ivSpec = new IvParameterSpec(rawIV);
MessageDigest digest = MessageDigest.getInstance("SHA-256");
digest.update(keyString.getBytes());
byte[] key = new byte[16];
System.arraycopy(digest.digest(), 0, key, 0, key.length);
SecretKeySpec keySpec = new SecretKeySpec(key, "AES");
cipher.init(Cipher.DECRYPT_MODE, keySpec, ivSpec);
// Decrypt the data..
byte[] decrypted = cipher.doFinal(encryptedData);
// Deserialize the object
ByteArrayInputStream stream = new ByteArrayInputStream(decrypted);
ObjectInput in = new ObjectInputStream(stream);
Object obj = null;
try {
obj = in.readObject();
}finally {
stream.close();
in.close();
}
return obj;
}
}
view raw gistfile1.java hosted with ❤ by GitHub

Wednesday, September 4, 2013

Dojo App - loading a template from a url.



One of the features that I would love to see added to the dojo app project is the ability to support restful templates. Currently, it only loads templates from inside a static directory versus fetching a template from a restful endpoint.

So, If you want to be able to add this functionality, below is an example of how to do it. It currently works with the 1.9 dojo version, and is an extension of the current functionality, so all current features will continue to work.

The idea is that if a template is prefixed with the dollar sign - such as:  template: "$/foo/view",  then it will use
dojo/request to do a get on that end point for the returned content and load it into the view. Otherwise, it will  process it normally.

A normal config.json file would look something like:

  "views": {
        "header": {
            "constraint": "top",
            "controller": "foo/controllers/header",
            "template": "foo/views/header.html",  
            "dependencies": [
                "dijit/layout/BorderContainer",
                "dijit/layout/ContentPane",
                "dojox/app/widgets/Container",
                "ontrac/widgets/Navigation"
            ]
        },
        "dashboard": {
            "constraint": "middle",
            "controller": "foo/controllers/dashboard",
            "template": "$/foo/view/dashboard",        
            "dependencies": [
                "dijit/layout/BorderContainer",
                "dijit/layout/ContentPane",
                "dojox/app/widgets/Container"
            ]
        }

Below is the Gist of the code - Hope it helps, as it wasn't difficult to add the functionality. I don't make guarantees on cross domain requests.. I've not tested that, so if you need that functionality, you'll need to handle the issues/security that is involved in that.

Make sure you include the following dependencies in the AMD loader when overloading:

"dojox/app/View", "dojo/Deferred", "dojo/when", "dojo/_base/lang", "dojo/request"


// Overload the view loader..
View.prototype._loadTemplate = function(){
var localView = this;
// summary:
// load view HTML template and dependencies.
// tags:
// private
//
var tpl = this.template;
var deps = this.dependencies?this.dependencies:[];
if(this.templateString){
return true;
}else if (tpl.indexOf("$") == 0){
var url = tpl.substring(1, tpl.length);
var requestDeferred = request.get(url);
requestDeferred.then(function _success(data) {
localView.templateString = data;
},
function _error(error) {
localView.templateString = "<div>Error: "+error+"</div>";
});
var loadViewDeferred = new Deferred();
when(requestDeferred, lang.hitch(this, function() {
loadViewDeferred.resolve(this);
}));
return loadViewDeferred;
}else{
if(tpl){
if(tpl.indexOf("./") == 0){
tpl = "app/"+tpl;
}
deps = deps.concat(["dojo/text!"+tpl]);
}
var def = new Deferred();
if(deps.length > 0){
var requireSignal;
try{
requireSignal = require.on("error", lang.hitch(this, function(error){
if(def.isResolved() || def.isRejected()){
return;
}
if(error.info[0] && error.info[0].indexOf(this.template) >= 0 ){
def.resolve(false);
requireSignal.remove();
}
}));
require(deps, function(){
def.resolve.call(def, arguments);
requireSignal.remove();
});
}catch(e){
def.resolve(false);
if(requireSignal){
requireSignal.remove();
}
}
}else{
def.resolve(true);
}
var loadViewDeferred = new Deferred();
when(def, lang.hitch(this, function(){
this.templateString = this.template ? arguments[0][arguments[0].length - 1] : "<div></div>";
loadViewDeferred.resolve(this);
}));
return loadViewDeferred;
}
};
view raw gistfile1.txt hosted with ❤ by GitHub