001    package edu.harvard.deas.hyperenc;
002    
003    import java.io.Serializable;
004    import java.security.InvalidKeyException;
005    import java.security.Key;
006    import java.util.ArrayList;
007    import java.util.Arrays;
008    import java.util.List;
009    
010    import javax.crypto.Mac;
011    import javax.crypto.spec.SecretKeySpec;
012    
013    /**
014     * Represents a MAC computed with the Hyper-Encryption Message Authentication
015     * Code (HEMAC) algorithm. Also includes methods to compute and verify HEMACs.
016     * <p>
017     * HEMAC makes use of HMAC, specified in <a
018     * href="http://www.ietf.org/rfc/rfc2104.txt">RFC 2104</a>. HEMAC makes use of a
019     * cryptographic hash function <i>h</i> with output length <i>L</i>, and a
020     * secret key <i>K</i> of length <i>2L</i>. The HEMAC of a message <i>x</i> is
021     * computed as follows: <blockquote>HEMAC(<i>K</i>, <i>x</i>) =
022     * <i>K</i>[0:<i>L</i>] ^ HMAC<sub>h</sub>(<i>K</i>[<i>L</i>:2<i>L</i>],
023     * <i>x</i>)</blockquote> where ^ represents the XOR operation, and
024     * <i>K</i>[<i>i</i>:<i>j</i>] represents the <i>i</i>th through <i>j</i>th bits
025     * of <i>K</i> (including <i>i</i>, but excluding <i>j</i>).
026     * <p>
027     * HEMAC uses a similar naming scheme as HMAC: for example, if the underlying
028     * hash function <i>h</i> is SHA1, then the resulting HEMAC is called
029     * HEMAC-SHA1.
030     */
031    public class HyperMAC implements Serializable {
032      private static final long serialVersionUID = 1L;
033      
034      private final List<Integer> blockList;
035      private final byte[] mac;
036    
037      /**
038       * Constructs a new HEMAC blob.
039       * 
040       * @param blockList
041       *        List of encryption block IDs used as the HEMAC key.
042       * @param mac
043       *        The HEMAC value itself.
044       */
045      public HyperMAC(List<Integer> blockList, byte[] mac) {
046        this.blockList = new ArrayList<Integer>(blockList);
047        this.mac = Arrays.copyOf(mac, mac.length);
048      }
049    
050      /**
051       * Returns the list of IDs that identify the encryption blocks used for the
052       * key to this HEMAC.
053       * 
054       * @return the encryption block ID list
055       */
056      public List<Integer> getBlockList() {
057        return new ArrayList<Integer>(blockList);
058      }
059      
060      /**
061       * Returns the HEMAC value.
062       * @return the HEMAC value, computed as described in this class
063       */
064      public byte[] getMac() {
065        return Arrays.copyOf(mac, mac.length);
066      }
067      
068      
069      /**
070       * Computes the HEMAC of <code>msg</code> using the given secret key and HMAC
071       * instance.
072       * 
073       * @param hmac
074       *        A <code>Mac</code> instance representing the HMAC to be used by this
075       *        HEMAC. This <code>Mac</code> object will be initialized using
076       *        {@link Mac#init(Key)}, so any state it currently holds will be lost.
077       * @param key
078       *        The secret key for this HEMAC. Must be exactly twice the length of
079       *        the length of the output of <code>hmac</code>.
080       * @param msg
081       *        The message for which to compute the HEMAC.
082       * @return the HEMAC of <code>msg</code>, using the specified HMAC and key.
083       * @throws InvalidKeyException
084       *         if the provided key has incorrect length
085       */
086      public static final byte[] computeMac(Mac hmac, byte[] key, byte[] msg)
087          throws InvalidKeyException {
088        if (hmac.getMacLength() * 2 != key.length)
089          throw new InvalidKeyException("Key length is " + key.length
090              + " bytes, but must be " + 2 * hmac.getMacLength() + " bytes.");
091        
092        // Use the second half of the key as the key for the HMAC
093        Key hmacKey = new SecretKeySpec(key, key.length/2, key.length/2, "HEMAC");
094        try {
095          hmac.init(hmacKey);
096        } catch (InvalidKeyException e) {
097          throw new IllegalArgumentException("Provided key is invalid for HMAC", e);
098        }
099        byte[] part = hmac.doFinal(msg);
100        
101        // XOR the HMAC against the first half of the key
102        byte[] hemac = new byte[key.length / 2];
103        for (int i = 0; i < key.length / 2; i++)
104          hemac[i] = (byte)(key[i] ^ part[i]);
105        return hemac;
106      }
107    }