HashUtils.java

/**
 * Portions of this software was developed by employees of the National Institute
 * of Standards and Technology (NIST), an agency of the Federal Government and is
 * being made available as a public service. Pursuant to title 17 United States
 * Code Section 105, works of NIST employees are not subject to copyright
 * protection in the United States. This software may be subject to foreign
 * copyright. Permission in the United States and in foreign countries, to the
 * extent that NIST may hold copyright, to use, copy, modify, create derivative
 * works, and distribute this software and its documentation without fee is hereby
 * granted on a non-exclusive basis, provided that this notice and disclaimer
 * of warranty appears in all copies.
 *
 * THE SOFTWARE IS PROVIDED 'AS IS' WITHOUT ANY WARRANTY OF ANY KIND, EITHER
 * EXPRESSED, IMPLIED, OR STATUTORY, INCLUDING, BUT NOT LIMITED TO, ANY WARRANTY
 * THAT THE SOFTWARE WILL CONFORM TO SPECIFICATIONS, ANY IMPLIED WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND FREEDOM FROM
 * INFRINGEMENT, AND ANY WARRANTY THAT THE DOCUMENTATION WILL CONFORM TO THE
 * SOFTWARE, OR ANY WARRANTY THAT THE SOFTWARE WILL BE ERROR FREE.  IN NO EVENT
 * SHALL NIST BE LIABLE FOR ANY DAMAGES, INCLUDING, BUT NOT LIMITED TO, DIRECT,
 * INDIRECT, SPECIAL OR CONSEQUENTIAL DAMAGES, ARISING OUT OF, RESULTING FROM,
 * OR IN ANY WAY CONNECTED WITH THIS SOFTWARE, WHETHER OR NOT BASED UPON WARRANTY,
 * CONTRACT, TORT, OR OTHERWISE, WHETHER OR NOT INJURY WAS SUSTAINED BY PERSONS OR
 * PROPERTY OR OTHERWISE, AND WHETHER OR NOT LOSS WAS SUSTAINED FROM, OR AROSE OUT
 * OF THE RESULTS OF, OR USE OF, THE SOFTWARE OR SERVICES PROVIDED HEREUNDER.
 */

package gov.nist.secauto.swid.builder.resource;

import org.apache.commons.codec.DecoderException;
import org.apache.commons.codec.binary.Hex;

import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

public class HashUtils {
  private HashUtils() {
    // disable
  }

  /**
   * Converts an array of bytes into a list of bytes.
   * 
   * @param bytes
   *          the array of bytes to convert
   * @return a list of bytes
   */
  public static List<Byte> toList(byte[] bytes) {
    List<Byte> retval = new ArrayList<>(bytes.length);
    for (byte b : bytes) {
      retval.add(b);
    }
    return retval;
  }

  /**
   * Converts a list of bytes into an array of bytes.
   * 
   * @param bytes
   *          a list of bytes
   * @return the array of bytes to convert
   */
  public static byte[] toArray(List<Byte> bytes) {
    byte[] retval = new byte[bytes.size()];
    for (int pos = 0; pos < bytes.size(); pos++) {
      retval[pos] = bytes.get(pos);
    }
    return retval;
  }

  private static final char[] hexArray = "0123456789abcdef".toCharArray();

  /**
   * Converts an array of bytes into a hexadecimal string.
   * 
   * @param bytes
   *          the array of bytes to convert
   * @return a hexadecimal string
   */
  public static String toHexString(byte[] bytes) {
    // Based on example from:
    // http://stackoverflow.com/questions/9655181/how-to-convert-a-byte-array-to-a-hex-string-in-java
    char[] hexChars = new char[bytes.length * 2];
    for (int i = 0; i < bytes.length; i++) {
      int value = bytes[i] & 0xFF;
      hexChars[i * 2] = hexArray[value >>> 4];
      hexChars[i * 2 + 1] = hexArray[value & 0x0F];
    }
    return new String(hexChars);
  }

  /**
   * Converts an list of bytes into a hexadecimal string.
   * 
   * @param bytes
   *          the list of bytes to convert
   * @return a hexadecimal string
   */
  public static String toHexString(List<Byte> bytes) {
    // Based on example from:
    // http://stackoverflow.com/questions/9655181/how-to-convert-a-byte-array-to-a-hex-string-in-java
    char[] hexChars = new char[bytes.size() * 2];
    int pos = 0;
    for (byte b : bytes) {
      int value = b & 0xFF;
      hexChars[pos * 2] = hexArray[value >>> 4];
      hexChars[pos * 2 + 1] = hexArray[value & 0x0F];
      ++pos;
    }
    return new String(hexChars);
  }

  public static byte[] toBytes(String hashHexBytes) {
    try {
      return Hex.decodeHex(hashHexBytes);
    } catch (DecoderException e) {
      throw new RuntimeException(e);
    }
  }

  /**
   * Generates a hash value, in the form of an array of bytes, by digesting a provided input stream
   * based on the provided hash algorithm.
   * 
   * @param algorithm
   *          the hash function to use
   * @param file
   *          the file to read bytes from
   * @return an array of bytes representing a hash value
   * @throws NoSuchAlgorithmException
   *           if the selected hash function is not supported
   * @throws IOException
   *           if an error occured while reading the input stream
   */
  public static byte[] hash(HashAlgorithm algorithm, File file) throws NoSuchAlgorithmException, IOException {
    return hash(algorithm, new BufferedInputStream(new FileInputStream(file)));
  }

  /**
   * Generates a hash value, in the form of an array of bytes, by digesting a provided input stream
   * based on the provided hash algorithm.
   * 
   * @param algorithm
   *          the hash function to use
   * @param is
   *          the input stream to read bytes from
   * @return an array of bytes representing a hash value
   * @throws NoSuchAlgorithmException
   *           if the selected hash function is not supported
   * @throws IOException
   *           if an error occured while reading the input stream
   */
  public static byte[] hash(HashAlgorithm algorithm, InputStream is) throws NoSuchAlgorithmException, IOException {
    MessageDigest digest = MessageDigest.getInstance(algorithm.getName());
    byte[] dataBytes = new byte[1024];
    int nread = 0;

    while ((nread = is.read(dataBytes)) != -1) {
      digest.update(dataBytes, 0, nread);
    }

    return processDigest(algorithm, digest.digest());
  }

  /**
   * Generates a hash value, in the form of an array of bytes, by digesting a provided bytes based on
   * the provided hash algorithm.
   * 
   * @param algorithm
   *          the hash function to use
   * @param bytes
   *          the bytes to digest
   * @return an array of bytes representing a hash value
   * @throws NoSuchAlgorithmException
   *           if the selected hash function is not supported
   */
  public static byte[] hash(HashAlgorithm algorithm, byte[] bytes) throws NoSuchAlgorithmException {
    MessageDigest digest = MessageDigest.getInstance(algorithm.getName());
    digest.update(bytes);

    return processDigest(algorithm, digest.digest());
  }

  /**
   * Processes the provided hash value, truncating the value based on the width of the provided hash
   * algorithm.
   * 
   * @param algorithm
   *          the hash function to use
   * @param mdbytes
   *          the digest bytes to truncate
   * @return an array of bytes representing a hash value based on the width of the provided hash
   *         algorithm
   */
  public static byte[] processDigest(HashAlgorithm algorithm, byte[] mdbytes) {
    int valueLength = algorithm.getValueLength() / 8;
    if (valueLength < mdbytes.length) {
      mdbytes = Arrays.copyOfRange(mdbytes, 0, valueLength);
    }
    return mdbytes;

    // //convert the bytes to hex format
    // StringBuffer hexString = new StringBuffer();
    // for (int i=0;i<mdbytes.length;i++) {
    // hexString.append(Integer.toHexString(0xFF & mdbytes[i]));
    // }
    //
    // return hexString.toString();
  }
}