/******************************************************************************* * Copyright 2011, 2012, 2013 fanfou.com, Xiaoke, Zhang * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. *******************************************************************************/ /** * */ package com.fanfou.app.opensource.util; import java.io.IOException; 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; 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 android.text.TextUtils; import com.fanfou.app.opensource.util.support.Base64; /** * @author mcxiaoke * @version 1.0 2013.03.16 * */ public final class CryptoHelper { public static class AES { static final int ITERATION_COUNT_DEFAULT = 100; static final int KEY_SIZE_DEFAULT = 256; static final int SALT_SIZE_DEFAULT = 8; static final int IV_SIZE_DEFAULT = 16; public static byte[] decrypt(final byte[] data) { return AES.decrypt(data, AES.getSimplePassword(), AES.getSimpleSalt(), AES.getSimpleIV(), AES.KEY_SIZE_DEFAULT, AES.ITERATION_COUNT_DEFAULT); } /** * 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(final byte[] encrypted, final byte[] key, final 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 (final Exception e) { e.printStackTrace(); } return null; } public static byte[] decrypt(final byte[] data, final String password) { return AES.decrypt(data, password, AES.getSimpleSalt(), AES.getSimpleIV(), AES.KEY_SIZE_DEFAULT, AES.ITERATION_COUNT_DEFAULT); } public static byte[] decrypt(final byte[] data, final String password, final byte[] salt) { return AES.decrypt(data, password, salt, AES.getSimpleIV(), AES.KEY_SIZE_DEFAULT, AES.ITERATION_COUNT_DEFAULT); } public static byte[] decrypt(final byte[] data, final String password, final byte[] salt, final byte[] iv) { return AES.decrypt(data, password, salt, iv, AES.KEY_SIZE_DEFAULT, AES.ITERATION_COUNT_DEFAULT); } public static byte[] decrypt(final byte[] data, final String password, final byte[] salt, final byte[] iv, final int keySize) { return AES.decrypt(data, password, salt, iv, keySize, AES.ITERATION_COUNT_DEFAULT); } public static byte[] decrypt(final byte[] data, final String password, final byte[] salt, final byte[] iv, final int keySize, final int iterationCount) { return AES.process(data, Cipher.DECRYPT_MODE, password, salt, iv, keySize, iterationCount); } public static String decrypt(final String text) { return AES.decrypt(text, AES.getSimplePassword(), AES.getSimpleSalt(), AES.getSimpleIV()); } public static String decrypt(final String text, final String password) { return AES.decrypt(text, password, AES.getSimpleSalt(), AES.getSimpleIV()); } public static String decrypt(final String text, final String password, final byte[] salt) { return AES.decrypt(text, password, salt, AES.getSimpleIV()); } public static String decrypt(final String text, final String password, final byte[] salt, final byte[] iv) { final byte[] encryptedData = CryptoHelper.base64Decode(text); final byte[] data = AES.decrypt(encryptedData, password, salt, iv, AES.KEY_SIZE_DEFAULT, AES.ITERATION_COUNT_DEFAULT); return CryptoHelper.getString(data); } public static byte[] encrypt(final byte[] data) { return AES.encrypt(data, AES.getSimplePassword(), AES.getSimpleSalt(), AES.getSimpleIV(), AES.KEY_SIZE_DEFAULT, AES.ITERATION_COUNT_DEFAULT); } /** * 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(final byte[] original, final byte[] key, final 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"); cipher = Cipher.getInstance("AES/CBC/PKCS7Padding"); cipher.init(Cipher.ENCRYPT_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.ENCRYPT_MODE, keySpec); } return cipher.doFinal(original); } catch (final Exception e) { e.printStackTrace(); } return null; } public static byte[] encrypt(final byte[] data, final String password) { return AES.encrypt(data, password, AES.getSimpleSalt(), AES.getSimpleIV(), AES.KEY_SIZE_DEFAULT, AES.ITERATION_COUNT_DEFAULT); } public static byte[] encrypt(final byte[] data, final String password, final byte[] salt) { return AES.encrypt(data, password, salt, AES.getSimpleIV(), AES.KEY_SIZE_DEFAULT, AES.ITERATION_COUNT_DEFAULT); } public static byte[] encrypt(final byte[] data, final String password, final byte[] salt, final byte[] iv) { return AES.encrypt(data, password, salt, iv, AES.KEY_SIZE_DEFAULT, AES.ITERATION_COUNT_DEFAULT); } public static byte[] encrypt(final byte[] data, final String password, final byte[] salt, final byte[] iv, final int keySize) { return AES.encrypt(data, password, salt, iv, keySize, AES.ITERATION_COUNT_DEFAULT); } public static byte[] encrypt(final byte[] data, final String password, final byte[] salt, final byte[] iv, final int keySize, final int iterationCount) { return AES.process(data, Cipher.ENCRYPT_MODE, password, salt, iv, keySize, iterationCount); } public static String encrypt(final String text) { return AES.encrypt(text, AES.getSimplePassword(), AES.getSimpleSalt(), AES.getSimpleIV()); } public static String encrypt(final String text, final String password) { return AES.encrypt(text, password, AES.getSimpleSalt(), AES.getSimpleIV()); } public static String encrypt(final String text, final String password, final byte[] salt) { return AES.encrypt(text, password, salt, AES.getSimpleIV()); } public static String encrypt(final String text, final String password, final byte[] salt, final byte[] iv) { final byte[] data = CryptoHelper.getRawBytes(text); final byte[] encryptedData = AES.encrypt(data, password, salt, iv, AES.KEY_SIZE_DEFAULT, AES.ITERATION_COUNT_DEFAULT); return CryptoHelper.base64Encode(encryptedData); } static byte[] getSimpleIV() { final byte[] iv = new byte[AES.IV_SIZE_DEFAULT]; Arrays.fill(iv, (byte) 5); return iv; } static String getSimplePassword() { return "GZ9Gn2U5nhpea8hw"; } static byte[] getSimpleSalt() { return "rUiey8D2GNzV69Mp".getBytes(); } static byte[] process(final byte[] data, final int mode, final String password, final byte[] salt, final byte[] iv, final int keySize, final int iterationCount) { final KeySpec keySpec = new PBEKeySpec(password.toCharArray(), salt, iterationCount, keySize); try { final SecretKeyFactory keyFactory = SecretKeyFactory .getInstance("PBKDF2WithHmacSHA1"); final byte[] keyBytes = keyFactory.generateSecret(keySpec) .getEncoded(); final SecretKey key = new SecretKeySpec(keyBytes, "AES"); final Cipher cipher = Cipher .getInstance("AES/CBC/PKCS5Padding"); final IvParameterSpec ivParams = new IvParameterSpec(iv); cipher.init(mode, key, ivParams); return cipher.doFinal(data); } catch (final Exception e) { e.printStackTrace(); } return null; } } // 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 = 10000; private static final int SALT_SIZE_DEFAULT = 8; 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_DEFAULT = 16; private String password; private byte[] salt; private byte[] iv; private int keySize; private int iterationCount; public AESCrypto(final String password) { initialize(password, AESCrypto.KEY_SIZE_DEFAULT, null, AESCrypto.ITERATION_COUNT_DEFAULT); } public AESCrypto(final String password, final int keySize, final String salt, final int iterationCount) { initialize(password, keySize, salt, iterationCount); } public AESCrypto(final String password, final String salt) { initialize(password, AESCrypto.KEY_SIZE_DEFAULT, salt, AESCrypto.ITERATION_COUNT_DEFAULT); } private void checkAndSetIterationCount(final int iterationCount) { if (iterationCount < AESCrypto.ITERATION_COUNT_MIN) { this.iterationCount = AESCrypto.ITERATION_COUNT_MIN; } else if (iterationCount > AESCrypto.ITERATION_COUNT_MAX) { this.iterationCount = AESCrypto.ITERATION_COUNT_MAX; } else { this.iterationCount = iterationCount; } } private void checkAndSetIV() { this.iv = CryptoHelper.getRandomBytes(AESCrypto.IV_SIZE_DEFAULT); } private void checkAndSetKeySize(final int keySize) { if (keySize < AESCrypto.KEY_SIZE_MIN) { this.keySize = AESCrypto.KEY_SIZE_MIN; } else if (keySize > AESCrypto.KEY_SIZE_MAX) { this.keySize = AESCrypto.KEY_SIZE_MAX; } else { this.keySize = keySize; } } private void checkAndSetPassword(final String password) { if (TextUtils.isEmpty(password)) { this.password = CryptoHelper.getRandomString(); } else { this.password = password; } } private void checkAndSetSalt(final String salt) { if (TextUtils.isEmpty(salt)) { this.salt = CryptoHelper .getRandomBytes(AESCrypto.SALT_SIZE_DEFAULT); } else { this.salt = CryptoHelper.getRawBytes(salt); } } public byte[] decrypt(final byte[] encryptedData) { return process(encryptedData, Cipher.DECRYPT_MODE); } public String decrypt(final String text) { final byte[] encryptedData = CryptoHelper.base64Decode(text); final byte[] data = decrypt(encryptedData); return CryptoHelper.getString(data); } public byte[] encrypt(final byte[] data) { return process(data, Cipher.ENCRYPT_MODE); } public String encrypt(final String text) { final byte[] data = CryptoHelper.getRawBytes(text); final byte[] encryptedData = encrypt(data); return CryptoHelper.base64Encode(encryptedData); } public int getIterationCount() { return this.iterationCount; } public byte[] getIv() { return this.iv; } public int getKeySize() { return this.keySize; } public String getPassword() { return this.password; } public byte[] getSalt() { return this.salt; } private void initialize(final String password, final int keySize, final String salt, final int iterationCount) { checkAndSetPassword(password); checkAndSetKeySize(keySize); checkAndSetSalt(salt); checkAndSetIterationCount(iterationCount); checkAndSetIV(); } private byte[] process(final byte[] data, final int mode) { return AES.process(data, mode, this.password, this.salt, this.iv, this.keySize, this.iterationCount); } public void setIterationCount(final int iterationCount) { checkAndSetIterationCount(iterationCount); } public void setKeySize(final int keySize) { checkAndSetKeySize(keySize); } public void setPassword(final String password) { checkAndSetPassword(password); } public void setSalt(final String salt) { checkAndSetSalt(salt); } } public static 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' }; private static char[] encodeHex(final byte[] data) { return HASH.encodeHex(data, true); } private static char[] encodeHex(final byte[] data, final boolean toLowerCase) { return HASH.encodeHex(data, toLowerCase ? HASH.DIGITS_LOWER : HASH.DIGITS_UPPER); } private static char[] encodeHex(final byte[] data, final char[] toDigits) { final int l = data.length; final 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; } private static MessageDigest getDigest(final String algorithm) { try { return MessageDigest.getInstance(algorithm); } catch (final NoSuchAlgorithmException e) { throw new IllegalArgumentException(e); } } public static String md5(final byte[] data) { return new String(HASH.encodeHex(HASH.md5Bytes(data))); } public static String md5(final String text) { return new String(HASH.encodeHex(HASH.md5Bytes(CryptoHelper .getRawBytes(text)))); } public static byte[] md5Bytes(final byte[] data) { return HASH.getDigest(HASH.MD5).digest(data); } public static String sha1(final byte[] data) { return new String(HASH.encodeHex(HASH.sha1Bytes(data))); } public static String sha1(final String text) { return new String(HASH.encodeHex(HASH.sha1Bytes(CryptoHelper .getRawBytes(text)))); } public static byte[] sha1Bytes(final byte[] data) { return HASH.getDigest(HASH.SHA_1).digest(data); } public static String sha256(final byte[] data) { return new String(HASH.encodeHex(HASH.sha256Bytes(data))); } public static String sha256(final String text) { return new String(HASH.encodeHex(HASH.sha256Bytes(CryptoHelper .getRawBytes(text)))); } public static byte[] sha256Bytes(final byte[] data) { return HASH.getDigest(HASH.SHA_256).digest(data); } } public static final String TAG = CryptoHelper.class.getSimpleName(); public static final String ENC_UTF8 = "UTF-8"; static byte[] base64Decode(final String text) { try { return Base64.decode(text); } catch (final IOException e) { e.printStackTrace(); return null; } } static String base64Encode(final byte[] data) { return Base64.encodeBytes(data); } static byte[] getRandomBytes(final int size) { final SecureRandom random = new SecureRandom(); final byte[] bytes = new byte[size]; random.nextBytes(bytes); return bytes; } static String getRandomString() { final SecureRandom random = new SecureRandom(); return String.valueOf(random.nextLong()); } static byte[] getRawBytes(final String text) { try { return text.getBytes(CryptoHelper.ENC_UTF8); } catch (final UnsupportedEncodingException e) { return text.getBytes(); } } static String getString(final byte[] data) { try { return new String(data, CryptoHelper.ENC_UTF8); } catch (final UnsupportedEncodingException e) { return new String(data); } } }