I'm trying to use Biometric and EncryptedSharedPreferences to store multi passwords of a user in the AndroidKeystore (and in the StrongBox).
I'm trying it, but I can't get to store more than one. I explain it below:
We store a string value by passing a keyalias for the keystore and the key-value we want to save. Example:
KEYALIAS: FIRSTKS
KEY: password1
VALUE: 123abc
If we try to read the value, we get it without problem.
If we try to save another value in FIRSTKS, the previously stored value is lost and only the new saved value is found.
KEYALIAS: FIRSTKS
KEY: password2
VALUE: 456def
If I try to read password1, the value is not found.
I can only store new values if I create a different keyalias. Example:
KEYALIAS: FIRSTKS
KEY: password1
VALUE: 123abc
KEYALIAS: SECONDKS
KEY: password2
VALUE: 456def
I don't know if this is a normal behaviour or not.
I guess my biometrics methods to access encrypt and decrypt are OK because if not, I wouldn't be able to read the values. But I leave my cryptographic class to see if you see any errors in the logic.
public class CryptographicUtils {
private static final String TAG = "CrytographicUtils";
/**
* Transforms Byte array into Hexadecimal String.
*
* #param buf Byte array to be transformed.
* #return String.
*/
public static String asHex(byte[] buf) {
StringBuilder strum = new StringBuilder(buf.length * 2);
int i;
for (i = 0; i < buf.length; i++) {
if (((int) buf[i] & 0xff) < 0x10) {
strum.append("0");
}
strum.append(Long.toString((int) buf[i] & 0xff, 16));
}
return strum.toString();
}
/**
* Transforms Hexadecimal String into Byte array.
*
* #param string String to be transformed.
* #return Byte array,
*/
public static byte[] fromHexString(String string) {
int len = string.length();
byte[] data = new byte[len / 2];
for (int i = 0; i < len; i += 2) {
data[i / 2] = (byte) ((Character.digit(string.charAt(i), 16) << 4)
+ Character.digit(string.charAt(i + 1), 16));
}
return data;
}
/**
* Generates the Biometric Key.
*
* #param context Context.
*/
public static void generateBiometricKey(Context context, String keyAlias, boolean userAuthenticationRequired, int validationTimeout) {
try {
KeyGenParameterSpec.Builder spec = new KeyGenParameterSpec.Builder(
keyAlias,
KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT);
spec.setBlockModes(KeyProperties.BLOCK_MODE_CBC);
spec.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_PKCS7);
spec.setKeySize(256);
spec.setUserAuthenticationRequired(userAuthenticationRequired);
spec.setUserAuthenticationValidityDurationSeconds(validationTimeout);
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.M ) {
spec.setInvalidatedByBiometricEnrollment(userAuthenticationRequired);
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
boolean hasStrongBox = context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_STRONGBOX_KEYSTORE);
spec.setDigests(KeyProperties.DIGEST_SHA256);
spec.setIsStrongBoxBacked(hasStrongBox);
}
generateSecretKey(spec.build());
} catch (NoSuchProviderException | NoSuchAlgorithmException | InvalidAlgorithmParameterException e) {
e.printStackTrace();
Toast.makeText(context,
"Authentication error: " + e.getMessage(), Toast.LENGTH_SHORT)
.show();
}
}
/**
* Generates the Biometric Secret Key.
*
* #param keyGenParameterSpec Key Generator.
* #throws NoSuchProviderException Exception with Security Provider.
* #throws NoSuchAlgorithmException Exception with cryptographic algorithm.
* #throws InvalidAlgorithmParameterException Exception for invalid or inappropriate algorithm parameters.
*/
private static void generateSecretKey(KeyGenParameterSpec keyGenParameterSpec) throws NoSuchProviderException, NoSuchAlgorithmException, InvalidAlgorithmParameterException {
KeyGenerator keyGenerator = KeyGenerator.getInstance(
KeyProperties.KEY_ALGORITHM_AES, "AndroidKeyStore");
keyGenerator.init(keyGenParameterSpec);
keyGenerator.generateKey();
}
/**
* Obtains a Secret Key.
*
* #return Secret Key.
* #throws KeyStoreException KeyStore exception.
* #throws CertificateException Exception of certificate problems.
* #throws NoSuchAlgorithmException Exception with cryptographic algorithm.
* #throws IOException I/O exception.
* #throws UnrecoverableKeyException Exception key in the keystore cannot be recovered
*/
public static SecretKey getSecretKey(String keyAlias) throws KeyStoreException, CertificateException, NoSuchAlgorithmException, IOException, UnrecoverableKeyException {
KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore");
// Before the keystore can be accessed, it must be loaded.
keyStore.load(null);
return ((SecretKey) keyStore.getKey(keyAlias, null));
}
/**
* Obtains a Cipher.
*
* #return Cipher.
* #throws NoSuchPaddingException Exception padding mechanism is not available in the environment.
* #throws NoSuchAlgorithmException Exception with cryptographic algorithm.
*/
public static Cipher getEncryptCipher(Context context, String keyAlias, boolean userAuthenticationRequired, int validationTimeout) throws NoSuchPaddingException, NoSuchAlgorithmException, KeyStoreException, CertificateException, NoSuchAlgorithmException, IOException, UnrecoverableKeyException, InvalidKeyException {
CryptographicUtils.generateBiometricKey(context, keyAlias, userAuthenticationRequired, validationTimeout);
Cipher res = Cipher.getInstance(KeyProperties.KEY_ALGORITHM_AES + "/"
+ KeyProperties.BLOCK_MODE_CBC + "/"
+ KeyProperties.ENCRYPTION_PADDING_PKCS7);
SecretKey secretKey = getSecretKey(keyAlias);
res.init(Cipher.ENCRYPT_MODE, secretKey);
return res;
}
/**
* Obtains a Decrypt Cipher.
*
* #throws NoSuchPaddingException Exception padding mechanism is not available in the environment.
* #throws NoSuchAlgorithmException Exception with cryptographic algorithm.
* #throws InvalidAlgorithmParameterException Exception for invalid or inappropriate algorithm parameters.
* #throws InvalidKeyException Exception for invalid Keys.
*/
public static Cipher getDecryptCipher(String keyAlias, String ivString) throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidAlgorithmParameterException, InvalidKeyException, UnrecoverableKeyException, CertificateException, KeyStoreException, IOException {
SecretKey secretKey = CryptographicUtils.getSecretKey(keyAlias);
String algorithm = KeyProperties.KEY_ALGORITHM_AES;
String blockMode = KeyProperties.BLOCK_MODE_CBC;
String padding = KeyProperties.ENCRYPTION_PADDING_PKCS7;
String alg = algorithm + "/" + blockMode + "/" + padding;
Cipher res = Cipher.getInstance(alg);
byte[] iv = fromHexString(ivString);
res.init(Cipher.DECRYPT_MODE, secretKey, new IvParameterSpec(iv));
return res;
}
/**
* The Cipher created with [getInitializedCipherForEncryption] is used here
*/
public static String encryptData(String plainText, Cipher cipher) throws BadPaddingException, IllegalBlockSizeException {
return asHex(Objects.requireNonNull(Objects.requireNonNull(cipher.doFinal(plainText.getBytes(Charset.defaultCharset())))));
}
public static String getIV(Cipher cipher) {
return asHex(cipher.getIV());
}
/**
* The Cipher created with [getInitializedCipherForDecryption] is used here
*/
public static String decryptData(String ciphertext, Cipher cipher) throws BadPaddingException, IllegalBlockSizeException {
byte [] cipherData = fromHexString(ciphertext);
byte[] passBioByteArray = cipher.doFinal(cipherData);
return new String(passBioByteArray, Charset.defaultCharset());
}
}
I understand that I can store more than one value in the keystore with a particular keyalias, or not?
Thanks for all
Related
i am working on one project which Uses AccessibilityService class of android.
Getting Password through AccessibillityService ( TYPE_VIEW_TEXT_CHANGED ) .
But Password is in form of dots ........... .
How to convert these dots to text string?
or
is there is any possible decryption solution ?
Thanks
Create a Key Object, using the KeyGenerator, for the DESede
algorithm.
Create a Cipher that implements the DESede transformation, with the
getInstance(String algorithm) API method.
Here is code:-
public class Main {
static String algorithm = "DESede";
public static void main(String[] args) throws Exception {
Key symKey = KeyGenerator.getInstance(algorithm).generateKey();
Cipher c = Cipher.getInstance(algorithm);
byte[] encryptionBytes = encryptF("texttoencrypt", symKey, c);
System.out.println("Decrypted: " + decryptF(encryptionBytes, symKey, c));
}
private static byte[] encryptF(String input, Key pkey, Cipher c) throws InvalidKeyException, BadPaddingException,
IllegalBlockSizeException {
c.init(Cipher.ENCRYPT_MODE, pkey);
byte[] inputBytes = input.getBytes();
return c.doFinal(inputBytes);
}
private static String decryptF(byte[] encryptionBytes, Key pkey, Cipher c) throws InvalidKeyException,
BadPaddingException, IllegalBlockSizeException {
c.init(Cipher.DECRYPT_MODE, pkey);
byte[] decrypt = c.doFinal(encryptionBytes);
String decrypted = new String(decrypt);
return decrypted;
}
}
I am trying to implement client side encryption/decryption on Android platform by referencing IOS implementations. I am wrestling with the question that encryption and decryption on Android and IOS platform are different even they have used the same algorithm. Let`s say, when Android device encrypt and upload a file to server, the IOS device couldn`t download and decrypt it correctly.
The algorithm I am using
Encrypt the file key with the user provided password. We first use PBKDF2 algorithm (1000 iteratioins of SHA256) to derive a key/iv pair from the password, then use AES 256/CBC to encrypt the file key. The result is called the "encrypted file key". This encrypted file key will be sent to and stored on the server. When you need to access the data, you can decrypt the file key from the encrypted file key.
All file data is encrypted by the file key with AES 256/CBC. We use PBKDF2 algorithm (1000 iterations of SHA256) to derive key/iv pair from the file key.
store the derived key/iv pair locally and use them to encrypt files. After encryption, the data is uploaded to the server. The same with decrypt files when downloading files.
Android code
private static final String TAG = Crypto.class.getSimpleName();
private static final String CIPHER_ALGORITHM = "AES/CBC/NoPadding";
private static int KEY_LENGTH = 32;
private static int KEY_LENGTH_SHORT = 16;
// minimum values recommended by PKCS#5, increase as necessary
private static int ITERATION_COUNT = 1000;
// Should generate random salt for each repo
private static byte[] salt = {(byte) 0xda, (byte) 0x90, (byte) 0x45, (byte) 0xc3, (byte) 0x06, (byte) 0xc7, (byte) 0xcc, (byte) 0x26};
private Crypto() {
}
/**
* decrypt repo encKey
*
* #param password
* #param randomKey
* #param version
* #return
* #throws UnsupportedEncodingException
* #throws NoSuchAlgorithmException
*/
public static String deriveKeyPbkdf2(String password, String randomKey, int version) throws UnsupportedEncodingException, NoSuchAlgorithmException {
if (TextUtils.isEmpty(password) || TextUtils.isEmpty(randomKey)) {
return null;
}
PKCS5S2ParametersGenerator gen = new PKCS5S2ParametersGenerator(new SHA256Digest());
gen.init(PBEParametersGenerator.PKCS5PasswordToUTF8Bytes(password.toCharArray()), salt, ITERATION_COUNT);
byte[] keyBytes;
if (version == 2) {
keyBytes = ((KeyParameter) gen.generateDerivedMacParameters(KEY_LENGTH * 8)).getKey();
} else
keyBytes = ((KeyParameter) gen.generateDerivedMacParameters(KEY_LENGTH_SHORT * 8)).getKey();
SecretKey realKey = new SecretKeySpec(keyBytes, "AES");
final byte[] iv = deriveIVPbkdf2(realKey.getEncoded());
return seafileDecrypt(fromHex(randomKey), realKey, iv);
}
public static byte[] deriveIVPbkdf2(byte[] key) throws UnsupportedEncodingException {
PKCS5S2ParametersGenerator gen = new PKCS5S2ParametersGenerator(new SHA256Digest());
gen.init(key, salt, 10);
return ((KeyParameter) gen.generateDerivedMacParameters(KEY_LENGTH_SHORT * 8)).getKey();
}
/**
* All file data is encrypted by the file key with AES 256/CBC.
*
* We use PBKDF2 algorithm (1000 iterations of SHA256) to derive key/iv pair from the file key.
* After encryption, the data is uploaded to the server.
*
* #param plaintext
* #param key
* #return
*/
private static byte[] seafileEncrypt(byte[] plaintext, SecretKey key, byte[] iv) {
try {
Cipher cipher = Cipher.getInstance(CIPHER_ALGORITHM);
IvParameterSpec ivParams = new IvParameterSpec(iv);
cipher.init(Cipher.ENCRYPT_MODE, key, ivParams);
return cipher.doFinal(plaintext);
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
Log.e(TAG, "NoSuchAlgorithmException " + e.getMessage());
return null;
} catch (InvalidKeyException e) {
e.printStackTrace();
Log.e(TAG, "InvalidKeyException " + e.getMessage());
return null;
} catch (InvalidAlgorithmParameterException e) {
e.printStackTrace();
Log.e(TAG, "InvalidAlgorithmParameterException " + e.getMessage());
return null;
} catch (NoSuchPaddingException e) {
e.printStackTrace();
Log.e(TAG, "NoSuchPaddingException " + e.getMessage());
return null;
} catch (IllegalBlockSizeException e) {
e.printStackTrace();
Log.e(TAG, "IllegalBlockSizeException " + e.getMessage());
return null;
} catch (BadPaddingException e) {
e.printStackTrace();
Log.e(TAG, "BadPaddingException " + e.getMessage());
return null;
}
}
/**
* All file data is encrypted by the file key with AES 256/CBC.
*
* We use PBKDF2 algorithm (1000 iterations of SHA256) to derive key/iv pair from the file key.
* After encryption, the data is uploaded to the server.
*
* #param plaintext
* #param key
* #return
*/
public static byte[] encrypt(byte[] plaintext, String key, byte[] iv, int version) throws NoSuchAlgorithmException {
PKCS5S2ParametersGenerator gen = new PKCS5S2ParametersGenerator(new SHA256Digest());
gen.init(PBEParametersGenerator.PKCS5PasswordToUTF8Bytes(key.toCharArray()), salt, ITERATION_COUNT);
byte[] keyBytes;
if (version == 2) {
keyBytes = ((KeyParameter) gen.generateDerivedMacParameters(KEY_LENGTH * 8)).getKey();
} else
keyBytes = ((KeyParameter) gen.generateDerivedMacParameters(KEY_LENGTH_SHORT * 8)).getKey();
SecretKey realKey = new SecretKeySpec(keyBytes, "AES");
return seafileEncrypt(plaintext, realKey , iv);
}
IOS code
+ (int)deriveKey:(const char *)data_in inlen:(int)in_len version:(int)version key:(unsigned char *)key iv:(unsigned char *)iv
{
unsigned char salt[8] = { 0xda, 0x90, 0x45, 0xc3, 0x06, 0xc7, 0xcc, 0x26 };
if (version == 2) {
PKCS5_PBKDF2_HMAC (data_in, in_len,
salt, sizeof(salt),
1000,
EVP_sha256(),
32, key);
PKCS5_PBKDF2_HMAC ((char *)key, 32,
salt, sizeof(salt),
10,
EVP_sha256(),
16, iv);
return 0;
} else if (version == 1)
return EVP_BytesToKey (EVP_aes_128_cbc(), /* cipher mode */
EVP_sha1(), /* message digest */
salt, /* salt */
(unsigned char*)data_in,
in_len,
1 << 19, /* iteration times */
key, /* the derived key */
iv); /* IV, initial vector */
else
return EVP_BytesToKey (EVP_aes_128_ecb(), /* cipher mode */
EVP_sha1(), /* message digest */
NULL, /* salt */
(unsigned char*)data_in,
in_len,
3, /* iteration times */
key, /* the derived key */
iv); /* IV, initial vector */
}
+(int)seafileEncrypt:(char **)data_out outlen:(int *)out_len datain:(const char *)data_in inlen:(const int)in_len version:(int)version key:(uint8_t *)key iv:(uint8_t *)iv
{
int ret, blks;
EVP_CIPHER_CTX ctx;
EVP_CIPHER_CTX_init (&ctx);
if (version == 2)
ret = EVP_EncryptInit_ex (&ctx,
EVP_aes_256_cbc(), /* cipher mode */
NULL, /* engine, NULL for default */
key, /* derived key */
iv); /* initial vector */
else if (version == 1)
ret = EVP_EncryptInit_ex (&ctx,
EVP_aes_128_cbc(), /* cipher mode */
NULL, /* engine, NULL for default */
key, /* derived key */
iv); /* initial vector */
else
ret = EVP_EncryptInit_ex (&ctx,
EVP_aes_128_ecb(), /* cipher mode */
NULL, /* engine, NULL for default */
key, /* derived key */
iv); /* initial vector */
if (ret == DEC_FAILURE)
return -1;
blks = (in_len / BLK_SIZE) + 1;
*data_out = (char *)malloc (blks * BLK_SIZE);
if (*data_out == NULL) {
Debug ("failed to allocate the output buffer.\n");
goto enc_error;
}
int update_len, final_len;
/* Do the encryption. */
ret = EVP_EncryptUpdate (&ctx,
(unsigned char*)*data_out,
&update_len,
(unsigned char*)data_in,
in_len);
if (ret == ENC_FAILURE)
goto enc_error;
/* Finish the possible partial block. */
ret = EVP_EncryptFinal_ex (&ctx,
(unsigned char*)*data_out + update_len,
&final_len);
*out_len = update_len + final_len;
/* out_len should be equal to the allocated buffer size. */
if (ret == ENC_FAILURE || *out_len != (blks * BLK_SIZE))
goto enc_error;
EVP_CIPHER_CTX_cleanup (&ctx);
return 0;
enc_error:
EVP_CIPHER_CTX_cleanup (&ctx);
*out_len = -1;
if (*data_out != NULL)
free (*data_out);
*data_out = NULL;
return -1;
}
+ (void)generateKey:(NSString *)password version:(int)version encKey:(NSString *)encKey key:(uint8_t *)key iv:(uint8_t *)iv
{
unsigned char key0[32], iv0[16];
char passwordPtr[256] = {0}; // room for terminator (unused)
[password getCString:passwordPtr maxLength:sizeof(passwordPtr) encoding:NSUTF8StringEncoding];
if (version < 2) {
[NSData deriveKey:passwordPtr inlen:(int)password.length version:version key:key iv:iv];
return;
}
[NSData deriveKey:passwordPtr inlen:(int)password.length version:version key:key0 iv:iv0];
char enc_random_key[48], dec_random_key[48];
int outlen;
hex_to_rawdata(encKey.UTF8String, enc_random_key, 48);
[NSData seafileDecrypt:dec_random_key outlen:&outlen datain:(char *)enc_random_key inlen:48 version:2 key:key0 iv:iv0];
[NSData deriveKey:dec_random_key inlen:32 version:2 key:key iv:iv];
}
- (NSData *)encrypt:(NSString *)password encKey:(NSString *)encKey version:(int)version
{
uint8_t key[kCCKeySizeAES256+1] = {0}, iv[kCCKeySizeAES128+1];
[NSData generateKey:password version:version encKey:encKey key:key iv:iv];
char *data_out;
int outlen;
int ret = [NSData seafileEncrypt:&data_out outlen:&outlen datain:self.bytes inlen:(int)self.length version:version key:key iv:iv];
if (ret < 0) return nil;
return [NSData dataWithBytesNoCopy:data_out length:outlen];
}
Here is the complete project for anyone who found this useful.
Support client side encryption #487
How does an encrypted library work?
Hi me too had the same problem.
I found 2 things causing mismatch for my code:
1. ios and android encryption algorithms was not same(i have requested PKCS7Padding algorithm in ios and i tried NoPadding and AES/CBC/PKCS5Padding algorithms in android.
2. The key generated in ios is different than in Android.
Please see my worked code both gives same output in ios and android:
IOS:
- (NSString *) encryptString:(NSString*)plaintext withKey:(NSString*)key {
NSData *data = [[plaintext dataUsingEncoding:NSUTF8StringEncoding] AES256EncryptWithKey:key];
return [data base64EncodedStringWithOptions:kNilOptions];
}
- (NSString *) decryptString:(NSString *)ciphertext withKey:(NSString*)key {
if ([ciphertext isKindOfClass:[NSString class]]) {
NSData *data = [[NSData alloc] initWithBase64EncodedString:ciphertext options:kNilOptions];
return [[NSString alloc] initWithData:[data AES256DecryptWithKey:key] encoding:NSUTF8StringEncoding];
}
return nil;
}
Android:(We modified aes w.r.t iOS default method)
private static String Key = "your key";
public static String encryptString(String stringToEncode) throws NullPointerException {
try {
SecretKeySpec skeySpec = getKey(Key);
byte[] clearText = stringToEncode.getBytes("UTF8");
final byte[] iv = new byte[16];
Arrays.fill(iv, (byte) 0x00);
IvParameterSpec ivParameterSpec = new IvParameterSpec(iv);
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS7Padding");
cipher.init(Cipher.ENCRYPT_MODE, skeySpec, ivParameterSpec);
String encrypedValue = Base64.encodeToString(cipher.doFinal(clearText), Base64.DEFAULT);
return encrypedValue;
} catch (Exception e) {
e.printStackTrace();
}
return "";
}
public static String encryptString(String stringToEncode) throws NullPointerException {
try {
SecretKeySpec skeySpec = getKey(Key);
byte[] clearText = stringToEncode.getBytes("UTF8");
final byte[] iv = new byte[16];
Arrays.fill(iv, (byte) 0x00);
IvParameterSpec ivParameterSpec = new IvParameterSpec(iv);
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS7Padding");
cipher.init(Cipher.DECRYPT_MODE, skeySpec, ivParameterSpec);
byte[] cipherData = cipher.doFinal(Base64.decode(stringToEncode.getBytes("UTF-8"), Base64.DEFAULT));
String decoded = new String(cipherData, "UTF-8");
return decoded;
} catch (Exception e) {
e.printStackTrace();
}
return "";
}
private static SecretKeySpec getKey(String password) throws UnsupportedEncodingException {
int keyLength = 256;
byte[] keyBytes = new byte[keyLength / 8];
Arrays.fill(keyBytes, (byte) 0x0);
byte[] passwordBytes = password.getBytes("UTF-8");
int length = passwordBytes.length < keyBytes.length ? passwordBytes.length : keyBytes.length;
System.arraycopy(passwordBytes, 0, keyBytes, 0, length);
SecretKeySpec key = new SecretKeySpec(keyBytes, "AES");
return key;
}
Hope this help u guys
Thanks
I've recently developed an app that I am soon to put on the Google play store. In this app I plan on adding one in-app purchase that once purchased allows the user to change colour settings(background & text).
I realised that checking the Google play store is unnecessary every time the application runs, especially if the purchase has been made already.It may also cause a problem if the user doesn't have GPRS/Wifi when using the app!
I was therefore thinking about creating a shared preference which would act as the condition to check whether the user has purchased the in-app purchase.
Is there any other way that is maybe more secure? as I've read shared preferences can be altered quite easily.
Any advice or suggestions would be appreciated.
You may use ProGuard.
Or you can save purchased status in secure way using some Encrypted text either in shared preference or in database. So it gets harder to manipulate it.
Encryption example:
Encrypt:
MCrypt mcrypt = new MCrypt();
String encrypted = MCrypt.bytesToHex(mcrypt.encrypt("Text to Encrypt"));
Decrypt:
MCrypt mcrypt = new MCrypt();
String decrypted = new String(mcrypt.decrypt(encrypted));
Mcrypt.java
public class MCrypt {
private String iv = "fedcba9876543210";//Dummy iv (CHANGE IT!)
private IvParameterSpec ivspec;
private SecretKeySpec keyspec;
private Cipher cipher;
private String SecretKey = "0123456789abcdef";//Dummy secretKey (CHANGE IT!)
public MCrypt() {
ivspec = new IvParameterSpec(iv.getBytes());
keyspec = new SecretKeySpec(SecretKey.getBytes(), "AES");
try {
cipher = Cipher.getInstance("AES/CBC/NoPadding");
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} catch (NoSuchPaddingException e) {
e.printStackTrace();
}
}
public byte[] encrypt(String text) throws Exception {
if (text == null || text.length() == 0) throw new Exception("Empty string");
byte[] encrypted = null;
cipher.init(Cipher.ENCRYPT_MODE, keyspec, ivspec);
encrypted = cipher.doFinal(padString(text).getBytes());
try {
encrypted = android.util.Base64.encode(encrypted, android.util.Base64.NO_PADDING);
} catch (NoClassDefFoundError e) {
}
return encrypted;
}
public byte[] decrypt(String code) throws Exception {
if (code == null || code.length() == 0) throw new Exception("Empty string");
byte[] decrypted = null;
cipher.init(Cipher.DECRYPT_MODE, keyspec, ivspec);
try {
decrypted = cipher.doFinal(android.util.Base64.decode(code, android.util.Base64.NO_PADDING));
} catch (NoClassDefFoundError e) {
}
return decrypted;
}
private static String padString(String source) {
char paddingChar = ' ';
int size = 16;
int x = source.length() % size;
int padLength = size - x;
for (int i = 0; i < padLength; i++) {
source += paddingChar;
}
return source;
}
}
Reference:
128 bit encryption
First of all, I've already seen
Android 4.2 broke my AES encrypt/decrypt code
and
Encryption error on Android 4.2
and the provided solution:
SecureRandom sr = null;
if (android.os.Build.VERSION.SDK_INT >= JELLY_BEAN_4_2) {
sr = SecureRandom.getInstance("SHA1PRNG", "Crypto");
} else {
sr = SecureRandom.getInstance("SHA1PRNG");
}
doesn't work for me, because, when decoding data encrypted in Android<4.2 in Android 4.2, I get:
javax.crypto.BadPaddingException: pad block corrupted
at com.android.org.bouncycastle.jcajce.provider.symmetric.util.BaseBlockCipher.engineDoFinal(BaseBlockCipher.java:709)
My code is quite simple, and was working until Android 4.2:
public static byte[] encrypt(byte[] data, String seed) throws Exception {
KeyGenerator keygen = KeyGenerator.getInstance("AES");
SecureRandom secrand = SecureRandom.getInstance("SHA1PRNG");
secrand.setSeed(seed.getBytes());
keygen.init(128, secrand);
SecretKey seckey = keygen.generateKey();
byte[] rawKey = seckey.getEncoded();
SecretKeySpec skeySpec = new SecretKeySpec(rawKey, "AES");
Cipher cipher = Cipher.getInstance("AES");
cipher.init(Cipher.ENCRYPT_MODE, skeySpec);
return cipher.doFinal(data);
}
public static byte[] decrypt(byte[] data, String seed) throws Exception {
KeyGenerator keygen = KeyGenerator.getInstance("AES");
SecureRandom secrand = SecureRandom.getInstance("SHA1PRNG");
secrand.setSeed(seed.getBytes());
keygen.init(128, secrand);
SecretKey seckey = keygen.generateKey();
byte[] rawKey = seckey.getEncoded();
SecretKeySpec skeySpec = new SecretKeySpec(rawKey, "AES");
Cipher cipher = Cipher.getInstance("AES");
cipher.init(Cipher.DECRYPT_MODE, skeySpec);
return cipher.doFinal(data);
}
My guess is that the default provider wasn't the only thing that changed in Android 4.2, otherwise my code would work with the proposed solution.
My code was based on some post I found here at StackOverflow a long time ago; I see that it differs from the mentioned posts as it just crypts and decrypts byte arrays, whereas the others solutions crypt and decrypt Strings (HEX Strings, I think).
Does it have to do with the seed? Does it have a min/max length, restriction of chars, etc?
Any idea / solution?
EDIT:
After a lot of tests, I see that there are 2 problems:
The provider changed in Android 4.2 (API 17) -> This one is easy to fix, just apply the solution I mentioned at top of the post
BouncyCastle changed from 1.34 to 1.45 in Android 2.2 (API 8)->Android2.3 (API 9), so the decryption problem I previously told is the same as described here: BouncyCastle AES error when upgrading to 1.45
So now the question is: is there any way to recover data crypted in BouncyCastle 1.34 in BouncyCastle 1.45+?
First a disclaimer:
DO NOT ever use SecureRandom to derive a key! This is broken and doesn't make sense!
The following block of code from the question tries to deterministically derive a key from a password, called the "seed" as the password is used to "seed" the random number generator.
KeyGenerator keygen = KeyGenerator.getInstance("AES");
SecureRandom secrand = SecureRandom.getInstance("SHA1PRNG");
secrand.setSeed(seed.getBytes());
keygen.init(128, secrand);
SecretKey seckey = keygen.generateKey();
However, the "SHA1PRNG" algorithm is not well defined and implementations of "SHA1PRNG" may return different or even fully random keys as a result.
If you're reading an AES key from disk, just store the actual key and don't go through this weird dance. You can get a SecretKey for AES usage from the bytes by doing:
SecretKey key = new SecretKeySpec(keyBytes, "AES");
If you're using a password to derive a key, follow Nelenkov's excellent tutorial with the caveat that a good rule of thumb is the salt size should be the same size as the key output.
The iterationCount (work factor) is of course subject to change and should be changed as CPU power progresses - generally it is recommended not to go lower than 40 to 100K as of 2018. Beware that PBKDF2 only adds a constant time delay to guessing passwords; it is not a replacement for really weak passwords.
It looks like this:
/* 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];
random.nextBytes(salt);
/* Use this to derive the key from the password: */
KeySpec keySpec = new PBEKeySpec(password.toCharArray(), salt,
iterationCount, keyLength);
SecretKeyFactory keyFactory = SecretKeyFactory
.getInstance("PBKDF2WithHmacSHA1");
byte[] keyBytes = keyFactory.generateSecret(keySpec).getEncoded();
SecretKey key = new SecretKeySpec(keyBytes, "AES");
That's it. Anything else you should not use.
private static final int ITERATION_COUNT = 1000;
private static final int KEY_LENGTH = 256;
private static final String PBKDF2_DERIVATION_ALGORITHM = "PBKDF2WithHmacSHA1";
private static final String CIPHER_ALGORITHM = "AES/CBC/PKCS5Padding";
private static final int PKCS5_SALT_LENGTH = 32;
private static final String DELIMITER = "]";
private static final SecureRandom random = new SecureRandom();
public static String encrypt(String plaintext, String password) {
byte[] salt = generateSalt();
SecretKey key = deriveKey(password, salt);
try {
Cipher cipher = Cipher.getInstance(CIPHER_ALGORITHM);
byte[] iv = generateIv(cipher.getBlockSize());
IvParameterSpec ivParams = new IvParameterSpec(iv);
cipher.init(Cipher.ENCRYPT_MODE, key, ivParams);
byte[] cipherText = cipher.doFinal(plaintext.getBytes("UTF-8"));
if(salt != null) {
return String.format("%s%s%s%s%s",
toBase64(salt),
DELIMITER,
toBase64(iv),
DELIMITER,
toBase64(cipherText));
}
return String.format("%s%s%s",
toBase64(iv),
DELIMITER,
toBase64(cipherText));
} catch (GeneralSecurityException e) {
throw new RuntimeException(e);
} catch (UnsupportedEncodingException e) {
throw new RuntimeException(e);
}
}
public static String decrypt(String ciphertext, String password) {
String[] fields = ciphertext.split(DELIMITER);
if(fields.length != 3) {
throw new IllegalArgumentException("Invalid encypted text format");
}
byte[] salt = fromBase64(fields[0]);
byte[] iv = fromBase64(fields[1]);
byte[] cipherBytes = fromBase64(fields[2]);
SecretKey key = deriveKey(password, salt);
try {
Cipher cipher = Cipher.getInstance(CIPHER_ALGORITHM);
IvParameterSpec ivParams = new IvParameterSpec(iv);
cipher.init(Cipher.DECRYPT_MODE, key, ivParams);
byte[] plaintext = cipher.doFinal(cipherBytes);
return new String(plaintext, "UTF-8");
} catch (GeneralSecurityException e) {
throw new RuntimeException(e);
} catch (UnsupportedEncodingException e) {
throw new RuntimeException(e);
}
}
private static byte[] generateSalt() {
byte[] b = new byte[PKCS5_SALT_LENGTH];
random.nextBytes(b);
return b;
}
private static byte[] generateIv(int length) {
byte[] b = new byte[length];
random.nextBytes(b);
return b;
}
private static SecretKey deriveKey(String password, byte[] salt) {
try {
KeySpec keySpec = new PBEKeySpec(password.toCharArray(), salt, ITERATION_COUNT, KEY_LENGTH);
SecretKeyFactory keyFactory = SecretKeyFactory.getInstance(PBKDF2_DERIVATION_ALGORITHM);
byte[] keyBytes = keyFactory.generateSecret(keySpec).getEncoded();
return new SecretKeySpec(keyBytes, "AES");
} catch (GeneralSecurityException e) {
throw new RuntimeException(e);
}
}
private static String toBase64(byte[] bytes) {
return Base64.encodeToString(bytes, Base64.NO_WRAP);
}
private static byte[] fromBase64(String base64) {
return Base64.decode(base64, Base64.NO_WRAP);
}
Source
The problem is that with the new provider, the following snippet of code
KeyGenerator keygen = KeyGenerator.getInstance("AES");
SecureRandom secrand = SecureRandom.getInstance("SHA1PRNG");
secrand.setSeed(seed.getBytes());
keygen.init(128, secrand);
SecretKey seckey = keygen.generateKey();
byte[] rawKey = seckey.getEncoded();
generates a different, genuinely random rawKey every time it's executed. So, you're trying to decrypt with a key different from the one used to encrypt data and you get the exception. You won't be able to recover your key or data when it has been generated this way, and only the seed has been saved.
What fixed it for me (as #Giorgio suggested) was just replacing this:
SecureRandom secrand = SecureRandom.getInstance("SHA1PRNG");
with this:
SecureRandom secrand = SecureRandom.getInstance("SHA1PRNG", "Crypto");
I am unable to give you answer to your asked question but I'd simply try to work this around >- if you face some problems with bouncycastle across devices/OS version, you should ditch built-in versions completely and instead add bouncycastle as jar to your project, change your import to point to that jar, rebuild and assuming it all works you'd be immune to android built-in version changes from now on.
Because all of this didn't help me to generate an encrypted password which was deterministic on all android devices (>=2.1), I searched for another AES implementation. I found one which works for me on all devices. I'm not a security specialist, I'm not sure if the technique isn't as secure as it could be. I'm only posting the code for people who have run in the same problem that I had face before.
import java.security.GeneralSecurityException;
import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import android.util.Log;
public class EncodeDecodeAES {
private static final String TAG_DEBUG = "TAG";
private IvParameterSpec ivspec;
private SecretKeySpec keyspec;
private Cipher cipher;
private String iv = "fedcba9876543210";//Dummy iv (CHANGE IT!)
private String SecretKey = "0123456789abcdef";//Dummy secretKey (CHANGE IT!)
public EncodeDecodeAES() {
ivspec = new IvParameterSpec(iv.getBytes());
keyspec = new SecretKeySpec(SecretKey.getBytes(), "AES");
try {
cipher = Cipher.getInstance("AES/CBC/NoPadding");
} catch (GeneralSecurityException e) {
Log.d(TAG_DEBUG, e.getMessage());
}
}
public byte[] encrypt(String text) throws Exception {
if (text == null || text.length() == 0)
throw new Exception("Empty string");
byte[] encrypted = null;
try {
cipher.init(Cipher.ENCRYPT_MODE, keyspec, ivspec);
encrypted = cipher.doFinal(padString(text).getBytes());
} catch (Exception e) {
Log.d(TAG_DEBUG, e.getMessage());
throw new Exception("[encrypt] " + e.getMessage());
}
return encrypted;
}
public byte[] decrypt(String code) throws Exception {
if (code == null || code.length() == 0)
throw new Exception("Empty string");
byte[] decrypted = null;
try {
cipher.init(Cipher.DECRYPT_MODE, keyspec, ivspec);
decrypted = cipher.doFinal(hexToBytes(code));
} catch (Exception e) {
Log.d(TAG_DEBUG, e.getMessage());
throw new Exception("[decrypt] " + e.getMessage());
}
return decrypted;
}
public static String bytesToHex(byte[] data) {
if (data == null) {
return null;
}
int len = data.length;
String str = "";
for (int i = 0; i < len; i++) {
if ((data[i] & 0xFF) < 16)
str = str + "0" + java.lang.Integer.toHexString(data[i] & 0xFF);
else
str = str + java.lang.Integer.toHexString(data[i] & 0xFF);
}
return str;
}
public static byte[] hexToBytes(String str) {
if (str == null) {
return null;
} else if (str.length() < 2) {
return null;
} else {
int len = str.length() / 2;
byte[] buffer = new byte[len];
for (int i = 0; i < len; i++) {
buffer[i] = (byte) Integer.parseInt(str.substring(i * 2, i * 2 + 2), 16);
}
return buffer;
}
}
private static String padString(String source) {
char paddingChar = ' ';
int size = 16;
int x = source.length() % size;
int padLength = size - x;
for (int i = 0; i < padLength; i++) {
source += paddingChar;
}
return source;
}
}
You can use it like:
EncodeDecodeAES aes = new EncodeDecodeAES ();
/* Encrypt */
String encrypted = EncodeDecodeAES.bytesToHex(aes.encrypt("Text to Encrypt"));
/* Decrypt */
String decrypted = new String(aes.decrypt(encrypted));
Source: HERE
It's does have to do with the seed indeed and it's also should use multiple of 8 (like 8, 16, 24 or 32), try complete the seed with A's and B's or 1's and 0s (has to be something like this ABAB..., because AAA.. or BBB.. will not work also.) up to reach a multiple of 8 number. There is an other thing if you are reading and encrypting only bytes, (not converting it to Char64 as I did), then you need an appropriate PKCS5 or PKCS7 Padding, however in your case (due only 128bits and it's has been created with older versions of Android) PKCS5 would be enough, though you also should put it in your SecreteKeySpec something like "AES/CBC/PKCS5Padding" or "AES/ECB/PKCS5Padding" rather than just "AES", because Android 4.2 it's using PKCS7Padding as default and if it's only bytes you really need the same algorithm that was the default before. Try get a device with an Android earlier than 4.2 check the Object tree on your "keygen.init(128, secrand);" if I'm not mistaken it's has the label cipher, than use it.
Give it a try.
I am writing an application which encrypts and decrypts the user notes based on the user set password. i used the following algorithms for encryption/decryption
1. PBEWithSHA256And256BitAES-CBC-BC
2. PBEWithMD5And128BitAES-CBC-OpenSSL
e_Cipher = Cipher.getInstance(PBEWithSHA256And256BitAES-CBC-BC);
d_Cipher = Cipher.getInstance(PBEWithSHA256And256BitAES-CBC-BC);
e_Cipher.init()
d_Cipher.init()
encryption is working well, but when trying to decrypt it gives
Exception - Illegal Block size
after encryption i am converting the cipherText to HEX and storing it in a sqlite database. i am retrieving correct values from the sqlite database during decyption but when calling d_Cipher.dofinal() it throws the Exception.
I thought i missed to specify the padding and tried to check what are the other available cipher algorithms but i was unable to found.
so request you to please give the some knowledge on what are the cipher algorithms and padding that are supported by Android? if the algorithm which i used can be used for padding, how should i specify the padding mechanism?
I am pretty new to Encryption so tried a couple of algorithms which are available in BouncyCastle.java but unsuccessful.
As requested here is the code
public class CryptoHelper {
private static final String TAG = "CryptoHelper";
//private static final String PBEWithSHA256And256BitAES = "PBEWithSHA256And256BitAES-CBC-BC";
//private static final String PBEWithSHA256And256BitAES = "PBEWithMD5And128BitAES-CBC-OpenSSL";
private static final String PBEWithSHA256And256BitAES = "PBEWithMD5And128BitAES-CBC-OpenSSLPBEWITHSHA1AND3-KEYTRIPLEDES-CB";
private static final String randomAlgorithm = "SHA1PRNG";
public static final int SALT_LENGTH = 8;
public static final int SALT_GEN_ITER_COUNT = 20;
private final static String HEX = "0123456789ABCDEF";
private Cipher e_Cipher;
private Cipher d_Cipher;
private SecretKey secretKey;
private byte salt[];
public CryptoHelper(String password) throws InvalidKeyException, NoSuchAlgorithmException, NoSuchPaddingException, InvalidAlgorithmParameterException, InvalidKeySpecException {
char[] cPassword = password.toCharArray();
PBEKeySpec pbeKeySpec = new PBEKeySpec(cPassword);
PBEParameterSpec pbeParamSpec = new PBEParameterSpec(salt, SALT_GEN_ITER_COUNT);
SecretKeyFactory keyFac = SecretKeyFactory.getInstance(PBEWithSHA256And256BitAES);
secretKey = keyFac.generateSecret(pbeKeySpec);
SecureRandom saltGen = SecureRandom.getInstance(randomAlgorithm);
this.salt = new byte[SALT_LENGTH];
saltGen.nextBytes(this.salt);
e_Cipher = Cipher.getInstance(PBEWithSHA256And256BitAES);
d_Cipher = Cipher.getInstance(PBEWithSHA256And256BitAES);
e_Cipher.init(Cipher.ENCRYPT_MODE, secretKey, pbeParamSpec);
d_Cipher.init(Cipher.DECRYPT_MODE, secretKey, pbeParamSpec);
}
public String encrypt(String cleartext) throws IllegalBlockSizeException, BadPaddingException {
byte[] encrypted = e_Cipher.doFinal(cleartext.getBytes());
return convertByteArrayToHex(encrypted);
}
public String decrypt(String cipherString) throws IllegalBlockSizeException {
byte[] plainText = decrypt(convertStringtobyte(cipherString));
return(new String(plainText));
}
public byte[] decrypt(byte[] ciphertext) throws IllegalBlockSizeException {
byte[] retVal = {(byte)0x00};
try {
retVal = d_Cipher.doFinal(ciphertext);
} catch (BadPaddingException e) {
Log.e(TAG, e.toString());
}
return retVal;
}
public String convertByteArrayToHex(byte[] buf) {
if (buf == null)
return "";
StringBuffer result = new StringBuffer(2*buf.length);
for (int i = 0; i < buf.length; i++) {
appendHex(result, buf[i]);
}
return result.toString();
}
private static void appendHex(StringBuffer sb, byte b) {
sb.append(HEX.charAt((b>>4)&0x0f)).append(HEX.charAt(b&0x0f));
}
private static byte[] convertStringtobyte(String hexString) {
int len = hexString.length()/2;
byte[] result = new byte[len];
for (int i = 0; i < len; i++) {
result[i] = Integer.valueOf(hexString.substring(2*i, 2*i+2), 16).byteValue();
}
return result;
}
public byte[] getSalt() {
return salt;
}
public SecretKey getSecretKey() {
return secretKey;
}
public static SecretKey createSecretKey(char[] password) throws NoSuchAlgorithmException, InvalidKeySpecException {
PBEKeySpec pbeKeySpec = new PBEKeySpec(password);
SecretKeyFactory keyFac = SecretKeyFactory.getInstance(PBEWithSHA256And256BitAES);
return keyFac.generateSecret(pbeKeySpec);
}
}
I will call mCryptoHelper.decrypt(String str) then this results in Illegal block size exception
My Env: Android 1.6 on Eclipse
#Vamsi is correct, it looks like a new Salt is being generated. This should be generated once, and stored as a known to the program. If the salt changes, then the encrypt/decrypt data checks aren't going to match.
In the code, each time i am generating the "salt",
SecureRandom saltGen = SecureRandom.getInstance(randomAlgorithm);
this.salt = new byte[SALT_LENGTH];
saltGen.nextBytes(this.salt);
hence there is a difference between the encryption and decryption cipher. so it is giving the error Bad Pad block or Padding block corrupted.
If i declare the Salt to some known value, it is working fine.