/*
* Copyright (C) 2012-2016 The Android Money Manager Ex Project Team
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 3
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.money.manager.ex.passcode;
import android.os.Build;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.security.GeneralSecurityException;
import java.security.SecureRandom;
import java.security.spec.KeySpec;
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 timber.log.Timber;
/**
* Wrapper for the encryption class/library.
* Ref: https://android.googlesource.com/platform/development/+/master/samples/BrokenKeyDerivation/src/com/example/android/brokenkeyderivation/BrokenKeyDerivationActivity.java
*/
public class Encryptor {
private static final int KEY_SIZE = 32;
public String encrypt(String text) {
return "not implemented";
}
/**
* Method used to derive an <b>insecure</b> key by emulating the SHA1PRNG algorithm from the
* deprecated Crypto provider.
*
* Do not use it to encrypt new data, just to decrypt encrypted data that would be unrecoverable
* otherwise.
*/
public static SecretKey deriveKeyInsecurely(String password, int keySizeInBytes) {
byte[] passwordBytes;
if (Build.VERSION.SDK_INT >= 19) {
passwordBytes = password.getBytes(StandardCharsets.US_ASCII);
} else {
passwordBytes = password.getBytes(Charset.forName("UTF-8"));
}
return new SecretKeySpec(
InsecureSHA1PRNGKeyDerivator.deriveInsecureKey(passwordBytes, keySizeInBytes), "AES");
}
/**
* Example use of a key derivation function, derivating a key securely from a password.
*/
private SecretKey deriveKeySecurely(String password, int keySizeInBytes) {
// Use this to derive the key from the password:
KeySpec keySpec = new PBEKeySpec(password.toCharArray(), retrieveSalt(),
100 /* iterationCount */,
keySizeInBytes * 8 /* key size in bits */);
try {
SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
byte[] keyBytes = keyFactory.generateSecret(keySpec).getEncoded();
return new SecretKeySpec(keyBytes, "AES");
} catch (Exception e) {
throw new RuntimeException("Deal with exceptions properly!", e);
}
}
/**
* This is from the Android blog post.
* @param password
* @return
*/
private SecretKey getKeyFor(String password) {
/* User types in their password: */
// String password = "password";
/* Store these things on disk used to derive key later: */
int iterationCount = 1000;
int saltLength = 32; // bytes; should be the same size as the output (256 / 8 = 32)
int keyLength = 256; // 256-bits for AES-256, 128-bits for AES-128, etc
byte[] salt; // Should be of saltLength
/* When first creating the key, obtain a salt with this: */
SecureRandom random = new SecureRandom();
// byte[] salt = new byte[saltLength];
salt = new byte[saltLength];
random.nextBytes(salt);
/* Use this to derive the key from the password: */
KeySpec keySpec = new PBEKeySpec(password.toCharArray(), salt,
iterationCount, keyLength);
byte[] keyBytes;
try {
SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
keyBytes = keyFactory.generateSecret(keySpec).getEncoded();
} catch (Exception e) {
Timber.e(e, "generating key");
return null;
}
SecretKey key = new SecretKeySpec(keyBytes, "AES");
return key;
}
// /**
// * Retrieve encrypted data using a password. If data is stored with an insecure key, re-encrypt
// * with a secure key.
// */
// private String retrieveData(String password) {
// String decryptedString;
// if (isDataStoredWithInsecureKey()) {
// SecretKey insecureKey = deriveKeyInsecurely(password, KEY_SIZE);
// byte[] decryptedData = decryptData(retrieveEncryptedData(), retrieveIv(), insecureKey);
// SecretKey secureKey = deriveKeySecurely(password, KEY_SIZE);
// storeDataEncryptedWithSecureKey(encryptData(decryptedData, retrieveIv(), secureKey));
// decryptedString = "Warning: data was encrypted with insecure key\n"
// + new String(decryptedData, StandardCharsets.UTF_8);
// } else {
// SecretKey secureKey = deriveKeySecurely(password, KEY_SIZE);
// byte[] decryptedData = decryptData(retrieveEncryptedData(), retrieveIv(), secureKey);
// decryptedString = "Great!: data was encrypted with secure key\n"
// + new String(decryptedData, StandardCharsets.UTF_8);
// }
// return decryptedString;
// }
private static byte[] encryptOrDecrypt(
byte[] data, SecretKey key, byte[] iv, boolean isEncrypt) {
try {
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS7PADDING");
cipher.init(isEncrypt ? Cipher.ENCRYPT_MODE : Cipher.DECRYPT_MODE, key,
new IvParameterSpec(iv));
return cipher.doFinal(data);
} catch (GeneralSecurityException e) {
throw new RuntimeException("This is unconceivable!", e);
}
}
private static byte[] encryptData(byte[] data, byte[] iv, SecretKey key) {
return encryptOrDecrypt(data, key, iv, true);
}
private static byte[] decryptData(byte[] data, byte[] iv, SecretKey key) {
return encryptOrDecrypt(data, key, iv, false);
}
private byte[] retrieveSalt() {
// Salt must be at least the same size as the key.
byte[] salt = new byte[KEY_SIZE];
// Create a random salt if encrypting for the first time, and update it for future use.
readFromFileOrCreateRandom("salt", salt);
return salt;
}
/**
* Read from file or return random bytes in the given array.
*
* <p>Save to file if file didn't exist.
*/
private void readFromFileOrCreateRandom(String fileName, byte[] bytes) {
// if (fileExists(fileName)) {
// readBytesFromFile(fileName, bytes);
// return;
// }
SecureRandom sr = new SecureRandom();
sr.nextBytes(bytes);
// todo store.
// writeToFile(fileName, bytes);
}
}