import android.support.annotation.Nullable; import android.util.Base64; import java.nio.ByteBuffer; import java.security.SecureRandom; import javax.crypto.Cipher; import javax.crypto.spec.IvParameterSpec; import javax.crypto.spec.SecretKeySpec; /** * AesCipher *

Encode/Decode text by password using AES-128-CBC algorithm

*/ public class AesCipher { public static final int INIT_VECTOR_LENGTH = 16; /** * @see how-to-convert-a-byte-array-to-a-hex-string */ private final static char[] hexArray = "0123456789ABCDEF".toCharArray(); /** * Encoded/Decoded data */ protected String data; /** * Initialization vector value */ protected String initVector; /** * Error message if operation failed */ protected String errorMessage; private AesCipher() { super(); } /** * AesCipher constructor. * * @param initVector Initialization vector value * @param data Encoded/Decoded data * @param errorMessage Error message if operation failed */ private AesCipher(@Nullable String initVector, @Nullable String data, @Nullable String errorMessage) { super(); this.initVector = initVector; this.data = data; this.errorMessage = errorMessage; } /** * Encrypt input text by AES-128-CBC algorithm * * @param secretKey 16/24/32 -characters secret password * @param plainText Text for encryption * @return Encoded string or NULL if error */ public static AesCipher encrypt(String secretKey, String plainText) { String initVector = null; try { // Check secret length if (!isKeyLengthValid(secretKey)) { throw new Exception("Secret key's length must be 128, 192 or 256 bits"); } // Get random initialization vector SecureRandom secureRandom = new SecureRandom(); byte[] initVectorBytes = new byte[INIT_VECTOR_LENGTH / 2]; secureRandom.nextBytes(initVectorBytes); initVector = bytesToHex(initVectorBytes); initVectorBytes = initVector.getBytes("UTF-8"); IvParameterSpec ivParameterSpec = new IvParameterSpec(initVectorBytes); SecretKeySpec secretKeySpec = new SecretKeySpec(secretKey.getBytes("UTF-8"), "AES"); Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec, ivParameterSpec); // Encrypt input text byte[] encrypted = cipher.doFinal(plainText.getBytes("UTF-8")); ByteBuffer byteBuffer = ByteBuffer.allocate(initVectorBytes.length + encrypted.length); byteBuffer.put(initVectorBytes); byteBuffer.put(encrypted); // Result is base64-encoded string: initVector + encrypted result String result = Base64.encodeToString(byteBuffer.array(), Base64.DEFAULT); // Return successful encoded object return new AesCipher(initVector, result, null); } catch (Throwable t) { t.printStackTrace(); // Operation failed return new AesCipher(initVector, null, t.getMessage()); } } /** * Decrypt encoded text by AES-128-CBC algorithm * * @param secretKey 16/24/32 -characters secret password * @param cipherText Encrypted text * @return Self object instance with data or error message */ public static AesCipher decrypt(String secretKey, String cipherText) { try { // Check secret length if (!isKeyLengthValid(secretKey)) { throw new Exception("Secret key's length must be 128, 192 or 256 bits"); } // Get raw encoded data byte[] encrypted = Base64.decode(cipherText, Base64.DEFAULT); // Slice initialization vector IvParameterSpec ivParameterSpec = new IvParameterSpec(encrypted, 0, INIT_VECTOR_LENGTH); // Set secret password SecretKeySpec secretKeySpec = new SecretKeySpec(secretKey.getBytes("UTF-8"), "AES"); Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); cipher.init(Cipher.DECRYPT_MODE, secretKeySpec, ivParameterSpec); // Trying to get decrypted text String result = new String(cipher.doFinal(encrypted, INIT_VECTOR_LENGTH, encrypted.length - INIT_VECTOR_LENGTH)); // Return successful decoded object return new AesCipher(bytesToHex(ivParameterSpec.getIV()), result, null); } catch (Throwable t) { t.printStackTrace(); // Operation failed return new AesCipher(null, null, t.getMessage()); } } /** * Check that secret password length is valid * * @param key 16/24/32 -characters secret password * @return TRUE if valid, FALSE otherwise */ public static boolean isKeyLengthValid(String key) { return key.length() == 16 || key.length() == 24 || key.length() == 32; } /** * Convert Bytes to HEX * * @param bytes Bytes array * @return String with bytes values */ public static String bytesToHex(byte[] bytes) { char[] hexChars = new char[bytes.length * 2]; for (int j = 0; j < bytes.length; j++) { int v = bytes[j] & 0xFF; hexChars[j * 2] = hexArray[v >>> 4]; hexChars[j * 2 + 1] = hexArray[v & 0x0F]; } return new String(hexChars); } /** * Get encoded/decoded data */ public String getData() { return data; } /** * Get initialization vector value */ public String getInitVector() { return initVector; } /** * Get error message */ public String getErrorMessage() { return errorMessage; } /** * Check that operation failed * * @return TRUE if failed, FALSE otherwise */ public boolean hasError() { return this.errorMessage != null; } /** * To string return resulting data * * @return Encoded/decoded data */ public String toString() { return getData(); } } // USAGE String secretKey = "26kozQaKwRuNJ24t"; String text = "Some text"; AesCipher encrypted = AesCipher.encrypt(secretKey, text); AesCipher decrypted = AesCipher.decrypt(secretKey, encrypted); encrypted.hasError(); // TRUE if operation failed, FALSE otherwise encrypted.getData(); // Encoded/Decoded result encrypted.getInitVector(); // Get used (random if encode) init vector // decrypted.* has identical methods