413 lines
14 KiB
Java
413 lines
14 KiB
Java
/*
|
|
* Copyright (C) 2015 The Guava Authors
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
package com.google.common.hash;
|
|
|
|
import static com.google.common.base.Charsets.UTF_8;
|
|
import static com.google.common.io.BaseEncoding.base16;
|
|
|
|
import com.google.common.collect.ImmutableSet;
|
|
import com.google.common.collect.ImmutableTable;
|
|
import com.google.common.collect.Table;
|
|
import com.google.common.testing.NullPointerTester;
|
|
import java.security.Key;
|
|
import java.util.Arrays;
|
|
import javax.crypto.Mac;
|
|
import javax.crypto.SecretKey;
|
|
import javax.crypto.spec.SecretKeySpec;
|
|
import junit.framework.TestCase;
|
|
import sun.security.jca.ProviderList;
|
|
import sun.security.jca.Providers;
|
|
|
|
/**
|
|
* Tests for the MacHashFunction.
|
|
*
|
|
* @author Kurt Alfred Kluever
|
|
*/
|
|
public class MacHashFunctionTest extends TestCase {
|
|
|
|
private static final ImmutableSet<String> INPUTS = ImmutableSet.of("", "Z", "foobar");
|
|
|
|
private static final SecretKey MD5_KEY =
|
|
new SecretKeySpec("secret key".getBytes(UTF_8), "HmacMD5");
|
|
private static final SecretKey SHA1_KEY =
|
|
new SecretKeySpec("secret key".getBytes(UTF_8), "HmacSHA1");
|
|
private static final SecretKey SHA256_KEY =
|
|
new SecretKeySpec("secret key".getBytes(UTF_8), "HmacSHA256");
|
|
private static final SecretKey SHA512_KEY =
|
|
new SecretKeySpec("secret key".getBytes(UTF_8), "HmacSHA512");
|
|
|
|
// From http://docs.oracle.com/javase/7/docs/technotes/guides/security/StandardNames.html#Mac
|
|
private static final ImmutableTable<String, SecretKey, HashFunction> ALGORITHMS =
|
|
new ImmutableTable.Builder<String, SecretKey, HashFunction>()
|
|
.put("HmacMD5", MD5_KEY, Hashing.hmacMd5(MD5_KEY))
|
|
.put("HmacSHA1", SHA1_KEY, Hashing.hmacSha1(SHA1_KEY))
|
|
.put("HmacSHA256", SHA256_KEY, Hashing.hmacSha256(SHA256_KEY))
|
|
.put("HmacSHA512", SHA512_KEY, Hashing.hmacSha512(SHA512_KEY))
|
|
.build();
|
|
|
|
public void testNulls() {
|
|
NullPointerTester tester =
|
|
new NullPointerTester().setDefault(String.class, "HmacMD5").setDefault(Key.class, MD5_KEY);
|
|
tester.testAllPublicConstructors(MacHashFunction.class);
|
|
tester.testAllPublicInstanceMethods(new MacHashFunction("HmacMD5", MD5_KEY, "toString"));
|
|
}
|
|
|
|
public void testHashing() throws Exception {
|
|
for (String stringToTest : INPUTS) {
|
|
for (Table.Cell<String, SecretKey, HashFunction> cell : ALGORITHMS.cellSet()) {
|
|
String algorithm = cell.getRowKey();
|
|
SecretKey key = cell.getColumnKey();
|
|
HashFunction hashFunc = cell.getValue();
|
|
assertMacHashing(HashTestUtils.ascii(stringToTest), algorithm, key, hashFunc);
|
|
}
|
|
}
|
|
}
|
|
|
|
@AndroidIncompatible // sun.security
|
|
public void testNoProviders() {
|
|
ProviderList providers = Providers.getProviderList();
|
|
Providers.setProviderList(ProviderList.newList());
|
|
try {
|
|
Hashing.hmacMd5(MD5_KEY);
|
|
fail("expected ISE");
|
|
} catch (IllegalStateException expected) {
|
|
} finally {
|
|
Providers.setProviderList(providers);
|
|
}
|
|
}
|
|
|
|
public void testMultipleUpdates() throws Exception {
|
|
Mac mac = Mac.getInstance("HmacSHA1");
|
|
mac.init(SHA1_KEY);
|
|
mac.update("hello".getBytes(UTF_8));
|
|
mac.update("world".getBytes(UTF_8));
|
|
|
|
assertEquals(
|
|
HashCode.fromBytes(mac.doFinal()),
|
|
Hashing.hmacSha1(SHA1_KEY)
|
|
.newHasher()
|
|
.putString("hello", UTF_8)
|
|
.putString("world", UTF_8)
|
|
.hash());
|
|
}
|
|
|
|
public void testMultipleUpdatesDoFinal() throws Exception {
|
|
Mac mac = Mac.getInstance("HmacSHA1");
|
|
mac.init(SHA1_KEY);
|
|
mac.update("hello".getBytes(UTF_8));
|
|
mac.update("world".getBytes(UTF_8));
|
|
|
|
assertEquals(
|
|
HashCode.fromBytes(mac.doFinal("!!!".getBytes(UTF_8))),
|
|
Hashing.hmacSha1(SHA1_KEY)
|
|
.newHasher()
|
|
.putString("hello", UTF_8)
|
|
.putString("world", UTF_8)
|
|
.putString("!!!", UTF_8)
|
|
.hash());
|
|
}
|
|
|
|
public void testCustomKey() throws Exception {
|
|
SecretKey customKey =
|
|
new SecretKey() {
|
|
@Override
|
|
public String getAlgorithm() {
|
|
return "HmacMD5";
|
|
}
|
|
|
|
@Override
|
|
public byte[] getEncoded() {
|
|
return new byte[8];
|
|
}
|
|
|
|
@Override
|
|
public String getFormat() {
|
|
return "RAW";
|
|
}
|
|
};
|
|
assertEquals(
|
|
"ad262969c53bc16032f160081c4a07a0",
|
|
Hashing.hmacMd5(customKey)
|
|
.hashString("The quick brown fox jumps over the lazy dog", UTF_8)
|
|
.toString());
|
|
}
|
|
|
|
public void testBadKey_emptyKey() throws Exception {
|
|
SecretKey badKey =
|
|
new SecretKey() {
|
|
@Override
|
|
public String getAlgorithm() {
|
|
return "HmacMD5";
|
|
}
|
|
|
|
@Override
|
|
public byte[] getEncoded() {
|
|
return null;
|
|
}
|
|
|
|
@Override
|
|
public String getFormat() {
|
|
return "RAW";
|
|
}
|
|
};
|
|
try {
|
|
Hashing.hmacMd5(badKey);
|
|
fail();
|
|
} catch (IllegalArgumentException expected) {
|
|
} catch (NullPointerException toleratedOnAndroid) {
|
|
// TODO(cpovirk): In an ideal world, we'd check here that we're running on Android.
|
|
}
|
|
}
|
|
|
|
public void testEmptyInputs() throws Exception {
|
|
String knownOutput = "8cbf764cbe2e4623d99a41354adfd390";
|
|
|
|
Mac mac = Mac.getInstance("HmacMD5");
|
|
mac.init(MD5_KEY);
|
|
assertEquals(knownOutput, HashCode.fromBytes(mac.doFinal()).toString());
|
|
assertEquals(knownOutput, Hashing.hmacMd5(MD5_KEY).newHasher().hash().toString());
|
|
}
|
|
|
|
public void testEmptyInputs_mixedAlgorithms() throws Exception {
|
|
String knownOutput = "8cbf764cbe2e4623d99a41354adfd390";
|
|
|
|
Mac mac = Mac.getInstance("HmacMD5");
|
|
mac.init(SHA1_KEY);
|
|
assertEquals(knownOutput, HashCode.fromBytes(mac.doFinal()).toString());
|
|
assertEquals(knownOutput, Hashing.hmacMd5(SHA1_KEY).newHasher().hash().toString());
|
|
}
|
|
|
|
public void testKnownInputs() throws Exception {
|
|
String knownOutput = "9753980fe94daa8ecaa82216519393a9";
|
|
String input = "The quick brown fox jumps over the lazy dog";
|
|
|
|
Mac mac = Mac.getInstance("HmacMD5");
|
|
mac.init(MD5_KEY);
|
|
mac.update(input.getBytes(UTF_8));
|
|
assertEquals(knownOutput, HashCode.fromBytes(mac.doFinal()).toString());
|
|
assertEquals(knownOutput, HashCode.fromBytes(mac.doFinal(input.getBytes(UTF_8))).toString());
|
|
assertEquals(knownOutput, Hashing.hmacMd5(MD5_KEY).hashString(input, UTF_8).toString());
|
|
assertEquals(knownOutput, Hashing.hmacMd5(MD5_KEY).hashBytes(input.getBytes(UTF_8)).toString());
|
|
}
|
|
|
|
public void testKnownInputs_mixedAlgorithms() throws Exception {
|
|
String knownOutput = "9753980fe94daa8ecaa82216519393a9";
|
|
String input = "The quick brown fox jumps over the lazy dog";
|
|
|
|
Mac mac = Mac.getInstance("HmacMD5");
|
|
mac.init(SHA1_KEY);
|
|
mac.update(input.getBytes(UTF_8));
|
|
assertEquals(knownOutput, HashCode.fromBytes(mac.doFinal()).toString());
|
|
assertEquals(knownOutput, HashCode.fromBytes(mac.doFinal(input.getBytes(UTF_8))).toString());
|
|
assertEquals(knownOutput, Hashing.hmacMd5(SHA1_KEY).hashString(input, UTF_8).toString());
|
|
assertEquals(
|
|
knownOutput, Hashing.hmacMd5(SHA1_KEY).hashBytes(input.getBytes(UTF_8)).toString());
|
|
}
|
|
|
|
public void testPutAfterHash() {
|
|
Hasher hasher = Hashing.hmacMd5(MD5_KEY).newHasher();
|
|
|
|
assertEquals(
|
|
"9753980fe94daa8ecaa82216519393a9",
|
|
hasher.putString("The quick brown fox jumps over the lazy dog", UTF_8).hash().toString());
|
|
try {
|
|
hasher.putInt(42);
|
|
fail();
|
|
} catch (IllegalStateException expected) {
|
|
}
|
|
}
|
|
|
|
public void testHashTwice() {
|
|
Hasher hasher = Hashing.hmacMd5(MD5_KEY).newHasher();
|
|
|
|
assertEquals(
|
|
"9753980fe94daa8ecaa82216519393a9",
|
|
hasher.putString("The quick brown fox jumps over the lazy dog", UTF_8).hash().toString());
|
|
try {
|
|
hasher.hash();
|
|
fail();
|
|
} catch (IllegalStateException expected) {
|
|
}
|
|
}
|
|
|
|
public void testToString() {
|
|
byte[] keyData = "secret key".getBytes(UTF_8);
|
|
|
|
assertEquals(
|
|
"Hashing.hmacMd5(Key[algorithm=HmacMD5, format=RAW])", Hashing.hmacMd5(MD5_KEY).toString());
|
|
assertEquals(
|
|
"Hashing.hmacMd5(Key[algorithm=HmacMD5, format=RAW])", Hashing.hmacMd5(keyData).toString());
|
|
|
|
assertEquals(
|
|
"Hashing.hmacSha1(Key[algorithm=HmacSHA1, format=RAW])",
|
|
Hashing.hmacSha1(SHA1_KEY).toString());
|
|
assertEquals(
|
|
"Hashing.hmacSha1(Key[algorithm=HmacSHA1, format=RAW])",
|
|
Hashing.hmacSha1(keyData).toString());
|
|
|
|
assertEquals(
|
|
"Hashing.hmacSha256(Key[algorithm=HmacSHA256, format=RAW])",
|
|
Hashing.hmacSha256(SHA256_KEY).toString());
|
|
assertEquals(
|
|
"Hashing.hmacSha256(Key[algorithm=HmacSHA256, format=RAW])",
|
|
Hashing.hmacSha256(keyData).toString());
|
|
|
|
assertEquals(
|
|
"Hashing.hmacSha512(Key[algorithm=HmacSHA512, format=RAW])",
|
|
Hashing.hmacSha512(SHA512_KEY).toString());
|
|
assertEquals(
|
|
"Hashing.hmacSha512(Key[algorithm=HmacSHA512, format=RAW])",
|
|
Hashing.hmacSha512(keyData).toString());
|
|
}
|
|
|
|
private static void assertMacHashing(
|
|
byte[] input, String algorithm, SecretKey key, HashFunction hashFunc) throws Exception {
|
|
Mac mac = Mac.getInstance(algorithm);
|
|
mac.init(key);
|
|
mac.update(input);
|
|
|
|
assertEquals(HashCode.fromBytes(mac.doFinal()), hashFunc.hashBytes(input));
|
|
assertEquals(HashCode.fromBytes(mac.doFinal(input)), hashFunc.hashBytes(input));
|
|
}
|
|
|
|
// Tests from RFC2022: https://tools.ietf.org/html/rfc2202
|
|
|
|
public void testRfc2202_hmacSha1_case1() {
|
|
byte[] key = fillByteArray(20, 0x0b);
|
|
String data = "Hi There";
|
|
|
|
checkSha1("b617318655057264e28bc0b6fb378c8ef146be00", key, data);
|
|
}
|
|
|
|
public void testRfc2202_hmacSha1_case2() {
|
|
byte[] key = "Jefe".getBytes(UTF_8);
|
|
String data = "what do ya want for nothing?";
|
|
|
|
checkSha1("effcdf6ae5eb2fa2d27416d5f184df9c259a7c79", key, data);
|
|
}
|
|
|
|
public void testRfc2202_hmacSha1_case3() {
|
|
byte[] key = fillByteArray(20, 0xaa);
|
|
byte[] data = fillByteArray(50, 0xdd);
|
|
|
|
checkSha1("125d7342b9ac11cd91a39af48aa17b4f63f175d3", key, data);
|
|
}
|
|
|
|
public void testRfc2202_hmacSha1_case4() {
|
|
byte[] key = base16().lowerCase().decode("0102030405060708090a0b0c0d0e0f10111213141516171819");
|
|
byte[] data = fillByteArray(50, 0xcd);
|
|
|
|
checkSha1("4c9007f4026250c6bc8414f9bf50c86c2d7235da", key, data);
|
|
}
|
|
|
|
public void testRfc2202_hmacSha1_case5() {
|
|
byte[] key = fillByteArray(20, 0x0c);
|
|
String data = "Test With Truncation";
|
|
|
|
checkSha1("4c1a03424b55e07fe7f27be1d58bb9324a9a5a04", key, data);
|
|
}
|
|
|
|
public void testRfc2202_hmacSha1_case6() {
|
|
byte[] key = fillByteArray(80, 0xaa);
|
|
String data = "Test Using Larger Than Block-Size Key - Hash Key First";
|
|
|
|
checkSha1("aa4ae5e15272d00e95705637ce8a3b55ed402112", key, data);
|
|
}
|
|
|
|
public void testRfc2202_hmacSha1_case7() {
|
|
byte[] key = fillByteArray(80, 0xaa);
|
|
String data = "Test Using Larger Than Block-Size Key and Larger Than One Block-Size Data";
|
|
|
|
checkSha1("e8e99d0f45237d786d6bbaa7965c7808bbff1a91", key, data);
|
|
}
|
|
|
|
public void testRfc2202_hmacMd5_case1() {
|
|
byte[] key = fillByteArray(16, 0x0b);
|
|
String data = "Hi There";
|
|
|
|
checkMd5("9294727a3638bb1c13f48ef8158bfc9d", key, data);
|
|
}
|
|
|
|
public void testRfc2202_hmacMd5_case2() {
|
|
byte[] key = "Jefe".getBytes(UTF_8);
|
|
String data = "what do ya want for nothing?";
|
|
|
|
checkMd5("750c783e6ab0b503eaa86e310a5db738", key, data);
|
|
}
|
|
|
|
public void testRfc2202_hmacMd5_case3() {
|
|
byte[] key = fillByteArray(16, 0xaa);
|
|
byte[] data = fillByteArray(50, 0xdd);
|
|
|
|
checkMd5("56be34521d144c88dbb8c733f0e8b3f6", key, data);
|
|
}
|
|
|
|
public void testRfc2202_hmacMd5_case4() {
|
|
byte[] key = base16().lowerCase().decode("0102030405060708090a0b0c0d0e0f10111213141516171819");
|
|
byte[] data = fillByteArray(50, 0xcd);
|
|
|
|
checkMd5("697eaf0aca3a3aea3a75164746ffaa79", key, data);
|
|
}
|
|
|
|
public void testRfc2202_hmacMd5_case5() {
|
|
byte[] key = fillByteArray(16, 0x0c);
|
|
String data = "Test With Truncation";
|
|
|
|
checkMd5("56461ef2342edc00f9bab995690efd4c", key, data);
|
|
}
|
|
|
|
public void testRfc2202_hmacMd5_case6() {
|
|
byte[] key = fillByteArray(80, 0xaa);
|
|
String data = "Test Using Larger Than Block-Size Key - Hash Key First";
|
|
|
|
checkMd5("6b1ab7fe4bd7bf8f0b62e6ce61b9d0cd", key, data);
|
|
}
|
|
|
|
public void testRfc2202_hmacMd5_case7() {
|
|
byte[] key = fillByteArray(80, 0xaa);
|
|
String data = "Test Using Larger Than Block-Size Key and Larger Than One Block-Size Data";
|
|
|
|
checkMd5("6f630fad67cda0ee1fb1f562db3aa53e", key, data);
|
|
}
|
|
|
|
private static void checkSha1(String expected, byte[] key, String data) {
|
|
checkSha1(expected, key, data.getBytes(UTF_8));
|
|
}
|
|
|
|
private static void checkSha1(String expected, byte[] key, byte[] data) {
|
|
checkHmac(expected, Hashing.hmacSha1(key), data);
|
|
}
|
|
|
|
private static void checkMd5(String expected, byte[] key, String data) {
|
|
checkMd5(expected, key, data.getBytes(UTF_8));
|
|
}
|
|
|
|
private static void checkMd5(String expected, byte[] key, byte[] data) {
|
|
checkHmac(expected, Hashing.hmacMd5(key), data);
|
|
}
|
|
|
|
private static void checkHmac(String expected, HashFunction hashFunc, byte[] data) {
|
|
assertEquals(HashCode.fromString(expected), hashFunc.hashBytes(data));
|
|
}
|
|
|
|
private static byte[] fillByteArray(int size, int toFillWith) {
|
|
byte[] array = new byte[size];
|
|
Arrays.fill(array, (byte) toFillWith);
|
|
return array;
|
|
}
|
|
}
|