/* * Copyright (C) 2015 P100 OG, Inc. * * 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.shiftconnects.android.auth.util; import android.content.SharedPreferences; import android.support.annotation.NonNull; import android.text.TextUtils; import android.util.Base64; import android.util.Log; import java.security.NoSuchAlgorithmException; import java.security.SecureRandom; import java.security.spec.InvalidKeySpecException; import java.security.spec.KeySpec; import javax.crypto.Cipher; import javax.crypto.KeyGenerator; import javax.crypto.NoSuchPaddingException; import javax.crypto.SecretKey; import javax.crypto.SecretKeyFactory; import javax.crypto.spec.IvParameterSpec; import javax.crypto.spec.PBEKeySpec; import javax.crypto.spec.SecretKeySpec; import static com.shiftconnects.android.auth.util.AuthConstants.DEBUG; import static com.shiftconnects.android.auth.util.AuthConstants.DEBUG_TAG; /** * Crypto implementation using an AES transformation with a 128-bit key length. */ public class AESCrypto implements Crypto { private static final String TAG = AESCrypto.class.getSimpleName(); private static final String IV = "iv"; private static final String SALT = "salt"; private static final int ITERATIONS = 1000; private static final int KEY_LENGTH = 128; private static final int SALT_LENGTH = 128; // same size as key output private SharedPreferences mSharedPrefs; private byte[] mSalt; private byte[] mIv; /** * Default constructor * @param sharedPrefs - {@link SharedPreferences} used to store a generated salt and iv used for * encryption/decryption */ public AESCrypto(SharedPreferences sharedPrefs) { mSharedPrefs = sharedPrefs; mSalt = generateSalt(); mIv = generateIV(); } @NonNull public String encrypt(@NonNull String password, @NonNull String decryptedString) throws Exception { Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); IvParameterSpec ivParams = new IvParameterSpec(mIv); cipher.init(Cipher.ENCRYPT_MODE, deriveKeyPbkdf2(mSalt, password), ivParams); byte[] cipherBytes = cipher.doFinal(decryptedString.getBytes("UTF-8")); return Base64.encodeToString(cipherBytes, Base64.DEFAULT); } @NonNull public String decrypt(@NonNull String password, @NonNull String encryptedString) throws Exception { Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); IvParameterSpec ivParams = new IvParameterSpec(mIv); cipher.init(Cipher.DECRYPT_MODE, deriveKeyPbkdf2(mSalt, password), ivParams); byte[] plaintext = cipher.doFinal(Base64.decode(encryptedString, Base64.DEFAULT)); return new String(plaintext , "UTF-8"); } private SecretKey deriveKeyPbkdf2(byte[] salt, String password) throws NoSuchAlgorithmException, InvalidKeySpecException { KeySpec keySpec = new PBEKeySpec(password.toCharArray(), salt, ITERATIONS, KEY_LENGTH); SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1"); byte[] keyBytes = keyFactory.generateSecret(keySpec).getEncoded(); return new SecretKeySpec(keyBytes, "AES"); } private byte[] generateSalt() { byte[] salt; String saltString = mSharedPrefs.getString(SALT, null); if (TextUtils.isEmpty(saltString)) { try { if (DEBUG) { Log.d(String.format(DEBUG_TAG, TAG), "salt is null. Generating one..."); } // generate a new salt SecureRandom secureRandom = new SecureRandom(); KeyGenerator keyGenerator = KeyGenerator.getInstance("AES"); keyGenerator.init(SALT_LENGTH, secureRandom); SecretKey key = keyGenerator.generateKey(); salt = key.getEncoded(); // encode it saltString = Base64.encodeToString(salt, Base64.DEFAULT); if (DEBUG) { Log.d(String.format(DEBUG_TAG, TAG), "Newly generated encoded salt: " + saltString); } // save to shared prefs mSharedPrefs.edit().putString(SALT, saltString).apply(); } catch (NoSuchAlgorithmException e) { Log.e(String.format(DEBUG_TAG, TAG), "Could not setup salt. This is bad."); throw new RuntimeException("Unable to setup salt. Cannot run app.", e); } } else { salt = Base64.decode(saltString, Base64.DEFAULT); if (DEBUG) { Log.d(String.format(DEBUG_TAG, TAG), "Salt loaded from disk: " + saltString); } } return salt; } private byte[] generateIV() { byte[] iv; String ivString = mSharedPrefs.getString(IV, null); if (TextUtils.isEmpty(ivString)) { try { if (DEBUG) { Log.d(String.format(DEBUG_TAG, TAG), "Initialization vector is null. Generating one..."); } // generate a new iv SecureRandom secureRandom = new SecureRandom(); Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); iv = new byte[cipher.getBlockSize()]; secureRandom.nextBytes(iv); // encode it ivString = Base64.encodeToString(iv, Base64.DEFAULT); if (DEBUG) { Log.d(String.format(DEBUG_TAG, TAG), "Newly generated encoded initialization vector: " + ivString); } // save to shared prefs mSharedPrefs.edit().putString(IV, ivString).apply(); } catch (NoSuchAlgorithmException | NoSuchPaddingException e) { Log.e(String.format(DEBUG_TAG, TAG), "Could not setup IV. This is bad."); throw new RuntimeException("Unable to setup IV. Cannot run app.", e); } } else { iv = Base64.decode(ivString, Base64.DEFAULT); if (DEBUG) { Log.d(String.format(DEBUG_TAG, TAG), "IV loaded from disk: " + ivString); } } return iv; } }