/* * The MIT License (MIT) * * Copyright (c) 2015 Lachlan Dowding * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all * copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ package permafrost.tundra.security; import permafrost.tundra.io.MarkableInputStream; import permafrost.tundra.io.InputStreamHelper; import permafrost.tundra.lang.BytesHelper; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; import java.nio.charset.Charset; import java.security.DigestInputStream; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.AbstractMap; import java.util.Map; public final class MessageDigestHelper { /** * The default message digest algorithm name. */ public static final String DEFAULT_ALGORITHM_NAME = "SHA-512"; /** * The default message digest algorithm. */ public static final MessageDigest DEFAULT_ALGORITHM = getDefault(); /** * Disallow instantiation of this class. */ private MessageDigestHelper() {} /** * Returns the default MessageDigest algorithm. * * @return The default MessageDigest algorithm. */ private static MessageDigest getDefault() { MessageDigest messageDigest = null; try { messageDigest = MessageDigest.getInstance(DEFAULT_ALGORITHM_NAME); } catch(NoSuchAlgorithmException ex) { throw new RuntimeException(ex); } return messageDigest; } /** * Returns a MessageDigest object for the given named algorithm. * * @param algorithmName The algorithm to use when calculating a message digest. * @return A MessageDigest that implements the given algorithm. * @throws NoSuchAlgorithmException If there is no provider for the named algorithm. */ public static MessageDigest normalize(String algorithmName) throws NoSuchAlgorithmException { return algorithmName == null ? DEFAULT_ALGORITHM : MessageDigest.getInstance(algorithmName); } /** * Returns either the given algorithm if not null, or the default algorithm if given null. * * @param algorithm The algorithm to be normalized. * @return Either the given algorithm if not null, or the default algorithm. * @throws NoSuchAlgorithmException If there is no provider for the default algorithm. */ public static MessageDigest normalize(MessageDigest algorithm) throws NoSuchAlgorithmException { return algorithm == null ? DEFAULT_ALGORITHM : algorithm; } /** * Calculates a message digest for the given data using the given algorithm. * * @param algorithm The algorithm to use when calculating the message digest. * @param data The data to calculate the digest for. * @param charset The character set used to encode the text when data is provided as a string. * @return The message digest calculated for the given data using the given algorithm. * @throws IOException If an I/O exception occurs reading from the stream. * @throws NoSuchAlgorithmException If there is no provider for the default algorithm. */ public static Map.Entry<? extends Object, byte[]> digest(MessageDigest algorithm, Object data, Charset charset) throws IOException, NoSuchAlgorithmException { Map.Entry<? extends Object, byte[]> output = null; if (data instanceof String) { byte[] digest = digest(algorithm, (String)data, charset); output = new AbstractMap.SimpleImmutableEntry<String, byte[]>((String)data, digest); } else if (data instanceof byte[]) { byte[] digest = digest(algorithm, (byte[])data); output = new AbstractMap.SimpleImmutableEntry<byte[], byte[]>((byte[])data, digest); } else if (data instanceof InputStream) { output = digest(algorithm, (InputStream)data); } return output; } /** * Calculates a message digest for the given data using the given algorithm. * * @param algorithm The algorithm to use when calculating the message digest. * @param data The data to calculate the digest for. * @return The message digest calculated for the given data using the given algorithm. * @throws IOException If an I/O exception occurs reading from the stream. * @throws NoSuchAlgorithmException If there is no provider for the default algorithm. */ public static Map.Entry<? extends InputStream, byte[]> digest(MessageDigest algorithm, InputStream data) throws IOException, NoSuchAlgorithmException { if (data == null) return null; Map.Entry<? extends InputStream, byte[]> output; if (data instanceof ByteArrayInputStream) { // treat ByteArrayInputStream classes differently, to optimise performance in this case output = digest(algorithm, (ByteArrayInputStream)data); } else { algorithm = normalize(algorithm); DigestInputStream digestInputStream = new DigestInputStream(data, algorithm); data = new MarkableInputStream(digestInputStream); // generating the digest relies on the fact that the MarkableInputStream constructor reads the entire stream byte[] digest = digestInputStream.getMessageDigest().digest(); // turn off the digest function now that its complete digestInputStream.on(false); output = new AbstractMap.SimpleImmutableEntry<InputStream, byte[]>(data, digest); } return output; } /** * Calculates a message digest for the given data using the given algorithm. * * @param algorithm The algorithm to use when calculating the message digest. * @param data The data to calculate the digest for. * @return The message digest calculated for the given data using the given algorithm. * @throws IOException If an I/O exception occurs reading from the stream. * @throws NoSuchAlgorithmException If there is no provider for the default algorithm. */ public static Map.Entry<ByteArrayInputStream, byte[]> digest(MessageDigest algorithm, ByteArrayInputStream data) throws IOException, NoSuchAlgorithmException { if (data == null) return null; byte[] bytes = InputStreamHelper.read(data, false); data.reset(); return new AbstractMap.SimpleImmutableEntry<ByteArrayInputStream, byte[]>(data, digest(algorithm, bytes)); } /** * Calculates a message digest for the given data using the given algorithm. * * @param algorithm The algorithm to use when calculating the message digest. * @param data The data to calculate the digest for. * @return The message digest calculated for the given data using the given algorithm. * @throws NoSuchAlgorithmException If there is no provider for the default algorithm. */ public static byte[] digest(MessageDigest algorithm, byte[] data) throws NoSuchAlgorithmException { return data == null ? null : normalize(algorithm).digest(data); } /** * Calculates a message digest for the given data using the given algorithm. * * @param algorithm The algorithm to use when calculating the message digest. * @param data The data to calculate the digest for. * @param charset The character set to use when encoding the text in data. * @return The message digest calculated for the given data using the given algorithm. * @throws NoSuchAlgorithmException If there is no provider for the default algorithm. */ public static byte[] digest(MessageDigest algorithm, String data, Charset charset) throws NoSuchAlgorithmException { return digest(algorithm, BytesHelper.normalize(data, charset)); } }