/*
* Copyright 2014 Martin Gangl
*
* This library is free software: you can redistribute it and/or modify it under
* the terms of the GNU Lesser General Public License as published by the Free
* Software Foundation, either version 3 of the License, or (at your option) any
* later version.
*
* This library is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
* details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this library. If not, see <http://www.gnu.org/licenses/>.
*/
package eu.bges.crypto;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Random;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import eu.bges.IOUtil;
import eu.bges.MathUtil;
import eu.bges.SysUtil;
import eu.bges.SysUtil.OS;
/**
* Utilites for encryption, random numbers and creating hardware UUIDs.
*
* @author Martin Gangl
*
*/
public final class CryptoUtil {
private static final Logger LOG = LoggerFactory.getLogger(CryptoUtil.class);
private static final String UTF_8 = IOUtil.UTF_8.toString();
public static final String SHA_512 = "SHA-512";
public static final String DEFAULT_ALGORITHM = SHA_512;
public static final String DEFAULT_ENCODING = UTF_8;
/**
* Creates a message digest using SHA-512. The used encoding is UTF-8.
*
* @param message
* string to be hashed
* @return a SHA-512 message digest of the given input or null if an invalid
* input (null) was given
*/
public static String createMD(final String message) {
try {
return createMD(message, SHA_512, UTF_8);
} catch (NoSuchAlgorithmException | UnsupportedEncodingException e) {
LOG.warn(e.getMessage(), e);
}
return null;
}
/**
* Creates a message digest using the specified {@code algorithm} and
* {@code encoding}.
* <p>
* If they are null, {@value #DEFAULT_ALGORITHM} and
* {@value #DEFAULT_ENCODING} are used respectively.
*
* @param message
* string to be hashed
* @param encoding
* to be used in the digest
* @param algorithm
* to be used in the digest
* @return a message digest of the given {@code message} or null if an
* invalid input (null) was given
* @throws NoSuchAlgorithmException
* if an invalid algorithm identifier was given
* @throws UnsupportedEncodingException
* if an invalid encoding identifier was given
*/
public static String createMD(final String message, String algorithm,
String encoding) throws NoSuchAlgorithmException,
UnsupportedEncodingException {
String retVal = null;
if (message != null) {
if (algorithm == null) {
algorithm = DEFAULT_ALGORITHM;
}
if (encoding == null) {
encoding = DEFAULT_ENCODING;
}
final MessageDigest md = MessageDigest.getInstance(algorithm);
final byte[] hash = md.digest(message.getBytes(encoding));
if (hash != null) {
final StringBuilder sB = new StringBuilder();
for (final byte b : hash) {
sB.append(b);
}
retVal = sB.toString();
}
}
return retVal;
}
/**
* Create a unique computer id (UUID). If possible the id will be strong
* (hardware dependent), otherwise the id will be weak (operating system,
* system architecture).
*
* @return the {@link ComputerUUID} either strong or weak
*/
public static ComputerUUID getComputerUUID() {
boolean uuidIsStrong = true;
String uuidStr = getStrongComputerUUID();
// Fall back to the software uuid
if (uuidStr == null) {
uuidStr = getWeakComputerUUID();
uuidIsStrong = false;
}
final ComputerUUID uuid = new ComputerUUID(uuidIsStrong, uuidStr);
return uuid;
}
/**
* @param value
* of which the CRC should be created
* @return the CRC16 of the given String in binary encoding or null if a
* null input was given
*/
public static String getCRC16(final String value) {
String retVal = null;
if (value != null) {
final CRC16 crc = new CRC16();
try {
crc.update(value.getBytes(UTF_8));
retVal = Long.toBinaryString(crc.getValue());
if (retVal.length() < 16) {
final int preLength = 16 - retVal.length();
final StringBuilder sB = new StringBuilder();
for (int i = 0; i < preLength; i++) {
sB.append('0');
}
retVal = sB.toString() + retVal;
}
} catch (final UnsupportedEncodingException e) {
LOG.warn(e.getMessage(), e);
}
}
return retVal;
}
/**
* @param value
* of which the CRC should be created
* @return the CRC16 of the given String in hex encoding or null if a null
* input was given
*/
public static String getCRC16Hex(final String value) {
String retVal = null;
if (value != null) {
final String binCRC = getCRC16(value);
retVal = MathUtil.binToHex(binCRC, false);
if (retVal.length() == 3) {
System.out.println("here");
}
}
return retVal;
}
/**
* @param length
* of the random string
* @return a random String (only containing characters A-E and 0-9); null if
* a length lesser or equal to 0 was given
*/
public static String getRandomHexString(final int length) {
String retVal = null;
if (length >= 0) {
final Random random = new Random();
final char[] CHARS = { '0', '1', '2', '3', '4', '5', '6', '7', '8',
'9', '0', 'A', 'B', 'C', 'D', 'E' };
final StringBuilder sB = new StringBuilder();
for (int i = 0; i < length; i++) {
sB.append(CHARS[(int) (random.nextDouble() * length)]);
}
retVal = sB.toString();
}
return retVal;
}
/**
* @param length
* of the random string
* @return a random String (only containing characters a-z, A-Z and 0-9);
* null if a length lesser or equal to 0 was given
*/
public static String getRandomString(final int length) {
String retVal = null;
if (length >= 0) {
final Random random = new Random();
final char[] SPECIAL_CHARS = { 33, 34, 35, 36, 37, 38, 39, 40, 41,
42, 43, 44, 45, 46, 47, 58, 59, 60, 61, 62, 63, 64, 91, 92,
93, 94, 95, 96, 123, 124, 125, 126 };
final int LAST_INDEX = 94; // Set last valid ASCII index here
final int CORRECTION = 33; // Set correction value here (ASCII
// characters < 33 are not supported as
// key character in most applications)
final StringBuilder sB = new StringBuilder();
int i = 0;
char current;
while (i < length) {
boolean valid = true;
current = (char) (random.nextInt(LAST_INDEX) + CORRECTION);
for (final char element : SPECIAL_CHARS) {
if (element == current) {
valid = false;
}
}
if (valid) {
sB.append(current);
i++;
}
}
retVal = sB.toString();
}
return retVal;
}
/**
* @return an unique id identifying the computer based on it's hardware if a
* supported operating system (Windows, Linux, Mac OSX) is
* installed; null otherwise
*/
private static String getStrongComputerUUID() {
final OS currOs = SysUtil.getOS();
final String uuid;
switch (currOs) {
case WINDOWS:
uuid = getStrongComputerUUIDwindows();
break;
case MAC:
uuid = getStrongComputerUUIDosx();
break;
case LINUX:
uuid = getStrongComputerUUIDlinux();
break;
default:
uuid = null;
}
return uuid;
}
/**
* @return a hardware dependent UUID of this computer if running on a Linux
* platform; null otherwise
*/
private static String getStrongComputerUUIDlinux() {
return null;
// TODO implement
}
/**
* @return a hardware dependent UUID of this computer if running on Mac OSX
* platform; null otherwise
*/
private static String getStrongComputerUUIDosx() {
return null;
// TODO implement
}
/**
* @return a hardware dependent UUID of this computer if running on a
* Microsoft Windows platform; null otherwise
*/
private static String getStrongComputerUUIDwindows() {
String retVal = null;
File mbFile;
try {
mbFile = File.createTempFile(getRandomString(8), ".vbs");
final File hdFile = File.createTempFile(getRandomString(8), ".vbs");
final Process mbP = Runtime.getRuntime().exec(
"cscript //NoLogo " + mbFile.getPath());
final Process hdP = Runtime.getRuntime().exec(
"cscript //NoLogo " + hdFile.getPath());
try (final FileWriter mbFw = new FileWriter(mbFile);
final FileWriter hdFw = new FileWriter(hdFile);
final BufferedReader mbInput = new BufferedReader(
new InputStreamReader(mbP.getInputStream()));
final BufferedReader hdInput = new BufferedReader(
new InputStreamReader(hdP.getInputStream()));) {
final String drive = "C";
final StringBuilder mbId = new StringBuilder();
final StringBuilder hdId = new StringBuilder();
mbFile.deleteOnExit();
hdFile.deleteOnExit();
final String mbVbs = "Set objWMIService = GetObject(\"winmgmts:\\\\.\\root\\cimv2\")\n"
+ "Set colItems = objWMIService.ExecQuery _ \n"
+ " (\"Select * from Win32_BaseBoard\") \n"
+ "For Each objItem in colItems \n"
+ " Wscript.Echo objItem.SerialNumber \n"
+ " exit for ' do the first cpu only! \n"
+ "Next \n";
final String hdVbs = "Set objFSO = CreateObject(\"Scripting.FileSystemObject\")\n"
+ "Set colDrives = objFSO.Drives\n"
+ "Set objDrive = colDrives.item(\""
+ drive
+ "\")\n"
+ "Wscript.Echo objDrive.SerialNumber";
mbFw.write(mbVbs);
hdFw.write(hdVbs);
mbFw.close();
hdFw.close();
int c;
while ((c = mbInput.read()) != -1) {
mbId.append(c);
}
mbInput.close();
while ((c = hdInput.read()) != -1) {
hdId.append(c);
}
hdInput.close();
retVal = mbId.toString() + "_" + hdId.toString();
}
} catch (final IOException e) {
LOG.warn(e.getMessage(), e);
}
return retVal;
}
/**
* @return a id of this computer based on platform independent accessible
* data
*/
private static String getWeakComputerUUID() {
final String osName = System.getProperty("os.name");
final String osArch = System.getProperty("os.arch");
String hostName = null;
try {
final InetAddress addr = InetAddress.getLocalHost();
hostName = addr.getHostName();
} catch (final UnknownHostException e) {
hostName = "";
}
return (hostName + "_" + osName + "_" + osArch);
}
}