I need to encrypt password, but for Android 4.2 and below version, my solution doesn't work, The encrypted password is random. it's due to PRNG. So I saw this post :
https://android-developers.blogspot.com/2013/08/some-securerandom-thoughts.html
I implemented PRNGFixes, but it does not change...
How to solve this problem of randomly generated number ?
Code :
KeySpec spec = new PBEKeySpec(PASSWORD.toCharArray(), byteSalt, NB_ITER_RFC, SIZE_KEY);
SecretKey temp = factory.generateSecret(spec);
Cipher c = Cipher.getInstance(DES_EDE_PKCS5);
IvParameterSpec ivParam = new IvParameterSpec(bytesIv);
c.init(Cipher.ENCRYPT_MODE, temp, ivParam);
byte[] encrypted = c.doFinal(texteAChiffrer.getBytes("UTF-8"));
mdp = Base64.encodeToString(encrypted, Base64.DEFAULT);
OR :
PBEKeySpec pbeKeySpec = new PBEKeySpec(PASSWORD.toCharArray(), byteSalt, NB_ITER_RFC, SIZE_KEY);
byte[] key2 = PBEParametersGenerator.PKCS12PasswordToBytes(pbeKeySpec.getPassword());
SecretKey temp2 = factory.generateSecret(pbeKeySpec);
Cipher c2 = Cipher.getInstance(DES_EDE_PKCS5);
c2.init(Cipher.ENCRYPT_MODE, temp2, ivParam);
byte[] encrypted2 = c2.doFinal(texteAChiffrer.getBytes("UTF-8"));
mdp = Base64.encodeToString(encrypted2, Base64.DEFAULT);
This two solutions give the same results from Android 4.3 and latest versions (4.4 and 5.0)
Thank you for your help :)
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.
I kind of got stuck with this exception:
java.lang.RuntimeException: error:0407806d:RSA routines:decrypt:DATA_LEN_NOT_EQUAL_TO_MOD_LEN
at com.android.org.conscrypt.NativeCrypto.RSA_private_decrypt(Native Method)
at com.android.org.conscrypt.OpenSSLCipherRSA.engineDoFinal(OpenSSLCipherRSA.java:274)
at javax.crypto.Cipher.doFinal(Cipher.java:1440)
at javax.crypto.CipherInputStream.close(CipherInputStream.java:190)
...
It is thrown when I close the CipherInputStream on Android Marshmallow. Everything seems to work with earlier Android Versions.
What does DATA_LEN_NOT_EQUAL_TO_MOD_LEN mean? Why does it seem to decrypt (call to RSA_private_decrypt) when it should free resource handles (close)?
UPDATE:
I managed to reproduce the error with some test code. It encrypts and decrypts "foobar". One time using the cipher directly and one time through a CipherInputStream (like it's done in the original app).
Everything works on android < 6 and the non-streaming code is even working on android 6.
I was able to get the streaming code to work on android 6 when I changed the explicit cipher RSA/ECB/PKCS1Padding to generic RSA.
But I would bet that it's there for a reason ;)
static final String RSA_ALGO = "RSA/ECB/PKCS1Padding";
// static final String RSA_ALGO = "RSA";
private void _testCrypto2() throws Exception {
KeyPairGenerator keyGen;
KeyPair keys;
byte[] encrypted;
byte[] decrypted;
String input;
String output;
keyGen = KeyPairGenerator.getInstance("RSA");
keyGen.initialize(2048);
keys = keyGen.generateKeyPair();
input = "foobar";
// Plain crypto.
encrypted = this.RSAEncrypt(input, keys.getPublic());
output = this.RSADecrypt(encrypted, keys.getPrivate());
// Streaming crypto.
encrypted = this.RSAEncryptStream(input, keys.getPublic());
output = this.RSADecryptStream(encrypted, keys.getPrivate());
}
public byte[] RSAEncrypt(final String plain, PublicKey _publicKey) throws Exception {
byte[] encryptedBytes;
Cipher cipher;
cipher = Cipher.getInstance(RSA_ALGO);
cipher.init(Cipher.ENCRYPT_MODE, _publicKey);
encryptedBytes = cipher.doFinal(plain.getBytes());
return encryptedBytes;
}
public String RSADecrypt(final byte[] encryptedBytes, PrivateKey _privateKey) throws Exception {
Cipher cipher;
byte[] decryptedBytes;
String decrypted;
cipher = Cipher.getInstance(RSA_ALGO);
cipher.init(Cipher.DECRYPT_MODE, _privateKey);
decryptedBytes = cipher.doFinal(encryptedBytes);
decrypted = new String(decryptedBytes);
return decrypted;
}
public byte[] RSAEncryptStream(final String _plain, PublicKey _publicKey) throws Exception {
Cipher cipher;
InputStream in;
ByteArrayOutputStream out;
int numBytes;
byte buffer[] = new byte[0xffff];
in = new ByteArrayInputStream(_plain.getBytes());
out = new ByteArrayOutputStream();
cipher = Cipher.getInstance(RSA_ALGO);
cipher.init(Cipher.ENCRYPT_MODE, _publicKey);
try {
in = new CipherInputStream(in, cipher);
while ((numBytes = in.read(buffer)) != -1) {
out.write(buffer, 0, numBytes);
}
}
finally {
in.close();
}
return out.toByteArray();
}
public String RSADecryptStream(final byte[] _encryptedBytes, PrivateKey _privateKey) throws Exception {
Cipher cipher;
InputStream in;
ByteArrayOutputStream out;
int numBytes;
byte buffer[] = new byte[0xffff];
in = new ByteArrayInputStream(_encryptedBytes);
out = new ByteArrayOutputStream();
cipher = Cipher.getInstance(RSA_ALGO);
cipher.init(Cipher.DECRYPT_MODE, _privateKey);
try {
in = new CipherInputStream(in, cipher);
while ((numBytes = in.read(buffer)) != -1) {
out.write(buffer, 0, numBytes);
}
}
finally {
in.close();
}
return new String(out.toByteArray());
}
However, it looks like there are two directions for a fix:
Getting rid of the streaming for RSA
Removing explicit RSA cipher instantiation
What do you think?
It looks like there were some changes for the default security providers of android.
Cipher c;
Provider p;
StringBuilder bldr;
c = Cipher.getInstance("RSA");
p = cipher.getProvider();
bldr = new StringBuilder();
bldr.append(_p.getName())
.append(" ").append(_p.getVersion())
.append(" (").append(_p.getInfo()).append(")");
Log.i("test", bldr.toString());
It seems to use a version of BouncyCastle on all tested Android versions (I tested down to 2.3):
Android 5:
BC 1.5 (BouncyCastle Security Provider v1.50)
Android 6:
BC 1.52 (BouncyCastle Security Provider v1.52)
However, something changed with the "explicit" cipher:
c = Cipher.getInstance("RSA/ECB/PKCS1Padding");
Android 4.1.2:
BC 1.46 (BouncyCastle Security Provider v1.46)
Android 4.4.2:
AndroidOpenSSL 1.0 (Android's OpenSSL-backed security provider)
Android 5.1.1:
AndroidOpenSSL 1.0 (Android's OpenSSL-backed security provider)
Android 6.0.1:
AndroidKeyStoreBCWorkaround 1.0 (Android KeyStore security provider to work around Bouncy Castle)
So the final solution is setting the provider explicitly to BouncyCastle which is working on all tested android versions, even with streaming:
Provider p;
Cipher c;
p = Security.getProvider("BC");
c = Cipher.getInstance("RSA/ECB/PKCS1Padding", p);
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);
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().
I am trying to implement AES128 algorithm on Android, and I have referenced this link for a basic AES implementation (http://java.sun.com/developer/technicalArticles/Security/AES/AES_v1.html).
The problem is,for my project the key is predefined, and it is 36 bytes, not 16/24/32 bytes. So I always got a "key length not 128/194/256 bits" exception. I try the solution from iphone sdk(see this link: The iOS encryption framework) and it works even when I pass a 36 byte predefined key. As I can not find the implementation details for the BlockCipher.c/CommonCryptor.c released by Apple, Can any body help me figure out how they select 16 bytes from 36 bytes?
Thanks.
-----------------------------------update Sep 13th------------------------------------
In order to avoid confusion I provide some sample and my progress. I change some data that is confidential, but the length and format remain the same. And for saving time I only reveal the core functions. No comments for the code as I think the code is self-explained enough.
the iOS sample:
NSString * _key = #"some 36 byte key";
StringEncryption *crypto = [[[StringEncryption alloc] init] autorelease];
NSData *_inputData = [inputString dataUsingEncoding:NSUTF8StringEncoding];
CCOptions padding = kCCOptionPKCS7Padding;
NSData *encryptedData = [crypto encrypt:_inputData key:[_key dataUsingEncoding:NSUTF8StringEncoding] padding:&padding];
NSString *encryptedString = [encryptedData base64EncodingWithLineLength:0];
return encryptedString;
the [crypto encrypt] implementation is exactly the same as the link I mentioned above. It calls the doCipher in encryption mode. The core functions includes CCCryptorCreate, CCCryptorUpdate and CCCryptorFinal, which are from . The CCCryptorCreate deals with the key length. It passes the raw key bytes, and pass an integer 16 (kCCKeySizeAES128) as the key size and do the trick. The call hierarchy is like CCCryptorCreate/CommonCryptor.c => ccBlockCipherCallouts->CCBlockCipherInit/BlockCipher.c => ccAlgInfo->setkey/BlockCipher.c . setkey is actually a pointer to a function, for AES it points to aes_cc_set_key. And I can not find the aes_cc_set_key implementation, got lost here.
----------------------------------------Update Sep 13th -----------------------------
I change the _key in iOS sample code, manually taking the first 16 byte as the new key, other parts remain the same, and it is working!!! Up to this point I solve the key length problem.
But the Android version outputs different from the iOS version for some long plain text, like 30 or 40 bytes. my java implementation is like below:
String key = "some 16 byte key";
byte[] keyBytes = key.getBytes("UTF-8");
byte[] plainBytes = plainText.getBytes("UTF-8");
SecretKeySpec skeySpec = new SecretKeySpec(keyBytes, "AES");
Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, skeySpec);
byte[] encrypted = cipher.doFinal(plainBytes);
String result = Base64.encodeBytes(encrypted);
return result;
Base64 is from org.apache.commons.codec.binary.Base64. What is the problem? or any hints on c/c++ libraries that can do the same thing? I can import it into android as well.
The remaining difference (provided that you only used the first 16 bytes of the key) is the cipher streaming mode. The iOS code uses CBC mode with an initialization set to all zeros. The Android code however uses ECB.
So the correct Java/Android code is:
// convert key to bytes
byte[] keyBytes = key.getBytes("UTF-8");
// Use the first 16 bytes (or even less if key is shorter)
byte[] keyBytes16 = new byte[16];
System.arraycopy(keyBytes, 0, keyBytes16, 0, Math.min(keyBytes.length, 16));
// convert plain text to bytes
byte[] plainBytes = plainText.getBytes("UTF-8");
// setup cipher
SecretKeySpec skeySpec = new SecretKeySpec(keyBytes16, "AES");
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
byte[] iv = new byte[16]; // initialization vector with all 0
cipher.init(Cipher.ENCRYPT_MODE, skeySpec, new IvParameterSpec(iv));
// encrypt
byte[] encrypted = cipher.doFinal(plainBytes);
I have tested it with about 100 bytes of data and got exactly the same result on iOS and in Java.
There is no such thing as a 36-byte (288 bits) AES key. AES 256 would use a 32 byte key, so maybe that is what you have, with some additional header/trailer bytes. Where did you get this key from? What is the format? The Apple implementation may be throwing away the unneeded bytes, or it already knows about that special format you are using.
Is the 36 bytes actually a passphrase? If so, then it is likely that the key being used is SHA-256(passphrase) or SHA-512(passphrase).
ETA:
Re your update. I note that your code is using ECB mode. That is insecure. It may well be that Apple is using CBC mode, hence you difficulty in decrypting longer (more than 16 bytes) messages. Try changing the mode to CBC and using 16 more bytes of your mysterious input as the IV. Looking quickly at the Apple code for CommonCryptor.c, they appear to be using PKCS7 padding, so you should use that as well.
In case you want to apply base64 encoding for transporting over the network this is the right code:
public String encryptString(String string, String key)
{
byte[] aesData;
String base64="";
try
{
aesData = encrypt(key, string.getBytes("UTF8"));
base64 = Base64.encodeToString(aesData, Base64.DEFAULT);
}
catch (Exception e)
{
e.printStackTrace();
}
return base64;
}
public String decryptString(String string, String key)
{
byte[] debase64 = null;
String result="";
try
{
debase64=Base64.decode(string, Base64.DEFAULT);
byte[] aesDecrypted = decrypt(key, debase64);;
result = new String(aesDecrypted, "UTF8");
}
catch (Exception e)
{
e.printStackTrace();
}
return result;
}
private byte[] decrypt(String k, byte[] plainBytes) throws Exception
{
// convert key to bytes
byte[] keyBytes = k.getBytes("UTF-8");
// Use the first 16 bytes (or even less if key is shorter)
byte[] keyBytes16 = new byte[16];
System.arraycopy(keyBytes, 0, keyBytes16, 0, Math.min(keyBytes.length, 16));
// setup cipher
SecretKeySpec skeySpec = new SecretKeySpec(keyBytes16, "AES");
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
byte[] iv = new byte[16]; // initialization vector with all 0
cipher.init(Cipher.DECRYPT_MODE, skeySpec, new IvParameterSpec(iv));
// encrypt
byte[] encrypted = cipher.doFinal(plainBytes);
return encrypted;
}
private byte[] encrypt(String k, byte[] plainBytes) throws Exception
{
// convert key to bytes
byte[] keyBytes = k.getBytes("UTF-8");
// Use the first 16 bytes (or even less if key is shorter)
byte[] keyBytes16 = new byte[16];
System.arraycopy(keyBytes, 0, keyBytes16, 0, Math.min(keyBytes.length, 16));
// setup cipher
SecretKeySpec skeySpec = new SecretKeySpec(keyBytes16, "AES");
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
byte[] iv = new byte[16]; // initialization vector with all 0
cipher.init(Cipher.ENCRYPT_MODE, skeySpec, new IvParameterSpec(iv));
// encrypt
byte[] encrypted = cipher.doFinal(plainBytes);
return encrypted;
}