Guide me on this. I am facing problem with decryption of string which is encrypted by iOS "AES/CBC/PKCS7Padding". It throws exception. I want to decrypt string in android, which is encrypted in ios.
In Android i am following this structure:
For Example:
String text = "rzp_test_DezQO1BVMXhkZY";
String key = "5b0904cfada01b8182bcc029b928244d"; // secret key - 128 bit key
String iv_key ="c999cbd1f130db1d";
I want to encrypt and decrypt 'text' string mentioned above. If i do encryption and decryption from Android only. then its working fine. but if i tried to decrypt key which is encrypted in ios then it throws errors.
// Create key and cipher
Cipher dcipher, d1cipher;
IvParameterSpec ivSpec = new IvParameterSpec(iv_key.getBytes());
Key aesKey = new SecretKeySpec(key.getBytes(), "AES");
ecipher = Cipher.getInstance("AES/CBC/PKCS7Padding");
dcipher = Cipher.getInstance("AES/CBC/PKCS7Padding");
ecipher.init(ENCRYPT_MODE, aesKey, ivSpec);
dcipher.init(DECRYPT_MODE, aesKey, ivSpec);
Encryption Algo:
public String encrypt(String str) throws Exception {
// Encode the string into bytes using utf-8
byte[] utf8 = str.getBytes("UTF8");
// Encrypt
byte[] enc = ecipher.doFinal(utf8);
// Encode bytes to base64 to get a string
return Base64.encodeToString(enc, Base64.DEFAULT);
}
Decryption Algo:
public String decrypt(String str) throws Exception {
// Decode base64 to get bytes
String decrypted = "";
try {
byte[] dec = Base64.decode(str, Base64.DEFAULT);
byte[] utf8 = dcipher.doFinal(dec);
// Decode using utf-8
decrypted = new String(utf8, "UTF8").trim();
} catch (Exception e) {
e.printStackTrace();
}
return decrypted;
}
Its working fine in Android for both Encryption and Decryption. Check with this:
String encrypted = encrypt(text);
System.out.println("Encrypted String: " + encrypted);
String decrypted = decrypt(encrypted);
System.out.println("Decrypted String: " + decrypted);
Problem is with decrypting below encrypted string from ios, This Throws error:
String decrypted1 = decrypt("c7076c78fc5d9d92c1d86c1500dcc0366ddf1b6e32df00ceadc911239935460d");
System.out.println("Decrypted String1: " + decrypted1);
Error is:
W/System.err: javax.crypto.BadPaddingException: error:1e000065:Cipher functions:OPENSSL_internal:BAD_DECRYPT
W/System.err: at com.android.org.conscrypt.NativeCrypto.EVP_CipherFinal_ex(Native Method)
W/System.err: at com.android.org.conscrypt.OpenSSLCipher$EVP_CIPHER.doFinalInternal(OpenSSLCipher.java:570)
W/System.err: at com.android.org.conscrypt.OpenSSLCipher.engineDoFinal(OpenSSLCipher.java:351)
W/System.err: at javax.crypto.Cipher.doFinal(Cipher.java:1741)
W/System.err: at com.example.aesencryption.Activities.MainActivity1.decrypt(MainActivity1.java:178)
Anyone can please guide me on this??
In IOS:
Following functions are used.
func aesEncrypt() -> String {
let iv: [UInt8] = Array(AES_IV.utf8)
let key: [UInt8] = Array(AES_SECRET.utf8)
do{
let encrypted = try AES (key: key, blockMode: CBC(iv: iv)).encrypt([UInt8](self.data(using: .utf8)!))
return Data(encrypted).base64EncodedString()
}
catch{
print("error on encrypting data ")
return ""
}
}
func aesDecrypt() -> String {
let iv: [UInt8] = Array(AES_IV.utf8)
let key: [UInt8] = Array(AES_SECRET.utf8)
do{
guard let data = Data(base64Encoded: self) else { return "" }
let decrypted = try AES(key: key, blockMode: CBC(iv: iv), padding: .pkcs7).decrypt([UInt8](data))
return String(bytes: decrypted, encoding: .utf8) ?? self
}
catch{
print("error on encrypting data ")
return ""
}
}
I got real string by using following method.
public static String decryptKey(String key_id) {
// Decode base64 to get bytes
String decrypted = "";
try {
Cipher dcipher = Cipher.getInstance("AES/CBC/PKCS7Padding");
dcipher.init(DECRYPT_MODE, aesKey, ivSpec);
byte[] utf8 = dcipher.doFinal(hexToBytes(key_id));
// Decode using utf-8
decrypted = new String(utf8, "UTF8").trim();
} catch (Exception e) {
e.printStackTrace();
}
return decrypted;
}
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;
}
}
I am using encryption - decryption in Android using public and private key respectively. I have both the keys saved in the RAW folder as a file. The encryption works perfectly, but for the decryption it always gives the following error:
java.security.spec.InvalidKeySpecException: java.lang.RuntimeException: error:0D0680A8:asn1 encoding routines:ASN1_CHECK_TLEN:wrong tag
I am enclosing the code snippet I have used:
public class AppUtils {
public static String encryptString(String value, Context context){
byte[] encodedBytes = null;
try {
//Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding", "AndroidOpenSSL");
Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
cipher.init(Cipher.ENCRYPT_MODE, getPublicKey(context) );
encodedBytes = cipher.doFinal(value.getBytes());
} catch (Exception e) {
e.printStackTrace();
}
return Base64.encodeToString(encodedBytes, Base64.DEFAULT);
}
public static String decryptString(String value, Context context){
byte[] decodedBytes = null;
try {
//Cipher c = Cipher.getInstance("RSA/ECB/PKCS1Padding", "AndroidOpenSSL");
Cipher c = Cipher.getInstance("RSA/ECB/PKCS1Padding");
c.init(Cipher.DECRYPT_MODE, getPrivateKey(context) );
decodedBytes = c.doFinal(Base64.decode(value, Base64.DEFAULT));
} catch (Exception e) {
e.printStackTrace();
}
return new String(decodedBytes);
}
public static PrivateKey getPrivateKey(Context context){
// reads the key_public key stored in a file
InputStream is = context.getResources().openRawResource(R.raw.key_private);
BufferedReader br = new BufferedReader(new InputStreamReader(is));
List<String> lines = new ArrayList<String>();
String line = null;
try {
while ((line = br.readLine()) != null)
lines.add(line);
// removes the first and last lines of the file (comments)
if (lines.size() > 1 && lines.get(0).startsWith("-----") && lines.get(lines.size()-1).startsWith("-----")) {
lines.remove(0);
lines.remove(lines.size()-1);
}
// concats the remaining lines to a single String
StringBuilder sb = new StringBuilder();
for (String aLine: lines)
sb.append(aLine);
String keyString = sb.toString();
byte [] encoded = Base64.decode(keyString, Base64.DEFAULT);
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(encoded);
PrivateKey myPrivKey = keyFactory.generatePrivate(keySpec);
return myPrivKey;
}catch (Exception e){
e.printStackTrace();
}
return null;
}
public static PublicKey getPublicKey(Context context){
// reads the key_public key stored in a file
InputStream is = context.getResources().openRawResource(R.raw.key_public);
BufferedReader br = new BufferedReader(new InputStreamReader(is));
List<String> lines = new ArrayList<String>();
String line = null;
try {
while ((line = br.readLine()) != null)
lines.add(line);
// removes the first and last lines of the file (comments)
if (lines.size() > 1 && lines.get(0).startsWith("-----") && lines.get(lines.size()-1).startsWith("-----")) {
lines.remove(0);
lines.remove(lines.size()-1);
}
// concats the remaining lines to a single String
StringBuilder sb = new StringBuilder();
for (String aLine: lines)
sb.append(aLine);
String keyString = sb.toString();
// converts the String to a PublicKey instance
byte[] keyBytes = Base64.decode(keyString, Base64.DEFAULT);
X509EncodedKeySpec spec = new X509EncodedKeySpec(keyBytes);
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
PublicKey key = keyFactory.generatePublic(spec);
return key;
}catch (Exception e){
e.printStackTrace();
}
return null;
}
}
Finally, I call the required function from MainActivity.java like this:
String encryptedString = AppUtils.encryptString("SHANKAR", MainActivity.this);
Log.d("DX1", " Encrypted String " + encryptedString );
String decryptedString = AppUtils.decryptString(encryptedString, MainActivity.this);
Log.d("DX1", " decrypted String " + decryptedString );
I see that, I get the encrypted string properly, but at the time of decryption the statement :
PrivateKey myPrivKey = keyFactory.generatePrivate(keySpec);
is throwing me the above error.But I can decrypt the encrypted string using the private key in any online tool.
Can someone kindly help me out.
My public key:
-----BEGIN PUBLIC KEY-----
MIGeMA0GCSqGSIb3DQEBAQUAA4GMADCBiAKBgEac6cgM4Ch5vY2Rqvzw2ARaNEHv
PCbXWW1nPy3ft8CNFyLoIltwrnouY0azYECclONARh48qQUQ+UG62wNUtciLq9yX
3m0ePE7u/RYmNUnGWok6LMNZK1gGBu6TBnzNjDWi3CuR00xFzZ2TBtUMDowOa/+b
tfGTywGDLPJjgbtPAgMBAAE=
-----END PUBLIC KEY-----
My private key:
-----BEGIN RSA PRIVATE KEY-----
MIICWgIBAAKBgEac6cgM4Ch5vY2Rqvzw2ARaNEHvPCbXWW1nPy3ft8CNFyLoIltw
rnouY0azYECclONARh48qQUQ+UG62wNUtciLq9yX3m0ePE7u/RYmNUnGWok6LMNZ
K1gGBu6TBnzNjDWi3CuR00xFzZ2TBtUMDowOa/+btfGTywGDLPJjgbtPAgMBAAEC
gYAAxTd7ukA70NAzmjI+XjZNHdrSGVUTq2fLXMQAsR8lF6T3+YZebwVISfdFTzGu
osaxEANz0v+ZEY1WnT5EdAkjqwtYauj0tDnYmuciju2uVnEYLPOaOvpkmM7e9y+a
NWTvG/C0qAXtTT/EJgAwfCyrhuigyxzwUIyqhW2xgQ8MKQJBAIgM5xPKd30HU98t
jOHzouwSjsv5NIFOM9RnLPGA6HazQGu/0h2S27UnzaU8KKjln1X2q22GMa1cSvIL
q06e+MsCQQCE3oZudjOBgJaKtk7iETiIIoVVk3K1RMSGT/56SrglB1K1kNuFOpg+
obs+8j366gQk47ZgaIZwSRfro0VhTysNAkBiLEVWv6QHgZEhG6Jsrb1j8mQ+hd5A
bGj0HVuODYIxnVmgJvP8yStnhohbcpS4g7G9e1jqmIoiWdXu4ULFYeuPAkAIcKYz
cBi3ejaV2xzJqXRg2WiE1hfsQdEGAyDUHdjyqTNsyyXWobE4EUf2qKadQK5AtaJJ
H3qiuVHmqvlmRAQlAkB4Cl2nYBpK1IdusfwtjfaKppLa/r5k1D35cybxLn1uS3Xz
wwqRUuXrDkb4+TCD3B7Lkuym/kfFE2iIpANAVkeN
-----END RSA PRIVATE KEY-----
I had the same problem and tried a couple of days to figure out how it works and finally, I got it!
Your private key not in PKCS#8 format.
A PKCS8 key starts with
-----BEGIN PRIVATE KEY-----
instead of
-----BEGIN RSA PRIVATE KEY-----.
Java works with PKCS#8 formats.
So you have two ways to fix it:
First:
Regenerate/convert your key on the server side in/to PCKS#8 format and use it in android application
Or Second:
Convert your private key in the application.
For this way you need include third party library
to the project.
Add this line into your app Gradle
compile 'com.madgag.spongycastle:core:1.56.0.0'
And replace your getPrivateKey method with this:
public static PrivateKey getPrivateKey(Context context) throws
GeneralSecurityException, IOException {
InputStream is = context.getResources().openRawResource(R.raw.rsa_2048_priv);
BufferedReader br = new BufferedReader(new InputStreamReader(is));
List<String> lines = new ArrayList<String>();
String line = null;
while ((line = br.readLine()) != null)
lines.add(line);
if (lines.size() > 1 && lines.get(0).startsWith("-----") && lines.get(lines.size() - 1).startsWith("-----")) {
lines.remove(0);
lines.remove(lines.size() - 1);
}
StringBuilder sb = new StringBuilder();
for (String aLine : lines)
sb.append(aLine);
String keyString = sb.toString();
byte[] encodedPrivateKey = Base64.decode(keyString, Base64.DEFAULT);
try {
ASN1Sequence primitive = (ASN1Sequence) ASN1Sequence
.fromByteArray(encodedPrivateKey);
Enumeration<?> e = primitive.getObjects();
BigInteger v = ((ASN1Integer) e.nextElement()).getValue();
int version = v.intValue();
if (version != 0 && version != 1) {
throw new IllegalArgumentException("wrong version for RSA private key");
}
/**
* In fact only modulus and private exponent are in use.
*/
BigInteger modulus = ((ASN1Integer) e.nextElement()).getValue();
BigInteger publicExponent = ((ASN1Integer) e.nextElement()).getValue();
BigInteger privateExponent = ((ASN1Integer) e.nextElement()).getValue();
RSAPrivateKeySpec spec = new RSAPrivateKeySpec(modulus, privateExponent);
KeyFactory kf = KeyFactory.getInstance("RSA");
return kf.generatePrivate(spec);
} catch (IOException e2) {
throw new IllegalStateException();
} catch (NoSuchAlgorithmException e) {
throw new IllegalStateException(e);
} catch (InvalidKeySpecException e) {
throw new IllegalStateException(e);
}
}
So it will convert private key to the PKCS#8,
and you can decode your text without error of wrong tag
Hope it help :)
Here the links where I found solutions:
Getting RSA private key from PEM BASE64 Encoded private key file
JAVA RSA Decryption not working, throws InvalidKeySpecException
Convert PEM traditional private key to PKCS8 private key
How to include the Spongy Castle JAR in Android?
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.