RuntimeException when closing CipherInputStream - android

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);

Related

AES 256 CBC encryption in Laravel and Decryption in android

So my problem is this: i have a password that i'm encrypting in Laravel 5.6 with AES-256-CBC and send it to an android device, problem is i can't find a way to decrypt it knowing that i found a way to extract the IV and the encrypted value and the key is available on the android device !
I'm successfully decrypting the value if i use AES-128-CBC using this code on the android device, but failing the AES-256-CBC cypher and i don't understand where the problem is !
The code :
public static String decrypt(byte[] keyValue, String ivValue, String encryptedData) throws Exception {
Key key = new SecretKeySpec(keyValue, "AES");
byte[] iv = Base64.decode(ivValue.getBytes("UTF-8"), Base64.DEFAULT);
byte[] decodedValue = Base64.decode(encryptedData.getBytes("UTF-8"), Base64.DEFAULT);
Cipher c = Cipher.getInstance("AES/CBC/PKCS7Padding");
c.init(Cipher.DECRYPT_MODE, key, new IvParameterSpec(iv));
byte[] decValue = c.doFinal(decodedValue);
return new String(decValue);
}
At what instance it's specified that this code should use AES-128 and not 256 ? and how can i change it !
Thanks in advance !
EDIT
the PHP code is as follows :
$cipher="AES-256-CBC";
$key='somerandomkeyof32byteslong';
$crypt=new Encrypter($key,$cipher);
$result=$crypt->encryptString('oussama');
//i'm sending the result to the android device
Try this one
Security.java
import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
import org.apache.commons.codec.binary.Base64;
public class Security {
public static String encrypt(String input, String key){
byte[] crypted = null;
try{
SecretKeySpec skey = new SecretKeySpec(key.getBytes(), "AES");
Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, skey);
crypted = cipher.doFinal(input.getBytes());
}catch(Exception e){
System.out.println(e.toString());
}
return new String(Base64.encodeBase64(crypted));
}
public static String decrypt(String input, String key){
byte[] output = null;
try{
SecretKeySpec skey = new SecretKeySpec(key.getBytes(), "AES");
Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
cipher.init(Cipher.DECRYPT_MODE, skey);
output = cipher.doFinal(Base64.decodeBase64(input));
}catch(Exception e){
System.out.println(e.toString());
}
return new String(output);
}
public static void main(String[] args) {
String key = "1234567891234567";
String data = "example";
System.out.println(Security.decrypt(Security.encrypt(data, key), key));
System.out.println(Security.encrypt(data, key));
}
}
Security.php
class Security {
public static function encrypt($input, $key) {
$size = mcrypt_get_block_size(MCRYPT_RIJNDAEL_128, MCRYPT_MODE_ECB);
$input = Security::pkcs5_pad($input, $size);
$td = mcrypt_module_open(MCRYPT_RIJNDAEL_128, '', MCRYPT_MODE_ECB, '');
$iv = mcrypt_create_iv (mcrypt_enc_get_iv_size($td), MCRYPT_RAND);
mcrypt_generic_init($td, $key, $iv);
$data = mcrypt_generic($td, $input);
mcrypt_generic_deinit($td);
mcrypt_module_close($td);
$data = base64_encode($data);
return $data;
}
private static function pkcs5_pad ($text, $blocksize) {
$pad = $blocksize - (strlen($text) % $blocksize);
return $text . str_repeat(chr($pad), $pad);
}
public static function decrypt($sStr, $sKey) {
$decrypted= mcrypt_decrypt(
MCRYPT_RIJNDAEL_128,
$sKey,
base64_decode($sStr),
MCRYPT_MODE_ECB
);
$dec_s = strlen($decrypted);
$padding = ord($decrypted[$dec_s-1]);
$decrypted = substr($decrypted, 0, -$padding);
return $decrypted;
}
}?>
Example.php
<?php
include 'security.php';
$value = 'plain text';
$key = "your key"; //16 Character Key
echo "Encrypt =>"."<br><br>";
echo Security::encrypt($value, $key);
echo "<br><br>"."Decrypt =>"."<br><br>";
echo Security::decrypt("AES Encrypted response",$key);
//echo Security::decrypt(Security::encrypt($value, $key), $key);
?>
If you need AES with 256 bit key length, you can do it like this:
Cipher c = Cipher.getInstance("AES_256/CBC/PKCS7Padding");
Android reference sometimes better than oracle when you want to use java classes for android. Here is reference.
But remember that is only api 26+. You can compile openssl and use it in an JNI if you need support for previous versions(and I think you need to do). or find another cryptographic library for java.

AES Encryption in iOS and Java

I am a newbie to this encryption. I am creating an application for both android and iOS in which i have to encrypt(using AES Encryprtion) a file at server side and decrypt at client side in both iOS and Android App. I got code in internet to perform AES encryption and decryption for both Android and iOS, they are working fine. Server side they are using java. But the problem is Java Encrypted File cant be decrypted by iOS program, even I got the same filesize, But the file is not in correct format. I posted the code below...
Java Encryption and Decryption:
public static byte[] encrypt(byte[] data, byte[] key, byte[] ivs) {
try {
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
SecretKeySpec secretKeySpec = new SecretKeySpec(key, "AES");
byte[] finalIvs = new byte[16];
int len = ivs.length > 16 ? 16 : ivs.length;
System.arraycopy(ivs, 0, finalIvs, 0, len);
IvParameterSpec ivps = new IvParameterSpec(finalIvs);
cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec, ivps);
return cipher.doFinal(data);
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
public static byte[] decrypt(byte[] data, byte[] key, byte[] ivs) {
try {
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
SecretKeySpec secretKeySpec = new SecretKeySpec(key, "AES");
byte[] finalIvs = new byte[16];
int len = ivs.length > 16 ? 16 : ivs.length;
System.arraycopy(ivs, 0, finalIvs, 0, len);
IvParameterSpec ivps = new IvParameterSpec(finalIvs);
cipher.init(Cipher.DECRYPT_MODE, secretKeySpec, ivps);
return cipher.doFinal(data);
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
iOS Code for Decryption:
- (NSData *) decryptFile:(NSString *)key withData:(NSData *)fileData {
char keyPtr[kCCKeySizeAES128+1];
bzero(keyPtr, sizeof(keyPtr));
NSString* iv = #"12345678";
[key getCString:keyPtr maxLength:sizeof(keyPtr) encoding:NSUTF8StringEncoding];
NSUInteger dataLength = [fileData length];
size_t bufferSize = dataLength + kCCBlockSizeAES128;
void *buffer = malloc(bufferSize);
size_t numBytesDecrypted = 0;
CCCryptorStatus cryptStatus = CCCrypt(kCCDecrypt, kCCAlgorithmAES128, kCCOptionPKCS7Padding,keyPtr, kCCKeySizeAES128,
iv /* initialization vector (optional) */,
[fileData bytes], dataLength, /* input */
buffer, bufferSize, /* output */
&numBytesDecrypted);
if (cryptStatus == kCCSuccess) {
//the returned NSData takes ownership of the buffer and will free it on deallocation
return [NSData dataWithBytesNoCopy:buffer length:numBytesDecrypted];
}
free(buffer); //free the buffer;
return nil;
}
Give me any solution or suggestion for this problem
The issue is with the iv parameter.
1) You are passing a NSString* as iv. You probably want to pass in the actual bytes.
2) The length of iv should be 16 (in this case) as per the api docs of CCCrypt. See link below:
http://www.opensource.apple.com/source/CommonCrypto/CommonCrypto-36064/CommonCrypto/CommonCryptor.h

What are best practices for using AES encryption in Android?

Why I ask this question:
I know there have been a lot of questions about AES encryption, even for Android. And there are lots of code snippets if you search the Web. But on every single page, in every Stack Overflow question, I find another implementation with major differences.
So I created this question to find a "best practice". I hope we can collect a list of the most important requirements and set up an implementation that is really secure!
I read about initialization vectors and salts. Not all implementations I found had these features. So do you need it? Does it increase the security a lot? How do you implement it? Should the algorithm raise exceptions if the encrypted data cannot be decrypted? Or is that insecure and it should just return an unreadable string? Can the algorithm use Bcrypt instead of SHA?
What about these two implementations I found? Are they okay? Perfect or some important things missing? What of these is secure?
The algorithm should take a string and a "password" for encryption and then encrypt the string with that password. The output should be a string (hex or base64?) again. Decryption should be possible as well, of course.
What is the perfect AES implementation for Android?
Implementation #1:
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.SecureRandom;
import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.PBEKeySpec;
import javax.crypto.spec.SecretKeySpec;
public class AdvancedCrypto implements ICrypto {
public static final String PROVIDER = "BC";
public static final int SALT_LENGTH = 20;
public static final int IV_LENGTH = 16;
public static final int PBE_ITERATION_COUNT = 100;
private static final String RANDOM_ALGORITHM = "SHA1PRNG";
private static final String HASH_ALGORITHM = "SHA-512";
private static final String PBE_ALGORITHM = "PBEWithSHA256And256BitAES-CBC-BC";
private static final String CIPHER_ALGORITHM = "AES/CBC/PKCS5Padding";
private static final String SECRET_KEY_ALGORITHM = "AES";
public String encrypt(SecretKey secret, String cleartext) throws CryptoException {
try {
byte[] iv = generateIv();
String ivHex = HexEncoder.toHex(iv);
IvParameterSpec ivspec = new IvParameterSpec(iv);
Cipher encryptionCipher = Cipher.getInstance(CIPHER_ALGORITHM, PROVIDER);
encryptionCipher.init(Cipher.ENCRYPT_MODE, secret, ivspec);
byte[] encryptedText = encryptionCipher.doFinal(cleartext.getBytes("UTF-8"));
String encryptedHex = HexEncoder.toHex(encryptedText);
return ivHex + encryptedHex;
} catch (Exception e) {
throw new CryptoException("Unable to encrypt", e);
}
}
public String decrypt(SecretKey secret, String encrypted) throws CryptoException {
try {
Cipher decryptionCipher = Cipher.getInstance(CIPHER_ALGORITHM, PROVIDER);
String ivHex = encrypted.substring(0, IV_LENGTH * 2);
String encryptedHex = encrypted.substring(IV_LENGTH * 2);
IvParameterSpec ivspec = new IvParameterSpec(HexEncoder.toByte(ivHex));
decryptionCipher.init(Cipher.DECRYPT_MODE, secret, ivspec);
byte[] decryptedText = decryptionCipher.doFinal(HexEncoder.toByte(encryptedHex));
String decrypted = new String(decryptedText, "UTF-8");
return decrypted;
} catch (Exception e) {
throw new CryptoException("Unable to decrypt", e);
}
}
public SecretKey getSecretKey(String password, String salt) throws CryptoException {
try {
PBEKeySpec pbeKeySpec = new PBEKeySpec(password.toCharArray(), HexEncoder.toByte(salt), PBE_ITERATION_COUNT, 256);
SecretKeyFactory factory = SecretKeyFactory.getInstance(PBE_ALGORITHM, PROVIDER);
SecretKey tmp = factory.generateSecret(pbeKeySpec);
SecretKey secret = new SecretKeySpec(tmp.getEncoded(), SECRET_KEY_ALGORITHM);
return secret;
} catch (Exception e) {
throw new CryptoException("Unable to get secret key", e);
}
}
public String getHash(String password, String salt) throws CryptoException {
try {
String input = password + salt;
MessageDigest md = MessageDigest.getInstance(HASH_ALGORITHM, PROVIDER);
byte[] out = md.digest(input.getBytes("UTF-8"));
return HexEncoder.toHex(out);
} catch (Exception e) {
throw new CryptoException("Unable to get hash", e);
}
}
public String generateSalt() throws CryptoException {
try {
SecureRandom random = SecureRandom.getInstance(RANDOM_ALGORITHM);
byte[] salt = new byte[SALT_LENGTH];
random.nextBytes(salt);
String saltHex = HexEncoder.toHex(salt);
return saltHex;
} catch (Exception e) {
throw new CryptoException("Unable to generate salt", e);
}
}
private byte[] generateIv() throws NoSuchAlgorithmException, NoSuchProviderException {
SecureRandom random = SecureRandom.getInstance(RANDOM_ALGORITHM);
byte[] iv = new byte[IV_LENGTH];
random.nextBytes(iv);
return iv;
}
}
Source: http://pocket-for-android.1047292.n5.nabble.com/Encryption-method-and-reading-the-Dropbox-backup-td4344194.html
Implementation #2:
import java.security.SecureRandom;
import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
/**
* Usage:
* <pre>
* String crypto = SimpleCrypto.encrypt(masterpassword, cleartext)
* ...
* String cleartext = SimpleCrypto.decrypt(masterpassword, crypto)
* </pre>
* #author ferenc.hechler
*/
public class SimpleCrypto {
public static String encrypt(String seed, String cleartext) throws Exception {
byte[] rawKey = getRawKey(seed.getBytes());
byte[] result = encrypt(rawKey, cleartext.getBytes());
return toHex(result);
}
public static String decrypt(String seed, String encrypted) throws Exception {
byte[] rawKey = getRawKey(seed.getBytes());
byte[] enc = toByte(encrypted);
byte[] result = decrypt(rawKey, enc);
return new String(result);
}
private static byte[] getRawKey(byte[] seed) throws Exception {
KeyGenerator kgen = KeyGenerator.getInstance("AES");
SecureRandom sr = SecureRandom.getInstance("SHA1PRNG");
sr.setSeed(seed);
kgen.init(128, sr); // 192 and 256 bits may not be available
SecretKey skey = kgen.generateKey();
byte[] raw = skey.getEncoded();
return raw;
}
private static byte[] encrypt(byte[] raw, byte[] clear) throws Exception {
SecretKeySpec skeySpec = new SecretKeySpec(raw, "AES");
Cipher cipher = Cipher.getInstance("AES");
cipher.init(Cipher.ENCRYPT_MODE, skeySpec);
byte[] encrypted = cipher.doFinal(clear);
return encrypted;
}
private static byte[] decrypt(byte[] raw, byte[] encrypted) throws Exception {
SecretKeySpec skeySpec = new SecretKeySpec(raw, "AES");
Cipher cipher = Cipher.getInstance("AES");
cipher.init(Cipher.DECRYPT_MODE, skeySpec);
byte[] decrypted = cipher.doFinal(encrypted);
return decrypted;
}
public static String toHex(String txt) {
return toHex(txt.getBytes());
}
public static String fromHex(String hex) {
return new String(toByte(hex));
}
public static byte[] toByte(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 static String toHex(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 final static String HEX = "0123456789ABCDEF";
private static void appendHex(StringBuffer sb, byte b) {
sb.append(HEX.charAt((b>>4)&0x0f)).append(HEX.charAt(b&0x0f));
}
}
Source: http://www.tutorials-android.com/learn/How_to_encrypt_and_decrypt_strings.rhtml
Neither implementation you give in your question is entirely correct, and neither implementation you give should be used as is. In what follows, I will discuss aspects of password-based encryption in Android.
Keys and Hashes
I will start discussing the password-based system with salts. The salt is a randomly generated number. It is not "deduced". Implementation 1 includes a generateSalt() method that generates a cryptographically strong random number. Because the salt is important to security, it should be kept secret once it is generated, though it only needs to be generated once. If this is a Web site, it's relatively easy to keep the salt secret, but for installed applications (for desktop and mobile devices), this will be much more difficult.
The method getHash() returns a hash of the given password and salt, concatenated into a single string. The algorithm used is SHA-512, which returns a 512-bit hash. This method returns a hash that's useful for checking a string's integrity, so it might as well be used by calling getHash() with just a password or just a salt, since it simply concatenates both parameters. Since this method won't be used in the password-based encryption system, I won't be discussing it further.
The method getSecretKey(), derives a key from a char array of the password and a hex-encoded salt, as returned from generateSalt(). The algorithm used is PBKDF1 (I think) from PKCS5 with SHA-256 as the hash function, and returns a 256-bit key. getSecretKey() generates a key by repeatedly generating hashes of the password, salt, and a counter (up to the iteration count given in PBE_ITERATION_COUNT, here 100) in order to increase the time needed to mount a brute-force attack. The salt's length should be at least as long as the key being generated, in this case, at least 256 bits. The iteration count should be set as long as possible without causing unreasonable delay. For more information on salts and iteration counts in key derivation, see section 4 in RFC2898.
The implementation in Java's PBE, however, is flawed if the password contains Unicode characters, that is, those that require more than 8 bits to be represented. As stated in PBEKeySpec, "the PBE mechanism defined in PKCS #5 looks at only the low order 8 bits of each character". To work around this problem, you can try generating a hex string (which will contain only 8-bit characters) of all 16-bit characters in the password before passing it to PBEKeySpec. For example, "ABC" becomes "004100420043". Note also that PBEKeySpec "requests the password as a char array, so it can be overwritten [with clearPassword()] when done". (With respect to "protecting strings in memory", see this question.) I don't see any problems, though, with representing a salt as a hex-encoded string.
Encryption
Once a key is generated, we can use it to encrypt and decrypt text.
In implementation 1, the cipher algorithm used is AES/CBC/PKCS5Padding, that is, AES in the Cipher Block Chaining (CBC) cipher mode, with padding defined in PKCS#5. (Other AES cipher modes include counter mode (CTR), electronic codebook mode (ECB), and Galois counter mode (GCM). Another question on Stack Overflow contains answers that discuss in detail the various AES cipher modes and the recommended ones to use. Be aware, too, that there are several attacks on CBC mode encryption, some of which are mentioned in RFC 7457.)
Note that you should use an encryption mode that also checks the encrypted data for integrity (e.g., authenticated encryption with associated data, AEAD, described in RFC 5116). However, AES/CBC/PKCS5Padding doesn't provide integrity checking, so it alone is not recommended. For AEAD purposes, using a secret that's at least twice as long as a normal encryption key is recommended, to avoid related key attacks: the first half serves as the encryption key, and the second half serves as the key for the integrity check. (That is, in this case, generate a single secret from a password and salt, and split that secret in two.)
Java Implementation
The various functions in implementation 1 use a specific provider, namely "BC", for its algorithms. In general, though, it is not recommended to request specific providers, since not all providers are available on all Java implementations, whether for lack of support, to avoid code duplication, or for other reasons. This advice has especially become important since the release of Android P preview in early 2018, because some functionality from the "BC" provider has been deprecated there — see the article "Cryptography Changes in Android P" in the Android Developers Blog. See also the Introduction to Oracle Providers.
Thus, PROVIDER should not exist and the string -BC should be removed from PBE_ALGORITHM. Implementation 2 is correct in this respect.
It is inappropriate for a method to catch all exceptions, but rather to handle only the exceptions it can. The implementations given in your question can throw a variety of checked exceptions. A method can choose to wrap only those checked exceptions with CryptoException, or specify those checked exceptions in the throws clause. For convenience, wrapping the original exception with CryptoException may be appropriate here, since there are potentially many checked exceptions the classes can throw.
SecureRandom in Android
As detailed in the article "Some SecureRandom Thoughts", in the Android Developers Blog, the implementation of java.security.SecureRandom in Android releases before 2013 has a flaw that reduces the strength of random numbers it delivers. This flaw can be mitigated as described in that article.
#2 should never be used as it uses only "AES" (which means ECB mode encryption on text, a big no-no) for the cipher. I'll just talk about #1.
The first implementation seems to adhere to best practices for encryption. The constants are generally OK, although both the salt size and the number of iterations for performing PBE are on the short side. Futhermore, it seems to be for AES-256 since the PBE key generation uses 256 as a hard coded value (a shame after all those constants). It uses CBC and PKCS5Padding which is at least what you would expect.
Completely missing is any authentication/integrity protection, so an attacker can change the cipher text. This means that padding oracle attacks are possible in a client/server model. It also means that an attacker can try and change the encrypted data. This will likely result in some error somewhere becaues the padding or content is not accepted by the application, but that's not a situation that you want to be in.
Exception handling and input validation could be enhanced, catching Exception is always wrong in my book. Furhtermore, the class implements ICrypt, which I don't know. I do know that having only methods without side effects in a class is a bit weird. Normally, you would make those static. There is no buffering of Cipher instances etc., so every required object gets created ad-nauseum. However, you can safely remove ICrypto from the definition it seems, in that case you could also refactor the code to static methods (or rewrite it to be more object oriented, your choice).
The problem is that any wrapper always makes assumptions about the use case. To say that a wrapper is right or wrong is therefore bunk. This is why I always try to avoid generating wrapper classes. But at least it does not seem explicitly wrong.
You have asked a pretty interesting question. As with all algorithms the cipher key is the "secret sauce", since once that's known to the public, everything else is too. So you look into ways to this document by Google
security
Besides Google In-App Billing also gives thoughts on security which is insightful as well
billing_best_practices
Use BouncyCastle Lightweight API. It provides 256 AES With PBE and Salt.
Here sample code, which can encrypt/decrypt files.
public void encrypt(InputStream fin, OutputStream fout, String password) {
try {
PKCS12ParametersGenerator pGen = new PKCS12ParametersGenerator(new SHA256Digest());
char[] passwordChars = password.toCharArray();
final byte[] pkcs12PasswordBytes = PBEParametersGenerator.PKCS12PasswordToBytes(passwordChars);
pGen.init(pkcs12PasswordBytes, salt.getBytes(), iterationCount);
CBCBlockCipher aesCBC = new CBCBlockCipher(new AESEngine());
ParametersWithIV aesCBCParams = (ParametersWithIV) pGen.generateDerivedParameters(256, 128);
aesCBC.init(true, aesCBCParams);
PaddedBufferedBlockCipher aesCipher = new PaddedBufferedBlockCipher(aesCBC, new PKCS7Padding());
aesCipher.init(true, aesCBCParams);
// Read in the decrypted bytes and write the cleartext to out
int numRead = 0;
while ((numRead = fin.read(buf)) >= 0) {
if (numRead == 1024) {
byte[] plainTemp = new byte[aesCipher.getUpdateOutputSize(numRead)];
int offset = aesCipher.processBytes(buf, 0, numRead, plainTemp, 0);
final byte[] plain = new byte[offset];
System.arraycopy(plainTemp, 0, plain, 0, plain.length);
fout.write(plain, 0, plain.length);
} else {
byte[] plainTemp = new byte[aesCipher.getOutputSize(numRead)];
int offset = aesCipher.processBytes(buf, 0, numRead, plainTemp, 0);
int last = aesCipher.doFinal(plainTemp, offset);
final byte[] plain = new byte[offset + last];
System.arraycopy(plainTemp, 0, plain, 0, plain.length);
fout.write(plain, 0, plain.length);
}
}
fout.close();
fin.close();
} catch (Exception e) {
e.printStackTrace();
}
}
public void decrypt(InputStream fin, OutputStream fout, String password) {
try {
PKCS12ParametersGenerator pGen = new PKCS12ParametersGenerator(new SHA256Digest());
char[] passwordChars = password.toCharArray();
final byte[] pkcs12PasswordBytes = PBEParametersGenerator.PKCS12PasswordToBytes(passwordChars);
pGen.init(pkcs12PasswordBytes, salt.getBytes(), iterationCount);
CBCBlockCipher aesCBC = new CBCBlockCipher(new AESEngine());
ParametersWithIV aesCBCParams = (ParametersWithIV) pGen.generateDerivedParameters(256, 128);
aesCBC.init(false, aesCBCParams);
PaddedBufferedBlockCipher aesCipher = new PaddedBufferedBlockCipher(aesCBC, new PKCS7Padding());
aesCipher.init(false, aesCBCParams);
// Read in the decrypted bytes and write the cleartext to out
int numRead = 0;
while ((numRead = fin.read(buf)) >= 0) {
if (numRead == 1024) {
byte[] plainTemp = new byte[aesCipher.getUpdateOutputSize(numRead)];
int offset = aesCipher.processBytes(buf, 0, numRead, plainTemp, 0);
// int last = aesCipher.doFinal(plainTemp, offset);
final byte[] plain = new byte[offset];
System.arraycopy(plainTemp, 0, plain, 0, plain.length);
fout.write(plain, 0, plain.length);
} else {
byte[] plainTemp = new byte[aesCipher.getOutputSize(numRead)];
int offset = aesCipher.processBytes(buf, 0, numRead, plainTemp, 0);
int last = aesCipher.doFinal(plainTemp, offset);
final byte[] plain = new byte[offset + last];
System.arraycopy(plainTemp, 0, plain, 0, plain.length);
fout.write(plain, 0, plain.length);
}
}
fout.close();
fin.close();
} catch (Exception e) {
e.printStackTrace();
}
}
I found a nice implementation here :
http://nelenkov.blogspot.fr/2012/04/using-password-based-encryption-on.html
and
https://github.com/nelenkov/android-pbe
That was also helpful in my quest for a good enough AES Implementation for Android

Java Android - Decrypted byte array 255 bytes long instead of "Hello World!".length

I'm reading a lot since some weeks to implement an encrypt/decrypt algoritm for my Android application. I'm implementing a license key that is downloaded from my website and stored in the external storage of my Android device. the application read the content of the file and decrypt it using the server public key (yes i know that i should with client private key but it's ok for my purpose). The problem is that the final string has a lot of black square with question mark inside. i've read a lot of other posts here on stackoverflow, but i think that the "only" problem is that, even if there should be 10 chars in the string, the string is long 255 bytes (with 2048 bit RSA key) and the remaining chars are filled with black "". Why the newPlainText var is not long as "Hello World!" ? Here below my code... Many thanks in advance!
public boolean licenseValid() throws IOException, InvalidKeyException, NoSuchAlgorithmException, NoSuchPaddingException, IllegalBlockSizeException, BadPaddingException{
java.io.File file = new java.io.File(Environment.getExternalStorageDirectory().toString() ,
"/folder/file.lic");
byte[] fileBArray = new byte[(int)file.length()];
FileInputStream fis = new FileInputStream(file);
// Read in the bytes
int offset = 0;
int numRead = 0;
while (offset < fileBArray.length
&& (numRead=fis.read(fileBArray, offset, fileBArray.length-offset)) >= 0) {
offset += numRead;
}
// Ensure all the bytes have been read in
if (offset < fileBArray.length) {
throw new IOException("Could not completely read file "+file.getName());
}
fis.close();
// Decrypt the ciphertext using the public key
PublicKey pubKey = readKeyFromFile();
Cipher cipher = Cipher.getInstance("RSA");
cipher.init(Cipher.DECRYPT_MODE, pubKey);
byte[] newPlainText = cipher.doFinal(fileBArray);
// THE FOLLOWING TOAST PRINTS MANY <?> AND THAN THE DECRYPTED MESSAGE. THE TOTAL NUMBER OF CHARACTERS IS 255, EVEN IF I CHANGE ENCRYPTED TEXT!
toast(String.valueOf(cipher.doFinal(fileBArray).length));
if (new String(newPlainText, "utf-8").compareTo("Hello World!") == 0)
return true;
else
return false;
}
PublicKey readKeyFromFile() throws IOException {
Resources myResources = getResources();
//public key filename "pub.lic"
InputStream is = myResources.openRawResource(R.raw.pub);
ObjectInputStream oin =
new ObjectInputStream(new BufferedInputStream(is));
try {
BigInteger m = (BigInteger) oin.readObject();
BigInteger e = (BigInteger) oin.readObject();
RSAPublicKeySpec keySpec = new RSAPublicKeySpec(m, e);
KeyFactory fact = KeyFactory.getInstance("RSA");
PublicKey pubKey = fact.generatePublic(keySpec);
return pubKey;
} catch (Exception e) {
throw new RuntimeException("Spurious serialisation error", e);
} finally {
oin.close();
}
}
If you encrypt with RSA the input and output are always the same length as the key. In your case, that should be 256 bytes (=2048 bits), so first check your code, you are missing a byte.
When the input is shorter, you need to apply a padding, and it looks like your server and client are using a different one. Cipher.getInstance("RSA") will use the platform default, which is probably different for Android and Java SE. You need to specify the padding explicitly in both programs for this to work. Something like this:
Cipher cipher = Cipher.getInstance("RSA/None/PKCS1Padding");
BTW, you really don't want to distribute the private key with your app, so using the public key is the right thing to do. (Whether your whole encryption scheme is secure is another matter though).

AES 128 Implementation with predefined key

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;
}

Categories

Resources