/* This code is part of Freenet. It is distributed under the GNU General * Public License, version 2 (or at your option any later version). See * http://www.gnu.org/ for further details of the GPL. */ package freenet.crypt; import java.nio.ByteBuffer; import java.security.InvalidAlgorithmParameterException; import java.security.InvalidKeyException; import java.security.MessageDigest; import javax.crypto.Mac; import javax.crypto.SecretKey; import javax.crypto.spec.IvParameterSpec; import org.bouncycastle.crypto.generators.Poly1305KeyGenerator; import freenet.support.Fields; /** * The MessageAuthCode class will generate the Message Authentication Code of a given set * of bytes using a secret key. It can also verify * @author unixninja92 * Suggested MACType to use: Poly1305AES */ public final class MessageAuthCode { private final MACType type; private final Mac mac; private final SecretKey key; private IvParameterSpec iv; /** * Creates an instance of MessageAuthCode that will use the specified algorithm and * key. If that algorithms requires an IV it will generate one or use the passed in IV * @param type The MAC algorithm to use * @param key The key to use * @param genIV Does an IV need to be generated if type requires one * @param iv The iv to be used. Can be null if none provided or required. * @throws InvalidKeyException */ private MessageAuthCode(MACType type, SecretKey key, boolean genIV, IvParameterSpec iv) throws InvalidKeyException{ this.type = type; mac = type.get(); this.key = key; try { if(type.ivlen != -1){; checkPoly1305Key(key.getEncoded()); if(genIV){ genIV(); } else{ setIV(iv); } mac.init(key, this.iv); } else{ mac.init(key); } }catch (InvalidAlgorithmParameterException e) { throw new IllegalArgumentException(e); // Definitely a bug ... } } /** * Creates an instance of MessageAuthCode that will use the specified algorithm and * key. Must not be used on algorithms that require an IV, as a specified key but a random IV * is probably not useful for an HMAC. * @param type The MAC algorithm to use * @param cryptoKey The key to use * @throws InvalidKeyException */ public MessageAuthCode(MACType type, SecretKey cryptoKey) throws InvalidKeyException { this(type, cryptoKey, false, null); } /** * Creates an instance of MessageAuthCode that will use the specified algorithm and * key which is converted from a byte[] to a SecretKey. Must not be used on algorithms that * require an IV, as a specified key but a random IV is probably not useful for an HMAC. * @param type The MAC algorithm to use * @param cryptoKey The key to use * @throws InvalidKeyException */ public MessageAuthCode(MACType type, byte[] cryptoKey) throws InvalidKeyException { this(type, KeyGenUtils.getSecretKey(type.keyType, cryptoKey)); } /** * Creates an instance of MessageAuthCode that will use the specified algorithm and * key which is converted from a ByteBuffer to a SecretKey. If that algorithms requires * an IV it will generate one. * @param type The MAC algorithm to use * @param cryptoKey The key to use * @throws InvalidKeyException */ public MessageAuthCode(MACType type, ByteBuffer cryptoKey) throws InvalidKeyException { this(type, Fields.copyToArray(cryptoKey)); } /** * Creates an instance of MessageAuthCode that will use the specified algorithm and * will generate a key (and an IV if necessary). * @param type The MAC algorithm to * @throws InvalidKeyException */ public MessageAuthCode(MACType type) throws InvalidKeyException{ this(type, KeyGenUtils.genSecretKey(type.keyType), true, null); } /** * Creates an instance of MessageAuthCode that will use the specified algorithm with * the specified key and iv. The specified algorithm must require an iv. * @param key They key to be used * @param iv The iv to be used * @throws InvalidKeyException * @throws InvalidAlgorithmParameterException */ public MessageAuthCode(MACType type, SecretKey key, IvParameterSpec iv) throws InvalidKeyException, InvalidAlgorithmParameterException{ this(type, key, false, iv); } /** * Creates an instance of MessageAuthCode that will use the specified algorithm with * the specified key and iv. The specified algorithm must require an iv. * @param key They key to be used as a byte[] * @param iv The iv to be used * @throws InvalidKeyException * @throws InvalidAlgorithmParameterException */ public MessageAuthCode(MACType type, byte[] key, IvParameterSpec iv) throws InvalidKeyException, InvalidAlgorithmParameterException{ this(type, KeyGenUtils.getSecretKey(type.keyType, key), iv); } /** * Checks to make sure the provided key is a valid Poly1305 key * @param encodedKey Key to check */ private final void checkPoly1305Key(byte[] encodedKey){ if(type != MACType.Poly1305AES){ throw new UnsupportedTypeException(type); } Poly1305KeyGenerator.checkKey(encodedKey); } /** * Adds the specified byte to the buffer of bytes to be used for MAC generation * @param input The byte to add */ public final void addByte(byte input){ mac.update(input); } /** * Adds the specified byte arrays to the buffer of bytes to be used for MAC generation * @param input The byte[]s to add */ public final void addBytes(byte[]... input){ for(byte[] b: input){ if(b == null){ throw new NullPointerException(); } mac.update(b); } } /** * Adds the remaining bytes from a ByteBuffer to the buffer of bytes * to be used for MAC generation. The bytes read from the ByteBuffer will be from * input.position() to input.remaining(). Upon return, the ByteBuffer's * .position() will be equal to .remaining() and .remaining() will * stay unchanged. * @param input The ByteBuffer to be added */ public final void addBytes(ByteBuffer input){ mac.update(input); } /** * Adds the specified portion of the byte[] passed in to the buffer * of bytes to be used for MAC generation * @param input The byte to add * @param offset What byte to start at * @param len How many bytes after offset to add to buffer */ public final void addBytes(byte[] input, int offset, int len){ if(input == null){ throw new NullPointerException(); } mac.update(input, offset, len); } /** * Generates the MAC of all the bytes in the buffer added with the * addBytes methods. The buffer is then cleared after the MAC has been * generated. * @return The Message Authentication Code. Will have a backing array and array offset 0, so * you can call array() on it if you really must. */ public final ByteBuffer genMac(){ return ByteBuffer.wrap(mac.doFinal()); } /** * Generates the MAC of only the specified bytes. The buffer is cleared before * processing the input to ensure that no extra data is included. Once the MAC * has been generated, the buffer is cleared again. * @param input * @return The Message Authentication Code */ public final ByteBuffer genMac(byte[]... input){ mac.reset(); addBytes(input); return genMac(); } /** * Generates the MAC of only the specified bytes. The buffer is cleared before * processing the input to ensure that no extra data is included. Once the MAC * has been generated, the buffer is cleared again. * @param input * @return The Message Authentication Code */ public final ByteBuffer genMac(ByteBuffer input){ mac.reset(); addBytes(input); return genMac(); } /** * Verifies that the two MAC addresses passed are equivalent. * @param mac1 First MAC to be verified * @param mac2 Second MAC to be verified * @return Returns true if the MACs match, otherwise false. */ public final static boolean verify(byte[] mac1, byte[] mac2){ /* * An April 2015 patch prevented null input from throwing. JVMs without that patch will * throw, so the change is included here for consistent behavior. * * http://hg.openjdk.java.net/jdk8u/jdk8u/jdk/rev/10929#l8.13 */ if (mac1 == mac2) { return true; } if (mac1 == null || mac2 == null) { return false; } return MessageDigest.isEqual(mac1, mac2); } /** * Verifies that the two MAC addresses passed are equivalent. The two ByteBuffer's will both * be emptied. * @param mac1 First MAC to be verified * @param mac2 Second MAC to be verified * @return Returns true if the MACs match, otherwise false. */ public final static boolean verify(ByteBuffer mac1, ByteBuffer mac2){ // Must be constant time, or as close as we can return MessageDigest.isEqual(Fields.copyToArray(mac1), Fields.copyToArray(mac2)); } /** * Generates the MAC of the byte arrays provided and checks to see if that MAC * is the same as the one passed in. The buffer is cleared before processing the * input to ensure that no extra data is included. Once the MAC has been * generated, the buffer is cleared again. * @param otherMac The MAC to check * @param data The data to check the MAC against * @return Returns true if it is a match, otherwise false. */ public final boolean verifyData(byte[] otherMac, byte[]... data){ return verify(Fields.copyToArray(genMac(data)), otherMac); } /** * Generates the MAC of the byte arrays provided and checks to see if that MAC * is the same as the one passed in. The buffer is cleared before processing the * input to ensure that no extra data is included. Once the MAC has been * generated, the buffer is cleared again. * @param otherMac The MAC to check * @param data The data to check the MAC against * @return Returns true if it is a match, otherwise false. */ public final boolean verifyData(ByteBuffer otherMac, ByteBuffer data){ return verify(genMac(data), otherMac); } /** * Gets the key being used * @return Returns the key as a SecretKey */ public final SecretKey getKey(){ return key; } /** * Gets the IV being used. Only works with algorithms that support IVs. * @return Returns the iv as a IvParameterSpec */ public final IvParameterSpec getIv() { if(type.ivlen == -1){ throw new UnsupportedTypeException(type); } return iv; } /** * Changes the current iv to the provided iv. Only works with algorithms that support IVs. * @param iv The new iv to use as IvParameterSpec * @throws InvalidAlgorithmParameterException */ public final void setIV(IvParameterSpec iv) throws InvalidAlgorithmParameterException{ if(type.ivlen == -1){ throw new UnsupportedTypeException(type); } this.iv = iv; try { mac.init(key, iv); } catch (InvalidKeyException e) { throw new IllegalArgumentException(e); // Definitely a bug ... } } /** * Generates a new IV to be used. Only works with algorithms that support IVs. * @return The generated IV */ public final IvParameterSpec genIV() { if(type.ivlen == -1){ throw new UnsupportedTypeException(type); } try { setIV(KeyGenUtils.genIV(type.ivlen)); } catch (InvalidAlgorithmParameterException e) { throw new IllegalArgumentException(e); // Definitely a bug ... } return this.iv; } }