views:

154

answers:

3

I'm looking for a way to inspect the contents of a HttpServletResponse to sign them with a MD5 hash.

The pseudocode might look like this

process(Response response, Request request){

defaultProcessingFor(response,request);

dispatcher.handle(response,request);

// Here I want to read the contents of the Response object (now filled with data) to create a MD5 hash with them and add it to a header.
}

Is that possible?

+2  A: 

Yes, that's possible. You need to decorate the response with help of HttpServletResponseWrapper wherein you replace the ServletOutputStream with a custom implementation which writes the bytes to both the MD5 digest and the "original" outputstream. Finally provide an accessor to obtain the final MD5 sum.

Update I just for fun played a bit round it, here's a kickoff example:

The response wrapper:

public class MD5ServletResponse extends HttpServletResponseWrapper {

    private final MD5ServletOutputStream output;
    private final PrintWriter writer;

    public MD5ServletResponse(HttpServletResponse response) throws IOException {
        super(response);
        output = new MD5ServletOutputStream(response.getOutputStream());
        writer = new PrintWriter(output, true);
    }

    public PrintWriter getWriter() throws IOException {
        return writer;
    }

    public ServletOutputStream getOutputStream() throws IOException {
        return output;
    }

    public byte[] getHash() {
        return output.getHash();
    }

}

The MD5 outputstream:

public class MD5ServletOutputStream extends ServletOutputStream {

    private final ServletOutputStream output;
    private final MessageDigest md5;

    {
        try {
            md5 = MessageDigest.getInstance("MD5");
        } catch (NoSuchAlgorithmException e) {
            throw new ExceptionInInitializerError(e);
        }
    }

    public MD5ServletOutputStream(ServletOutputStream output) {
        this.output = output;
    }

    public void write(int i) throws IOException {
        byte[] b = { (byte) i };
        md5.update(b);
        output.write(b, 0, 1);
    }

    public byte[] getHash() {
        return md5.digest();
    }

}

How to use it:

// Wrap original response with it:
MD5ServletResponse md5response = new MD5ServletResponse(response);

// Now just use md5response instead or response, e.g.:
dispatcher.handle(request, md5response);

// Then get the hash, e.g.:
byte[] hash = md5response.getHash();
StringBuilder hashAsHexString = new StringBuilder(hash.length * 2);
for (byte b : hash) {
    hashAsHexString.append(String.format("%02x", b));
}
System.out.println(hashAsHexString); // Example af28cb895a479397f12083d1419d34e7.
BalusC
@Pablo: Do you also need to sign the headers?
Thilo
@Thilo just the content would be OK
Pablo Fernandez
@BalusC don't know still if that's the approach I'm gonna take but that class `HttpServletResponseWrapper` is __NICE__ (+1)
Pablo Fernandez
I actually went with other solution: passing a PrintWriter instead of a HttpServletResponse and having my "actions" write on it. But this is a nice solution and would be better in most cases.
Pablo Fernandez
A: 

What are you trying to do?

You might be better off looking at a standard message format and wrap the contents of your response in such a message, and sign that. OAuth comes to mind.

Also, if you enable SSL, the client can be sure that the contents of the response have not been tampered with.

Thilo
I have a working app. I want to add a signature - hash to the response that assures the content was not modified in transit.
Pablo Fernandez
@Pablo: maybe just turn on SSL?
Thilo
@Thilo sadly that's not a choice :(
Pablo Fernandez
If something is modifying the content, are you confident it won't replace the MD5 digest you are returning with a new one, too? The classic approach here requires some secret that only you and the recipient know. For example, a MD5 digest seed string that is privately established by the recipient ahead of time.
Dilum Ranatunga
@Dilum I do that already. It was irrelevant for the actual question and that's why i didn't say it before
Pablo Fernandez
A: 

Technically, the term "signature" is reserved to, well, signatures, and hash functions do not compute those.

To ensure that data was not altered in transit, with a hash function, then you must have a secure out-of-band way of transmitting the hash value; adding the hash value within the HTTP headers will not do, because anybody able to alter the transmitted data may recompute the hash at will, and alter the HTTP headers as it sees fit.

With cryptography, you can "concentrate" that secure out-of-band transmission into a reusable key. If client and server have a shared secret value, unknown to the supposed attacker, then the acronym is MAC, as in "Message Authentication Code"; a usual MAC is HMAC.

In many practical situations, a MAC cannot be used, because a MAC requires a shared secret, and a secret which shared too many times is not really secret anymore. Every secret holder has the power to recompute MAC. If every client knows the secret, then basically it is not a secret and it is safe to assume that the attacker also knows it. Hence, you can go a step further and use digital signatures (real ones, those which use RSA, DSS, ECDSA...) in which the server uses a private key (which only the server knows) and the clients know only of the corresponding public key. Knowledge of the public key is enough to verify signatures, but not to produce new ones, and the private key cannot be recomputed from the public key (although they are mathematically linked to each other). However, implementing a digital signature and using it properly is much more difficult than it is usually assumed; your best bet is then to use an already debugged protocol, with existing implementations, and that protocol happens to be called "SSL".

The point here is that without SSL, chances are that whatever you do will not deter a determined attacker; it will just use CPU cycles and network bandwidth, and give you a warm fuzzy feeling.

Thomas Pornin
Both client and server share a common _secret_. The MD5 is then _signed_ using that secret (in fact I also use the HMAC-SHA algorithm for signing). I didn't specify those because it didn't have to do with the question per se.
Pablo Fernandez