在我的应用程序中,我想计算HTTP POST 请求正文的HMAC 。所以这个输入现在需要处理两个操作:请求解析和HMAC计算。
如何在不将整个请求复制到缓冲区(字符串、ByteArrayInputStream 等)的情况下实现这一点?
我很乐意解析入站请求正文,即使我稍后会因为 HMAC 标头无效而拒绝该请求。
在我的应用程序中,我想计算HTTP POST 请求正文的HMAC 。所以这个输入现在需要处理两个操作:请求解析和HMAC计算。
如何在不将整个请求复制到缓冲区(字符串、ByteArrayInputStream 等)的情况下实现这一点?
我很乐意解析入站请求正文,即使我稍后会因为 HMAC 标头无效而拒绝该请求。
通过包装 inbound 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");
}
}