package de.jeisfeld.augendiagnoselib.util;
import java.security.Key;
import java.security.MessageDigest;
import java.util.Arrays;
import java.util.List;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.spec.SecretKeySpec;
import android.annotation.SuppressLint;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.util.Base64;
import android.util.Log;
import de.jeisfeld.augendiagnoselib.Application;
import de.jeisfeld.augendiagnoselib.R;
/**
* Utility for handling user keys.
*/
public final class EncryptionUtil {
// JAVADOC:OFF
// parameters for encryption
private static Cipher mCipherEncrypt;
private static MessageDigest mMessageDigest;
private static final String DUMMY_HASH = "dummy";
private static final int HASH_LENGTH = 8;
private static final String ALGORITHM = "DES";
// JAVADOC:ON
/**
* Hide default constructor.
*/
private EncryptionUtil() {
throw new UnsupportedOperationException();
}
/**
* Special keys that get are set as valid.
*/
@NonNull
private static final List<String> SPECIAL_KEYS;
/**
* The private key to be used for user key generation.
*/
@NonNull
private static final String KEY;
/**
* Flag to store initialization status.
*/
private static boolean mIsInitialized = false;
static {
KEY = Application.getResourceString(R.string.private_key_string);
String[] specialKeys = Application.getAppContext().getResources().getStringArray(R.array.private_special_keys);
SPECIAL_KEYS = Arrays.asList(specialKeys);
initializeCipher();
}
/**
* Initialize the encryption cipher.
*/
@SuppressLint("TrulyRandom")
private static void initializeCipher() {
try {
mCipherEncrypt = Cipher.getInstance(ALGORITHM);
Key symKey = new SecretKeySpec(Base64.decode(KEY, Base64.DEFAULT), ALGORITHM);
mCipherEncrypt.init(Cipher.ENCRYPT_MODE, symKey);
mMessageDigest = MessageDigest.getInstance("MD5");
mIsInitialized = true;
}
catch (Exception e) {
Log.e(Application.TAG, "Failed to initialize EncryptionUtil", e);
}
}
/**
* Utility method to test generation and validation of a user key.
*
* @param name the user name for which key generation is to be tested.
*/
public static void test(@NonNull final String name) {
String key = createUserKey(name);
Log.i(Application.TAG, "Key: " + key + ". Verified: " + validateUserKey(key));
}
/**
* Validate a user key.
*
* @param key the user key to be validated.
* @return true if the user key is valid.
*/
public static boolean validateUserKey(@Nullable final String key) {
if (key == null || key.length() == 0 || !mIsInitialized) {
return false;
}
if (SPECIAL_KEYS.contains(key)) {
return true;
}
int index = key.lastIndexOf('-');
if (index > 0) {
String name = key.substring(0, index);
String hash = key.substring(index + 1);
return createCryptoHash(name).equals(hash);
}
else {
return false;
}
}
/**
* Generate a user key, which is a concatenation of user name and hash.
*
* @param input the user key without hash.
* @return the user key including hash.
*/
@NonNull
private static String createUserKey(@NonNull final String input) {
return input + "-" + createCryptoHash(input);
}
/**
* Create a cryptographic hash from a String.
*
* @param input the input for creating the hash. (Will be username.)
* @return the cryptographic hash.
*/
@NonNull
private static String createCryptoHash(@NonNull final String input) {
try {
return convertBase64(createHash(encrypt(input))).substring(0, HASH_LENGTH);
}
catch (Exception e) {
return DUMMY_HASH;
}
}
/**
* Create a hash value from an input.
*
* @param input the input for the hash creation.
* @return the hash.
*/
private static byte[] createHash(final byte[] input) {
return mMessageDigest.digest(input);
}
/**
* Do base 64 encoding of a message.
*
* @param bytes the bytes to be encoded.
* @return the base 64 encoded String.
*/
private static String convertBase64(final byte[] bytes) {
return Base64.encodeToString(bytes, Base64.DEFAULT);
}
/**
* Encrypt a String using DES.
*
* @param input the String to be encrypted.
* @return the encrypted String.
* @throws BadPaddingException if this cipher is in decryption mode, and (un)padding has been requested, but the decrypted data is
* not bounded by the appropriate padding bytes
* @throws IllegalBlockSizeException if this cipher is a block cipher, no padding has been requested (only in encryption mode), and the
* total input length of the data processed by this cipher is not a multiple of block size; or if this
* encryption algorithm is unable to process the input data provided.
*/
private static byte[] encrypt(@NonNull final String input) throws BadPaddingException, IllegalBlockSizeException {
return mCipherEncrypt.doFinal(input.getBytes());
}
/**
* Create a hash value of a String.
*
* @param input The input string.
* @return The hash value.
*/
public static String createHash(@NonNull final String input) {
try {
MessageDigest md = MessageDigest.getInstance("SHA1");
md.reset();
byte[] buffer = input.getBytes("UTF-8");
md.update(buffer);
byte[] digest = md.digest();
String hexStr = "";
for (int i = 0; i < digest.length; i++) {
hexStr += Integer.toString((digest[i] & 0xff) + 0x100, 16).substring(1); // MAGIC_NUMBER
}
return hexStr;
}
catch (Exception e) {
return null;
}
}
}