I was using the below code in my app for encrypting and decrypting passwords. It was working fine until suddenly I noticed it fail. The only change I remember doing is to update my ADT and eclipse a few days ago to latest version. Not sure why is it failing now. It works if I create new encryptions. But if I use any encrypted password which was generated earlier and pass it to the code below for decrypting, then the error comes. It is failing in the line "byte[] plainBytes = cipher.doFinal(cipherBytes);"
My decryption code below
String keyword = "keyword";
int iterationCount = 1000;
int keyLength = 256;
String[] fields = encryptedPassword.split("]");
byte[] salt = fromBase64(fields[0]);
byte[] cipherBytes = fromBase64(fields[1]);
KeySpec keySpec = new PBEKeySpec(keyword.toCharArray(), salt, iterationCount, keyLength);
SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("PBEWITHSHA256AND256BITAES-CBC-BC");
SecretKey key = keyFactory.generateSecret(keySpec);
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
PBEParameterSpec pbeSpec = new PBEParameterSpec(salt, iterationCount);
cipher.init(Cipher.DECRYPT_MODE, key, pbeSpec);
byte[] plainBytes = cipher.doFinal(cipherBytes);
plainStr = new String(plainBytes, "UTF-8").trim();
return plainStr;
and the error I am getting is below
java.lang.RuntimeException: javax.crypto.BadPaddingException: error:06065064:digital envelope routines:EVP_DecryptFinal_ex:bad decrypt
Can anyone please help here
Figured out what the problem was. I had updated the jdk version from 1.6 to 1.7. That was the culprit. Since I am using javax.crypto classes, any version change was causing the encryption also to slightly change.
I'm no security expert, but I believe PBEWithSHA256And256BitAES-CBC-BC is PKCS #12, whereas you're deriving keys using PKCS #5. Perhaps if you need to provide the same algorithm into Cipher.getInstance().
Related
someone told me he creates a hash like this:
const enc = await NativeModules.Aes.pbkdf2(plaintext_pasword, serial, 100000, 256);
hashed_password= Buffer.from(enc, 'hex').toString('base64').substr(0, 32);
In Android, I don't know how to translate this to Java. I tried
SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
KeySpec spec = new PBEKeySpec(password.toCharArray(), serialNumber.getBytes(), 100000, 256);
SecretKey tmp = factory.generateSecret(spec);
SecretKeySpec key = new SecretKeySpec(tmp.getEncoded(), "AES");
hashed_password = new String(Base64.encode(key.getEncoded(), Base64.NO_WRAP)).substring(0, 32);
but it might be this is not correct ;)
And also this is far slower than the original solution (the original is said to take less than 1 sec on a Huawei P20, mine takes nearly a minute on my P30).
Could anyone please help me to translate this code?
If the first code is for react native and the library "react-native-aes" then it uses SHA512 as hash and not SHA-1.
See it's implementation:
private static String pbkdf2(String pwd, String salt, Integer cost, Integer length)
throws NoSuchAlgorithmException, InvalidKeySpecException, UnsupportedEncodingException
{
PKCS5S2ParametersGenerator gen = new PKCS5S2ParametersGenerator(new SHA512Digest());
gen.init(pwd.getBytes("UTF_8"), salt.getBytes("UTF_8"), cost);
byte[] key = ((KeyParameter) gen.generateDerivedParameters(length)).getKey();
return bytesToHex(key);
}
https://github.com/tectiv3/react-native-aes/blob/master/android/src/main/java/com/tectiv3/aes/RCTAes.java#L178-L185
Note that PBKDF2withHmacSHA512 requires at least Android API level 26 (Android 8). So my recommendation would be to use Spongycastle Java library in the same way the react native library creates the PBKDF2 hash.
Well, I was about to encrypt a file from SDcard on user's order and Of course decrypt it back after that. well after a bit of searching I found out how to do it in android using "AES". everything was fine until users give me feedback about decryption Failure on Android Nougat! I checked and Unfortunately they were right :(
App was doing fine on Lollipop and Kitkat but not "NOUGAT"!
I checked the Log (as the first thing to do which comes to mind) and I saw this:
New versions of the Android SDK no longer support the Crypto provider.
If your app was relying on setSeed() to derive keys from strings, you
should switch to using SecretKeySpec to load raw key bytes directly OR
use a real key derivation function (KDF). See advice here :
http://android-developers.blogspot.com/2016/06/security-crypto-provider-deprecated-in.html
And I kind of freaked out because I was exactly using Crypto-Provider!!!
public static byte[] generateKey(String password) throws Exception {
byte[] keyStart = password.getBytes("UTF-8");
KeyGenerator kgen = KeyGenerator.getInstance("AES");
SecureRandom random = SecureRandom.getInstance("[SHA1PRNG][1]", "Crypto");
random.setSeed(keyStart);
kgen.init(128, random);
SecretKey skey = kgen.generateKey();
return skey.getEncoded();
}
Then I find some Talks about this on SO: [1], [2]
developer site says we should no longer use crypto-provider for generation of Key for AES because:
This provider only provided an implementation of the algorithm
“SHA1PRNG” for instances of SecureRandom. The problem is that the
SHA1PRNG algorithm is not cryptographically strong.
and instead we should generate key like this:
public byte[] generateKeyForAES(String password) throws NoSuchAlgorithmException, InvalidKeySpecException {
/* 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
/* 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");
return key.getEncoded();
}
As a good boy I replaced new way of generating key but it didn't work!!! (bad google) then I start to take a good look at this code and I realized that because every time we run this method, "salt" is generating "RANDOM"ly and we couldn't generate a same key twice. So you either save salt! after first time it is generated Or use an static salt. well I used an static salt :)
byte[] salt = {(byte) 0xA4, (byte) 0x0B, (byte) 0xC8, (byte) 0x34, (byte) 0xD6, (byte) 0x95, (byte) 0xF3, (byte) 0x13};
As you may guess, Yes it did NOT worked either! what I mean is it did decrypt the file but it was corrupted! NOT the same file was retrieved!
It's been a WEEK since I face this problem and gone through a LOT of way to solve it but none of them work.
There are a lot of Talks on SO which is about "being Unable to decrypt via AES" but there were either suggesting a way that didn't work or were irrelevant!
some says its a problem from Transformation Algorithm in initializing Cipher after generating key and instead of this:
Cipher cipher = Cipher.getInstance("AES");
we should do it like:
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
but after that decryption even Fails to Decrypt from the beginning and it is worse than decrypting some corrupted file (with the same size of course)!
After I became really exhausted I think that forget about all Security risks and just make it work!!!
Then as #artjom-b say in his ANSWER I tried to import CryptoProvider class locally in my project and then even if Android delete it completely, it will work in my project!!!
I just copy CryptoProvider, SHA1PRNG_SecureRandomImpl and of course all related class and put it in a package in my source code, then
,back to earlier way that I use to generate key for AES, I change generate key method as follows:
public static byte[] generateKey(String password) throws Exception {
byte[] keyStart = password.getBytes("UTF-8");
KeyGenerator kgen = KeyGenerator.getInstance("AES");
SecureRandom random = SecureRandom.getInstance("SHA1PRNG", new CryptoProvider());
random.setSeed(keyStart);
kgen.init(128, random);
SecretKey skey = kgen.generateKey();
return skey.getEncoded();
}
As you may noticed the way SucureRandom is being initialized is changed! and insted of "crypto" I reference to my local provider and turn it like:
SecureRandom random = SecureRandom.getInstance("SHA1PRNG", new CryptoProvider());
As the Serial of my failures in getting a Simple encryption on Android N to work follows, again it didn't work. I mean it did decrypting but the file was Corrupted again!
I really really appretiate any Help and I wonder If anyone face a same problem and knows how to fix it!?
I had tried to run the following AES/ CBC/ PKCS5Padding encryption and decryption code, with SHA-1 as key generation, in Nexus 5. It works very well so far.
However, my only concern is, Is AES/ CBC/ PKCS5Padding encryption decryption algorithm and SHA-1 hashing algorithm available in all type of Android devices?
Is there any chance that the following code will fail to run on certain Android devices? If so, is there any fall back plan?
AES/ CBC/ PKCS5Padding
// http://stackoverflow.com/questions/3451670/java-aes-and-using-my-own-key
public static byte[] generateKey(String key) throws GeneralSecurityException, UnsupportedEncodingException {
byte[] binary = key.getBytes("UTF-8");
MessageDigest sha = MessageDigest.getInstance("SHA-1");
binary = sha.digest(binary);
// Use only first 128 bit.
binary = Arrays.copyOf(binary, 16);
return binary;
}
// http://stackoverflow.com/questions/17322002/what-causes-the-error-java-security-invalidkeyexception-parameters-missing
public static String encrypt(byte[] key, String value) throws GeneralSecurityException {
// Argument validation.
if (key.length != 16) {
throw new IllegalArgumentException("Invalid key size.");
}
// Setup AES tool.
SecretKeySpec skeySpec = new SecretKeySpec(key, "AES");
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, skeySpec, new IvParameterSpec(new byte[16]));
// Do the job with AES tool.
byte[] original = value.getBytes(Charset.forName("UTF-8"));
byte[] binary = cipher.doFinal(original);
return Base64.encodeToString(binary, Base64.DEFAULT);
}
// // http://stackoverflow.com/questions/17322002/what-causes-the-error-java-security-invalidkeyexception-parameters-missing
public static String decrypt(byte[] key, String encrypted) throws GeneralSecurityException {
// Argument validation.
if (key.length != 16) {
throw new IllegalArgumentException("Invalid key size.");
}
// Setup AES tool.
SecretKeySpec skeySpec = new SecretKeySpec(key, "AES");
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.DECRYPT_MODE, skeySpec, new IvParameterSpec(new byte[16]));
// Do the job with AES tool.
byte[] binary = Base64.decode(encrypted, Base64.DEFAULT);
byte[] original = cipher.doFinal(binary);
return new String(original, Charset.forName("UTF-8"));
}
Usage
byte[] key = generateKey("my secret key");
String ciphertext = encrypt(key, "my plain content");
String plainContent = decrypt(key, ciphertext);
No, it's unlikely to the extreme that it will fail. The Android API has been derived from the Java API's. The Java API's have contained the "AES/CBC/PKCS5Padding" since version 1.4.
As for "SHA-1", that's an even older algorithm, which has been supported since time began.
Beware not to use "PKCS7Padding" instead. Java uses "PKCS5Padding" as replacement, "PKCS7Padding" support may be sketchy even if it means the same thing.
Note that you should be using password based encryption (PBE) instead of AES/CBC and SHA-1. Especially using SHA-1 as key derivation method is particularly dangerous as you don't use a salt or work factor as a good Password Based Key Derivation Function such as PBKDF2 should. Basically only do this if you know your password contains enough entropy.
Using an all zero IV for the same key is worse though (as already indicated in the comments). It lets attackers find repeats of (the starting blocks of) plaintext input. Authenticated encryption (e.g. using HMAC-SHA-1) is always recommended and more or less required for transport mode encryption (as opposed to in-place encryption where plaintext/padding oracle attacks are not possible).
This isn't answering your question directly, but...
cipher.init(Cipher.ENCRYPT_MODE, skeySpec, new IvParameterSpec(new byte[16]));
Do not use this construct! It will break any security you think you're getting!
This invocation initialises your cipher object with an all-zeros initialisation vector. This is a very very very bad thing, especially with CBC: CBC is quite malleable, and doesn't do any integrity-protection. Make sure you generate your IV using SecureRandom or similar, and preferably use GCM or CCM.
String text = name1.getText().toString();
// Sending side
byte[] data = null;
try {
data = text.getBytes("UTF-8");
} catch (UnsupportedEncodingException e1) {
e1.printStackTrace();
}
String base64 = Base64.encodeToString(data, Base64.DEFAULT);
was able to encrypt password and will to decrypt the same password but i have something in mind that im not sure of this is my first time trying to encrypt a password. Is it safe to encrypt the password this way because I tried encrypt a password : zxc and the result is just a four letter password (its result is : enhj) so im wondering if it is a safe way to encrypt the password. Any ideas on how to remake the code to make it safer and not easy to decode and ideas on how to decrypt the encrypted password?
UPDATE: This is a sample of encryption and decryption I found at this site here but I cant make it run.
encryption
String password = "password";
int iterationCount = 1000;
int keyLength = 256;
int saltLength = keyLength / 8; // same size as key output
SecureRandom random = new SecureRandom();
byte[] salt = new byte[saltLength];
randomb.nextBytes(salt);
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");
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
byte[] iv = new byte[cipher.getBlockSize());
random.nextBytes(iv);
IvParameterSpec ivParams = new IvParameterSpec(iv);
cipher.init(Cipher.ENCRYPT_MODE, key, ivParams);
byte[] ciphertext = cipher.doFinal(plaintext.getBytes("UTF-8"));
decryption
String[] fields = ciphertext.split("]");
byte[] salt = fromBase64(fields[0]);
byte[] iv = fromBase64(fields[1]);
byte[] cipherBytes = fromBase64(fields[2]);
// as above
SecretKey key = deriveKeyPbkdf2(salt, password);
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
IvParameterSpec ivParams = new IvParameterSpec(iv);
cipher.init(Cipher.DECRYPT_MODE, key, ivParams);
byte[] plaintext = cipher.doFinal(cipherBytes);
String plainrStr = new String(plaintext , "UTF-8");
You've tagged this cryptography, passwords, and encryption, so I'll answer it as such.
First, Base64 is not actually encryption, it's merely encoding - essentially changing from 8 bit bytes to 6 bit bytes, and your test is perfect - 3*8 bit characters = 24 bits. 24bits/6bits = 4 Base64 characters. I've also verified that enhj is indeed the Base64 encoding of zxc on my own C implementation of Base64. For further evidence of this, note that you didn't provide any encryption key!
Second, for user authentication (which is what I assume you're doing), do not encrypt passwords - that's a major blunder Adobe just made. For user authentication, you don't ever need to see the user's password again - you merely need to verify that they entered the same thing they did before. Thus, when they enter a password the first time, you salt and hash it. The next time, you retrieve the salt you used the first time, and hash the freshly entered password with the same salt (and # of iterations/work factor) - if the result is the same as you have on record, let them in, since giving the same password will get the same result.
The three canonical answers to How to securely hash passwords? are PBKDF2, Bcrypt, and Scrypt. A quick Google search regarding Android password hashing turned up:
How can I make sure password hashing is secure on computers while not being prohibitively slow on mobile devices? and safe to use jBCrypt and recommend it to my organization? which refer to the mindrot jBCrypt Java library and/or the Spring Security variant of jBCrypt
PBKDF2 with SHA256 on android refers to a SpongyCastle 1.47+ implementation of PBKDF2-HMAC-SHA-256 as well references to PBKDF2-HMAC-SHA-1.
PKCS5S2ParametersGenerator generator = new PKCS5S2ParametersGenerator(new SHA256Digest());
generator.init(PBEParametersGenerator.PKCS5PasswordToUTF8Bytes(password), salt, iterations);
KeyParameter key = (KeyParameter)generator.generateDerivedMacParameters(keySizeInBits);
The Android-developers blogspot article Using Cryptography to Store Credentials Safely also references PBKDF2-HMAC-SHA-1.
public static SecretKey generateKey(char[] passphraseOrPin, byte[] salt) throws NoSuchAlgorithmException, InvalidKeySpecException {
// Number of PBKDF2 hardening rounds to use. Larger values increase
// computation time. You should select a value that causes computation
// to take >100ms.
final int iterations = 8000;
// Generate a 160-bit key
final int outputKeyLength = 160;
SecretKeyFactory secretKeyFactory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
KeySpec keySpec = new PBEKeySpec(passphraseOrPin, salt, iterations, outputKeyLength);
SecretKey secretKey = secretKeyFactory.generateSecret(keySpec);
return secretKey;
}
In ALL cases, choose as high an iteration count/work factor as you can stand the delay of (using as fast a library for your chosen algorithm as you can abide by the license of). Your salt should be a cryptographically random series of bytes in the 8 to 16 byte length range.
For PBKDF2 in particular, never use more outputBytes than the native hash size or you give an attacker a comparative advantage - SHA-1's native size is 20 bytes, SHA-256 is 32 bytes, and SHA-512 is 64 bytes natively.
If you really do need encryption rather than authentication, the "Using Cryptography to Store Credentials Safely" link above covers that too, though the better answer is to store the salt and number of iterations/work factor and simply regenerate the key from the password each time - if the data decrypts, it was good. If not, well, bad password.
You are not encrypting anything. You are converting bytes to base64 encoding. You need to use a ciphering algorithm. See http://examples.javacodegeeks.com/core-java/security/simple-symmetric-key-encrypt-decrypt/
I'm figuring out how to do cross platform (Android & Python) encryption&decryption using AES, and I seem to succeed transferring data if I use a String based IV. But immediately if I switch to using bytes generated with SecureRandom.generateSeed(), it goes awry.
The secret keys are preshared.
Working Android code (try/catch blocks removed to keep it short):
String SecretKey = "0123456789abcdef";
String iv = "fedcba9876543210";
IvParameterSpec ivspec = new IvParameterSpec(iv.getBytes());
SecretKeySpec keyspec = new SecretKeySpec(SecretKey.getBytes(), "AES");
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
//Initialize the cipher
cipher.init(Cipher.ENCRYPT_MODE, keyspec, ivspec);
String message = "What's up?";
byte[] encrypted = cipher.doFinal(message.getBytes());
//Send the data
outputStream.write(encrypted);
There is a small transfer header that let's the client know the size of the incoming message, but I thought it's not relevant and I left that out.
The Python code receiving this message looks like:
#Predefined:
unpad = lambda s : s[0:-ord(s[-1])]
encrypted = cs.recv(messagesize) # Receive the encrypted message
iv = encrypted[:16]
key = AES.new('0123456789abcdef', AES.MODE_CBC,IV=iv)
padded_msg = key.decrypt(encrypted[16:])
decrypted = unpad(padded_msg) #Remove padding
print "read [%s]" % decrypted
Result looks like:
read [What's up]
And if I change two lines in Java code:
SecureRandom rnd = new SecureRandom();
IvParameterSpec ivspec = new IvParameterSpec(rnd.generateSeed(16));
Python output becomes:
read [?=H��m��lڈ�1ls]
I wonder what changes with the SecureRandom? I read that by default the String.getBytes() return platform default encoding (for Android 4.0), so I wonder if I have to do some manipulation on Python end to the IV that was generated with SecureRandom..?
The recipient needs to be told what the IV is. Look at the section on CBC in this Wikipedia entry: you can see that an encrypted n-block message consists of n+1 blocks, the additional block being the IV. There is no standard protocol for transmitting it, but every code i've seen does this by prepending the IV to the message, which really is the natural thing to do and thanks to the error-correcting properties of CBC works even when the code gets it slightly wrong. For example, you can find code in the wild that uses a constant IV but prepends the plain text with a random block, which basically does the same thing in a different way. Sometimes you'll find that kind of code even in books, like the InlineIvCBCExample.java in chapter 2 of David Hook's otherwise very good book.
I recommed the following way of doing AES/CBC/PKCS7Padding:
byte[] plaintext = ...;
byte[] key = ...;
// get iv
SecureRandom rnd = new SecureRandom();
byte[] iv = rnd.getBytes(16);
IvParameterSpec ivSpec = new IvParameterSpec(iv);
// encrypt
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS7Padding");
cipher.init(Cipher.ENCRYPT_MODE, key, ivSpec);
byte[] ciphertext = cipher.doFinal(plaintext);
// copy to result
byte[] result = new byte[iv.length + ciphertext.length];
System.arraycopy(iv, 0, result, 0, iv.length);
System.arraycopy(ciphertext, 0 , result, iv.length, ciphertext.length);