/**
*
*/
package com.mcxiaoke.next.utils;
import android.util.Base64;
import com.mcxiaoke.next.Charsets;
import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.PBEKeySpec;
import javax.crypto.spec.SecretKeySpec;
import java.io.UnsupportedEncodingException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.spec.KeySpec;
import java.util.Arrays;
/**
* @author mcxiaoke
* @version 1.0 2013.03.16
*/
public final class CryptoUtils {
public static final String TAG = CryptoUtils.class.getSimpleName();
public static final String ENC_UTF8 = Charsets.ENCODING_UTF_8;
private CryptoUtils() {
}
static String getRandomString() {
SecureRandom random = new SecureRandom();
return String.valueOf(random.nextLong());
}
static byte[] getRandomBytes(int size) {
SecureRandom random = new SecureRandom();
byte[] bytes = new byte[size];
random.nextBytes(bytes);
return bytes;
}
static byte[] getRawBytes(String text) {
try {
return text.getBytes(ENC_UTF8);
} catch (UnsupportedEncodingException e) {
return text.getBytes();
}
}
static String getString(byte[] data) {
try {
return new String(data, ENC_UTF8);
} catch (UnsupportedEncodingException e) {
return new String(data);
}
}
static byte[] base64Decode(String text) {
return Base64.decode(text, Base64.NO_WRAP);
}
static String base64Encode(byte[] data) {
return Base64.encodeToString(data, Base64.NO_WRAP);
}
public static final class AES {
static final int ITERATION_COUNT_DEFAULT = 100;
static final int KEY_SIZE_DEFAULT = 256;
static final int IV_SIZE_DEFAULT = 16;
static final String KEY_AES_SPEC = "AES/CBC/PKCS7Padding";
public static String encrypt(String text) {
return encrypt(text, getSimplePassword(), getSimpleSalt(),
getSimpleIV());
}
public static String decrypt(String text) {
return decrypt(text, getSimplePassword(), getSimpleSalt(),
getSimpleIV());
}
public static byte[] encrypt(byte[] data) {
return encrypt(data, getSimplePassword(), getSimpleSalt(),
getSimpleIV(), KEY_SIZE_DEFAULT, ITERATION_COUNT_DEFAULT);
}
public static byte[] decrypt(byte[] data) {
return decrypt(data, getSimplePassword(), getSimpleSalt(),
getSimpleIV(), KEY_SIZE_DEFAULT, ITERATION_COUNT_DEFAULT);
}
public static String encrypt(String text, String password) {
return encrypt(text, password, getSimpleSalt(), getSimpleIV());
}
public static String decrypt(String text, String password) {
return decrypt(text, password, getSimpleSalt(), getSimpleIV());
}
public static byte[] encrypt(byte[] data, String password) {
return encrypt(data, password, getSimpleSalt(), getSimpleIV(),
KEY_SIZE_DEFAULT, ITERATION_COUNT_DEFAULT);
}
public static byte[] decrypt(byte[] data, String password) {
return decrypt(data, password, getSimpleSalt(), getSimpleIV(),
KEY_SIZE_DEFAULT, ITERATION_COUNT_DEFAULT);
}
public static String encrypt(String text, String password, byte[] salt) {
return encrypt(text, password, salt, getSimpleIV());
}
public static String decrypt(String text, String password, byte[] salt) {
return decrypt(text, password, salt, getSimpleIV());
}
public static byte[] encrypt(byte[] data, String password, byte[] salt) {
return encrypt(data, password, salt, getSimpleIV(),
KEY_SIZE_DEFAULT, ITERATION_COUNT_DEFAULT);
}
public static byte[] decrypt(byte[] data, String password, byte[] salt) {
return decrypt(data, password, salt, getSimpleIV(),
KEY_SIZE_DEFAULT, ITERATION_COUNT_DEFAULT);
}
public static String encrypt(String text, String password, byte[] salt,
byte[] iv) {
byte[] data = getRawBytes(text);
byte[] encryptedData = encrypt(data, password, salt, iv,
KEY_SIZE_DEFAULT, ITERATION_COUNT_DEFAULT);
return base64Encode(encryptedData);
}
public static String decrypt(String text, String password, byte[] salt,
byte[] iv) {
byte[] encryptedData = base64Decode(text);
byte[] data = decrypt(encryptedData, password, salt, iv,
KEY_SIZE_DEFAULT, ITERATION_COUNT_DEFAULT);
return getString(data);
}
public static byte[] encrypt(byte[] data, String password, byte[] salt,
byte[] iv) {
return encrypt(data, password, salt, iv, KEY_SIZE_DEFAULT,
ITERATION_COUNT_DEFAULT);
}
public static byte[] decrypt(byte[] data, String password, byte[] salt,
byte[] iv) {
return decrypt(data, password, salt, iv, KEY_SIZE_DEFAULT,
ITERATION_COUNT_DEFAULT);
}
public static byte[] encrypt(byte[] data, String password, byte[] salt,
byte[] iv, int keySize) {
return encrypt(data, password, salt, iv, keySize,
ITERATION_COUNT_DEFAULT);
}
public static byte[] decrypt(byte[] data, String password, byte[] salt,
byte[] iv, int keySize) {
return decrypt(data, password, salt, iv, keySize,
ITERATION_COUNT_DEFAULT);
}
public static byte[] encrypt(byte[] data, String password, byte[] salt,
byte[] iv, int keySize, int iterationCount) {
return process(data, Cipher.ENCRYPT_MODE, password, salt, iv,
keySize, iterationCount);
}
public static byte[] decrypt(byte[] data, String password, byte[] salt,
byte[] iv, int keySize, int iterationCount) {
return process(data, Cipher.DECRYPT_MODE, password, salt, iv,
keySize, iterationCount);
}
/**
* AES encrypt function
*
* @param original
* @param key 16, 24, 32 bytes available
* @param iv initial vector (16 bytes) - if null: ECB mode, otherwise:
* CBC mode
* @return
*/
public static byte[] encrypt(byte[] original, byte[] key, byte[] iv) {
if (key == null
|| (key.length != 16 && key.length != 24 && key.length != 32)) {
return null;
}
if (iv != null && iv.length != 16) {
return null;
}
try {
SecretKeySpec keySpec = null;
Cipher cipher = null;
if (iv != null) {
keySpec = new SecretKeySpec(key, KEY_AES_SPEC);
cipher = Cipher.getInstance(KEY_AES_SPEC);
cipher.init(Cipher.ENCRYPT_MODE, keySpec,
new IvParameterSpec(iv));
} else // if(iv == null)
{
keySpec = new SecretKeySpec(key, KEY_AES_SPEC);
cipher = Cipher.getInstance(KEY_AES_SPEC);
cipher.init(Cipher.ENCRYPT_MODE, keySpec);
}
return cipher.doFinal(original);
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
/**
* AES decrypt function
*
* @param encrypted
* @param key 16, 24, 32 bytes available
* @param iv initial vector (16 bytes) - if null: ECB mode, otherwise:
* CBC mode
* @return
*/
public static byte[] decrypt(byte[] encrypted, byte[] key, byte[] iv) {
if (key == null
|| (key.length != 16 && key.length != 24 && key.length != 32)) {
return null;
}
if (iv != null && iv.length != 16) {
return null;
}
try {
SecretKeySpec keySpec = null;
Cipher cipher = null;
if (iv != null) {
keySpec = new SecretKeySpec(key, "AES/CBC/PKCS7Padding");// AES/ECB/PKCS5Padding
cipher = Cipher.getInstance("AES/CBC/PKCS7Padding");
cipher.init(Cipher.DECRYPT_MODE, keySpec,
new IvParameterSpec(iv));
} else // if(iv == null)
{
keySpec = new SecretKeySpec(key, "AES/ECB/PKCS7Padding");
cipher = Cipher.getInstance("AES/ECB/PKCS7Padding");
cipher.init(Cipher.DECRYPT_MODE, keySpec);
}
return cipher.doFinal(encrypted);
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
static byte[] process(byte[] data, int mode, String password,
byte[] salt, byte[] iv, int keySize, int iterationCount) {
KeySpec keySpec = new PBEKeySpec(password.toCharArray(), salt,
iterationCount, keySize);
try {
SecretKeyFactory keyFactory = SecretKeyFactory
.getInstance("PBKDF2WithHmacSHA1");
byte[] keyBytes = keyFactory.generateSecret(keySpec)
.getEncoded();
SecretKey key = new SecretKeySpec(keyBytes, "AES");
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
IvParameterSpec ivParams = new IvParameterSpec(iv);
cipher.init(mode, key, ivParams);
return cipher.doFinal(data);
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
static String getSimplePassword() {
return "GZ9Gn2U5nhpea8hw";
}
static byte[] getSimpleSalt() {
return "rUiey8D2GNzV69Mp".getBytes();
}
static byte[] getSimpleIV() {
byte[] iv = new byte[AES.IV_SIZE_DEFAULT];
Arrays.fill(iv, (byte) 5);
return iv;
}
}
// http://nelenkov.blogspot.jp/2012/04/using-password-based-encryption-on.html
public static class AESCrypto {
private static final int ITERATION_COUNT_DEFAULT = 100;
private static final int ITERATION_COUNT_MIN = 10;
private static final int ITERATION_COUNT_MAX = 5000;
private static final int KEY_SIZE_DEFAULT = 256;
private static final int KEY_SIZE_MIN = 64;
private static final int KEY_SIZE_MAX = 1024;
private static final int IV_SIZE = 16;
private String password;
private byte[] salt;
private byte[] iv;
private int keySize;
private int iterCount;
public AESCrypto(String password) {
initialize(password, AES.getSimpleSalt(), AES.getSimpleIV(),
KEY_SIZE_DEFAULT, ITERATION_COUNT_DEFAULT);
}
public AESCrypto(String password, byte[] salt) {
initialize(password, salt, AES.getSimpleIV(), KEY_SIZE_DEFAULT,
ITERATION_COUNT_DEFAULT);
}
public AESCrypto(String password, int keySize, byte[] salt, byte[] iv) {
initialize(password, salt, iv, keySize, ITERATION_COUNT_DEFAULT);
}
private void initialize(String password, byte[] salt, byte[] iv,
int keySize, int iterCount) {
AssertUtils
.notEmpty(password, "password must not be null or empty");
AssertUtils.notNull(salt, "salt must bot be null");
AssertUtils.notNull(iv, "iv must not be null");
AssertUtils.isTrue(keySize >= KEY_SIZE_MIN
&& keySize <= KEY_SIZE_MAX, "keySize must between "
+ KEY_SIZE_MIN + " and " + KEY_SIZE_MAX);
AssertUtils.isTrue(iterCount >= ITERATION_COUNT_MIN
&& iterCount <= ITERATION_COUNT_MAX,
"iterCount must between " + ITERATION_COUNT_MIN + " and "
+ ITERATION_COUNT_MAX
);
this.password = password;
this.salt = salt;
this.iv = iv;
this.keySize = keySize;
this.iterCount = iterCount;
}
public String encrypt(String text) {
byte[] data = getRawBytes(text);
byte[] encryptedData = encrypt(data);
return base64Encode(encryptedData);
}
public byte[] encrypt(byte[] data) {
return process(data, Cipher.ENCRYPT_MODE);
}
public String decrypt(String text) {
byte[] encryptedData = base64Decode(text);
byte[] data = decrypt(encryptedData);
return getString(data);
}
public byte[] decrypt(byte[] encryptedData) {
return process(encryptedData, Cipher.DECRYPT_MODE);
}
private byte[] process(byte[] data, int mode) {
return AES.process(data, mode, password, salt, iv, keySize,
iterCount);
}
}
public static final class HEX {
private static final char[] HEX_DIGITS = new char[]{'0', '1', '2',
'3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'};
private static final char[] FIRST_CHAR = new char[256];
private static final char[] SECOND_CHAR = new char[256];
static {
for (int i = 0; i < 256; i++) {
FIRST_CHAR[i] = HEX_DIGITS[(i >> 4) & 0xF];
SECOND_CHAR[i] = HEX_DIGITS[i & 0xF];
}
}
private static final byte[] DIGITS = new byte['f' + 1];
static {
for (int i = 0; i <= 'F'; i++) {
DIGITS[i] = -1;
}
for (byte i = 0; i < 10; i++) {
DIGITS['0' + i] = i;
}
for (byte i = 0; i < 6; i++) {
DIGITS['A' + i] = (byte) (10 + i);
DIGITS['a' + i] = (byte) (10 + i);
}
}
/**
* Quickly converts a byte array to a hexadecimal string representation.
*
* @param array byte array, possibly zero-terminated.
*/
public static String encodeHex(byte[] array, boolean zeroTerminated) {
char[] cArray = new char[array.length * 2];
int j = 0;
for (int i = 0; i < array.length; i++) {
int index = array[i] & 0xFF;
if (index == 0 && zeroTerminated) {
break;
}
cArray[j++] = FIRST_CHAR[index];
cArray[j++] = SECOND_CHAR[index];
}
return new String(cArray, 0, j);
}
/**
* Quickly converts a hexadecimal string to a byte array.
*/
public static byte[] decodeHex(String hexString) {
int length = hexString.length();
if ((length & 0x01) != 0) {
throw new IllegalArgumentException("Odd number of characters.");
}
boolean badHex = false;
byte[] out = new byte[length >> 1];
for (int i = 0, j = 0; j < length; i++) {
int c1 = hexString.charAt(j++);
if (c1 > 'f') {
badHex = true;
break;
}
final byte d1 = DIGITS[c1];
if (d1 == -1) {
badHex = true;
break;
}
int c2 = hexString.charAt(j++);
if (c2 > 'f') {
badHex = true;
break;
}
final byte d2 = DIGITS[c2];
if (d2 == -1) {
badHex = true;
break;
}
out[i] = (byte) (d1 << 4 | d2);
}
if (badHex) {
throw new IllegalArgumentException(
"Invalid hexadecimal digit: " + hexString);
}
return out;
}
}
public static final class HASH {
private static final String MD5 = "MD5";
private static final String SHA_1 = "SHA-1";
private static final String SHA_256 = "SHA-256";
private static final char[] DIGITS_LOWER = {'0', '1', '2', '3', '4',
'5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};
private static final char[] DIGITS_UPPER = {'0', '1', '2', '3', '4',
'5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'};
public static String md5(byte[] data) {
return new String(encodeHex(md5Bytes(data)));
}
public static String md5(String text) {
return new String(encodeHex(md5Bytes(getRawBytes(text))));
}
public static byte[] md5Bytes(byte[] data) {
return getDigest(MD5).digest(data);
}
public static String sha1(byte[] data) {
return new String(encodeHex(sha1Bytes(data)));
}
public static String sha1(String text) {
return new String(encodeHex(sha1Bytes(getRawBytes(text))));
}
public static byte[] sha1Bytes(byte[] data) {
return getDigest(SHA_1).digest(data);
}
public static String sha256(byte[] data) {
return new String(encodeHex(sha256Bytes(data)));
}
public static String sha256(String text) {
return new String(encodeHex(sha256Bytes(getRawBytes(text))));
}
public static byte[] sha256Bytes(byte[] data) {
return getDigest(SHA_256).digest(data);
}
private static MessageDigest getDigest(String algorithm) {
try {
return MessageDigest.getInstance(algorithm);
} catch (NoSuchAlgorithmException e) {
throw new IllegalArgumentException(e);
}
}
private static char[] encodeHex(byte[] data) {
return encodeHex(data, true);
}
private static char[] encodeHex(byte[] data, boolean toLowerCase) {
return encodeHex(data, toLowerCase ? DIGITS_LOWER : DIGITS_UPPER);
}
private static char[] encodeHex(byte[] data, char[] toDigits) {
int l = data.length;
char[] out = new char[l << 1];
for (int i = 0, j = 0; i < l; i++) {
out[j++] = toDigits[(0xF0 & data[i]) >>> 4];
out[j++] = toDigits[0x0F & data[i]];
}
return out;
}
}
}