从HTTP POST计算HMAC而不将整个请求主体复制到内存中

时间:2013-02-12 17:07:02

标签: java cryptography inputstream hmac

在我的应用程序中,我想计算HTTP POST请求正文的HMAC。因此,此输入现在需要处理两个操作:请求解析和HMAC计算。

如何在不将整个请求复制到缓冲区(String,ByteArrayInputStream等)的情况下实现此目的?

我很高兴解析入站请求正文,即使我稍后因HMAC标题无效而拒绝请求。

1 个答案:

答案 0 :(得分:1)

通过包装入站InputStream,可以在解析器处理它们时观察字节。然后,最后,可以计算和比较HMAC。

这是我今天整理的一个实现:

package com.drewnoakes.crypto;

import com.drewnoakes.util;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.io.IOException;
import java.io.InputStream;
import java.security.InvalidKeyException;
import java.security.Key;
import java.security.NoSuchAlgorithmException;

/**
 * An implementation of {@link InputStream} that builds a MAC by observing bytes as they pass through another stream.
 * <p/>
 * This implementation does not support mark/reset or seek, as all bytes of the request must be processed in order.
 * 
 * @author Drew Noakes http://drewnoakes.com
 */
public class HMACValidationStream extends InputStream
{
    private static final String HASH_ALGORITHM = "HmacSHA256";

    private final InputStream _inputStream;
    private final Mac _mac;

    public HMACValidationStream(@NotNull InputStream inputStream, @NotNull byte[] hmacSecret) throws NoSuchAlgorithmException, InvalidKeyException
    {
        _inputStream = inputStream;

        Key secretKeySpec = new SecretKeySpec(hmacSecret, HASH_ALGORITHM);

        _mac = Mac.getInstance(secretKeySpec.getAlgorithm());
        _mac.init(secretKeySpec);
    }

    /**
     * Calculates whether the built-up HMAC matches the provided one (commonly from an HTTP request header.)
     */
    public boolean matches(@NotNull String providedHMAC)
    {
        byte[] hmac = _mac.doFinal();

        String expectedHMAC = Convert.bytesToHex(hmac);
        return expectedHMAC.equals(providedHMAC);
    }

    //////////////////////////////////////////

    @Override
    public int read() throws IOException
    {
        int i = _inputStream.read();
        if (i != -1)
            _mac.update((byte)i);
        return i;
    }

    @Override
    public int read(byte[] b) throws IOException
    {
        int i = _inputStream.read(b);
        if (i != -1)
            _mac.update(b, 0, i);
        return i;
    }

    @Override
    public int read(byte[] b, int off, int len) throws IOException
    {
        int i = _inputStream.read(b, off, len);
        if (i != -1)
            _mac.update(b, off, i);
        return i;
    }

    @Override
    public long skip(long n) throws IOException
    {
        throw new IOException("Not supported");
    }

    @Override
    public int available() throws IOException
    {
        return _inputStream.available();
    }

    @Override
    public void close() throws IOException
    {
        _inputStream.close();
    }

    @Override
    public void mark(int readlimit)
    {}

    @Override
    public void reset() throws IOException
    {
        throw new IOException("Not supported");
    }

    @Override
    public boolean markSupported()
    {
        return false;
    }
}

Convert类:

package com.drewnoakes.util;

/**
 * Common, generic value conversions.
 *
 * @author Drew Noakes http://drewnoakes.com
 */
public class Convert
{
    private static final char[] _hexArray = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'};

    @NotNull
    public static String bytesToHex(@NotNull byte[] bytes)
    {
        char[] output = new char[bytes.length * 2];
        for (int i = 0, o = 0; i < bytes.length; i++) {
            byte v = bytes[i];
            output[o++] = _hexArray[(v >>> 4) & 0x0F];
            output[o++] = _hexArray[v & 0x0F];
        }
        return new String(output);
    }

    @NotNull
    public static byte[] hexToBytes(@NotNull String hex)
    {
        return hexToBytes(hex.toCharArray());
    }

    @NotNull
    public static byte[] hexToBytes(@NotNull char[] hex)
    {
        if (hex.length % 2 != 0)
            throw new IllegalArgumentException("Must pass an even number of characters.");

        int length = hex.length >> 1;
        byte[] raw = new byte[length];
        for (int o = 0, i = 0; o < length; o++) {
            raw[o] = (byte) ((getHexCharValue(hex[i++]) << 4)
                            | getHexCharValue(hex[i++]));
        }
        return raw;
    }

    public static byte getHexCharValue(char c)
    {
        if (c >= '0' && c <= '9')
            return (byte) (c - '0');
        if (c >= 'A' && c <= 'F')
            return (byte) (10 + c - 'A');
        if (c >= 'a' && c <= 'f')
            return (byte) (10 + c - 'a');
        throw new IllegalArgumentException("Invalid hex character");
    }
}