java.lang.ArrayIndexOutOfBoundsException: too much data for RSA block - android

I'm using RSA encrypt text and decrypt text. The public key and the private key are generated with openssl tool.
I encountered an "java.lang.ArrayIndexOutOfBoundsException: too much data for RSA block" exception when decrypting data.
Here is the RSA util class:
package studio.uphie.app;
import android.util.Base64;
import java.security.KeyFactory;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import javax.crypto.Cipher;
/**
* Created by Uphie on 2016/4/11.
*/
public class RSA {
private static String RSA = "RSA";
/**
*
* #param text text to be encrypted
* #param pub_key rsa public key
* #return encrypted data in byte-array form
*/
public static byte[] encryptData(String text, String pub_key) {
try {
byte[] data = text.getBytes();
PublicKey publicKey = getPublicKey(Base64.decode(pub_key.getBytes(), Base64.DEFAULT));
Cipher cipher = Cipher.getInstance(RSA);
cipher.init(Cipher.ENCRYPT_MODE, publicKey);
return cipher.doFinal(data);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
/**
*
* #param text text to be decrypted
* #param pri_key rsa private key
* #return
*/
public static byte[] decryptData(String text, String pri_key) {
try {
byte[] data = text.getBytes();
PrivateKey privateKey = getPrivateKey(Base64.decode(pri_key.getBytes(),Base64.DEFAULT));
Cipher cipher = Cipher.getInstance(RSA);
cipher.init(Cipher.DECRYPT_MODE, privateKey);
return cipher.doFinal(data);
} catch (Exception e) {
//"java.lang.ArrayIndexOutOfBoundsException: too much data for RSA block" exception occurs here.
return null;
}
}
/**
*
* #param keyBytes
* #return
* #throws NoSuchAlgorithmException
* #throws InvalidKeySpecException
*/
public static PublicKey getPublicKey(byte[] keyBytes) throws NoSuchAlgorithmException, InvalidKeySpecException {
X509EncodedKeySpec keySpec = new X509EncodedKeySpec(keyBytes);
KeyFactory keyFactory = KeyFactory.getInstance(RSA);
return keyFactory.generatePublic(keySpec);
}
/**
*
* #param keyBytes
* #return
* #throws NoSuchAlgorithmException
* #throws InvalidKeySpecException
*/
public static PrivateKey getPrivateKey(byte[] keyBytes) throws NoSuchAlgorithmException,
InvalidKeySpecException {
PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(keyBytes);
KeyFactory keyFactory = KeyFactory.getInstance(RSA);
return keyFactory.generatePrivate(keySpec);
}
}
And the snippet that encrypts and decrypts data:
//encrypt
byte[] e = RSA.encryptData(text, PUBLIC_KEY);
String result = Base64.encodeToString(e, Base64.DEFAULT);
tv_encrypted.setText(result);
//decrypt
byte[] d = RSA.decryptData(text, PRIVATE_KEY);
String result = Base64.encodeToString(d, Base64.DEFAULT);
tv_decrypted.setText("Decrypted result:\n" + result);
I know the reason may be that the text to be decrypted is too long , but I just encrypt "abc" and then decrypt the encrypted "abc". And how to handle encrypting long text if the text to be encrypted or decrypted should be 11 bytes less than the rsa private key? How can I do to solve it? I'm new to RSA.
Thanks in advance!

You are missing some steps in your code which makes it impossible to check. However, there are a few clues to suggest a problem. Your decryptData method takes a String argument and then calls String.getBytes() to get the data which is then decrypted. However, the result of encryption is a sequence of bytes which is not the encoding of any valid String. Perhaps you meant to base64 decode the input instead of calling getBytes(). In general to perform decryption and decoding you must reverse the steps you performed during encryption and encoding. So, if the plaintext is a byte[] then the steps are:
byte [] → Encrypt → byte [] → Base64 encode → String.
then, in the decrypt direction you start with a Base64 string, you must, in order:
String → Base64 decode → byte [] → decrypt → byte []
Also, another issue which is bad practice and a source of many portability bugs is the use of defaults. You are using defaults in two places and they're both troublesome. First you are using the default no-args String.getBytes() method, and presumably matching that up with the one-arg String (byte []) constructor. This use the platform default character set, but this can differ on different platforms. Therefore always specify a character set. For most applications 'UTF-8' is an ideal choice. Secondly, you are calling Cipher.getInstance('RSA') without specifying padding. Oracle's Java and Android's Java will give you different padding and thus your code will not be portable between the platforms. Always specify the complete padding string. Here the choice is little more difficult if you need portability to older Java implementations. OAEP padding should be your first choice, so Cipher.getInstance("RSA/ECB/OAEPWithSHA-256AndMGF1Padding"); is probably the right choice. See this for further discussion.
As for how to encrypt longer texts, see the answer from Henry.

Fianlly I modified my codes like that and they work well:
public static String encryptData(String text, String pub_key) {
try {
byte[] data = text.getBytes("utf-8");
PublicKey publicKey = getPublicKey(Base64.decode(pub_key.getBytes("utf-8"), Base64.DEFAULT));
Cipher cipher = Cipher.getInstance(RSA);
cipher.init(Cipher.ENCRYPT_MODE, publicKey);
return Base64.encodeToString(cipher.doFinal(data),Base64.DEFAULT);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
public static String decryptData(String text, String pri_key) {
try {
byte[] data =Base64.decode(text,Base64.DEFAULT);
PrivateKey privateKey = getPrivateKey(Base64.decode(pri_key.getBytes("utf-8"),Base64.DEFAULT));
Cipher cipher = Cipher.getInstance(RSA);
cipher.init(Cipher.DECRYPT_MODE, privateKey);
return new String(cipher.doFinal(data),"utf-8");
} catch (Exception e) {
return null;
}
}
If something seems wrong still you can remind me. Thanks for James and Henry's answer.

Usually, you generate a random secret key for a symmetric cipher (like AES) and use this to encrypt your pay load.
RSA is then only used to encrypt this random key. This does not only solve the length problem but has some other advantages as well:
Symmetric cyphers are usually much faster
If the message is sent to several recievers, only the encrypted key has to be added specifically for each receiver, the main content can be the same.

Related

How to encrypt data in node.js using node-rsa and decrypt encrypted data in android?

Node.js code for encryption
function encrypt(msg) {
try {
const public_key = fs.readFileSync(path.join(__dirname, 'PublicKey.pem'), 'utf8');
const encryptStr = crypto.publicEncrypt({
key: public_key,
padding: crypto.constants.RSA_PKCS1_PADDING
}, Buffer.from(msg,'utf8'));
let encryptString = encryptStr.toString('hex');
return encryptString;
} catch (e) {
console.log(e);
return false;
}
}
Android Code for decryption
public static String decryptStringWithPrivateKey(String s, String keyFilename) throws Exception {
Cipher cipher = Cipher.getInstance("RSA");
PrivateKey pkey = readPrivateKeyFromPem(keyFilename);
cipher.init(Cipher.DECRYPT_MODE, pkey);
String dec = new String(cipher.doFinal(Base64.getDecoder().decode(s)), "UTF-8");
return dec;
}
public static PrivateKey readPrivateKeyFromPem(String keyFilename) throws Exception {
byte[] keyBytes = Files.readAllBytes(new File(keyFilename).toPath());
String keyString = new String(keyBytes);
if (keyString.contains("BEGIN PRIVATE KEY")) {
return readPrivateKeyFromPem_PKCS8(keyFilename);
}
else if(keyString.contains("BEGIN RSA PRIVATE KEY")){
return readPrivateKeyFromPem_PKCS1(keyFilename);
}
throw new Exception("Unknown private key format in "+keyFilename);
}
I want to encrypt api on server side and decrypt the data on android device to avoid unauthorized access.
while decryption on android "Input byte array has incorrect ending byte at 172"
this one error are thrown.
In the NodeJS code the cipher text is hex encoded and in the Java code it is Base64 decoded. This must be made consistent, either Base64 or hex encoding on both sides.
Also, in the Java code, when instantiating the cipher, only the algorithm is specified. This results in a default padding being used, which e.g. on my machine (API 28, Android 9 Pie) corresponds to RSA/ECB/NoPadding. This is not compatible with the NodeJS side, which uses PKCS#1 v1.5 padding (besides that RSA without padding, so called textbook RSA, is insecure). Therefore, padding must also be specified in the cipher instantiation with RSA/ECB/PKCS1Padding.
Using a private key in PKCS#8 format and an own implementation of readPrivateKeyFromPem_PKCS8() both codes work on my machine if the two bugs mentioned above are fixed. However, the methods not posted could also contain flaws.

Encrypt and decrypt string using ChaCha20

I want to decrypt and encrypt a string using chacha20
BouncyCastleProvider is using chacha20 technique. So I included it jar. and tried the code but not able to work.
PBE.java
public class PBE extends AppCompatActivity {
private static final String salt = "A long, but constant phrase that will be used each time as the salt.";
private static final int iterations = 2000;
private static final int keyLength = 256;
private static final SecureRandom random = new SecureRandom();
#Override
protected void onCreate(#Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.pbe);
try {
Security.insertProviderAt(new BouncyCastleProvider(), 1);
//Security.addProvider(new BouncyCastleProvider());
String passphrase = "The quick brown fox jumped over the lazy brown dog";
String plaintext = "Hello";
byte [] ciphertext = encrypt(passphrase, plaintext);
String recoveredPlaintext = decrypt(passphrase, ciphertext);
TextView decryptedTv = (TextView) findViewById(R.id.tv_decrypt);
decryptedTv.setText(recoveredPlaintext);
System.out.println(recoveredPlaintext);
}catch (Exception e){
e.printStackTrace();
}
}
private static byte [] encrypt(String passphrase, String plaintext) throws Exception {
SecretKey key = generateKey(passphrase);
Cipher cipher = Cipher.getInstance("AES/CTR/NOPADDING");//,new BouncyCastleProvider());
cipher.init(Cipher.ENCRYPT_MODE, key, generateIV(cipher), random);
return cipher.doFinal(plaintext.getBytes());
}
private static String decrypt(String passphrase, byte [] ciphertext) throws Exception {
SecretKey key = generateKey(passphrase);
Cipher cipher = Cipher.getInstance("AES/CTR/NOPADDING");// , new BouncyCastleProvider());
cipher.init(Cipher.DECRYPT_MODE, key, generateIV(cipher), random);
return new String(cipher.doFinal(ciphertext));
}
private static SecretKey generateKey(String passphrase) throws Exception {
PBEKeySpec keySpec = new PBEKeySpec(passphrase.toCharArray(), salt.getBytes(), iterations, keyLength);
SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("PBEWITHSHA256AND256BITAES-CBC-BC");
return keyFactory.generateSecret(keySpec);
}
private static IvParameterSpec generateIV(Cipher cipher) throws Exception {
byte [] ivBytes = new byte[cipher.getBlockSize()];
random.nextBytes(ivBytes);
return new IvParameterSpec(ivBytes);
}
}
But it is not giving me proper result..
Edit and Updated Code
public class ChaCha20Encryptor implements Encryptor {
private final byte randomIvBytes[] = {0, 1, 2, 3, 4, 5, 6, 7};
static {
Security.addProvider(new BouncyCastleProvider());
}
#Override
public byte[] encrypt(byte[] data, byte[] randomKeyBytes) throws IOException, InvalidKeyException,
InvalidAlgorithmParameterException, InvalidCipherTextException {
ChaChaEngine cipher = new ChaChaEngine();
CipherParameters cp = new KeyParameter(getMyKey(randomKeyBytes));
cipher.init(true, new ParametersWithIV(cp , randomIvBytes));
//cipher.init(true, new ParametersWithIV(new KeyParameter(randomKeyBytes), randomIvBytes));
byte[] result = new byte[data.length];
cipher.processBytes(data, 0, data.length, result, 0);
return result;
}
#Override
public byte[] decrypt(byte[] data, byte[] randomKeyBytes)
throws InvalidKeyException, InvalidAlgorithmParameterException, IOException,
IllegalStateException, InvalidCipherTextException {
ChaChaEngine cipher = new ChaChaEngine();
CipherParameters cp = new KeyParameter(getMyKey(randomKeyBytes));
cipher.init(false, new ParametersWithIV(cp , randomIvBytes));
//cipher.init(false, new ParametersWithIV(new KeyParameter(randomKeyBytes), randomIvBytes));
byte[] result = new byte[data.length];
cipher.processBytes(data, 0, data.length, result, 0);
return result;
}
#Override
public int getKeyLength() {
return 32;
}
#Override
public String toString() {
return "ChaCha20()";
}
private static byte[] getMyKey(byte[] key){
try {
//byte[] key = encodekey.getBytes("UTF-8");
MessageDigest sha = MessageDigest.getInstance("SHA-1");
key = sha.digest(key);
key = Arrays.copyOf(key, 16); // use only first 128 bit
}
catch (NoSuchAlgorithmException e){
e.printStackTrace();
}
return key;
}
}
Now I have only problem decrypting. It shows an error that key must be 128 or 256 bits. What am I doing wrong.
Update on 24-DEC-2019 (Correction)
Unlike some other modes in AES like CBC, GCM mode does not require the IV to be unpredictable (same as Chacha20-Poly1305). The only requirement is that the IV (for AES) or nonce (for Chacha20-Poly1305) has to be unique for each invocation with a given key. If it repeats once for a given key, security can be compromised. An easy way to achieve this with good probability is to use a random IV or nonce from a strong pseudo random number generator as shown below. Probability of an IV or nonce collision (assuming a strong random source) will be at most 2^-32, which is low enough to deter attackers.
Using a sequence or timestamp as IV or nonce is also possible, but it may not be as trivial as it may sound. For example, if the system does not correctly keep track of the sequences already used as IV in a persistent store, an invocation may repeat an IV after a system reboot. Likewise, there is no perfect clock. Computer clocks readjusts etc.
Also, the key should be rotated after every 2^32 invocations.
SecureRandom.getInstanceStrong() can be used to generate a cryptographically strong random nonce.
Original Answer
Now that ChaCha20 is supported is Java 11. Here is a sample program for encrypting and decrypting using ChaCha20-Poly1305.
The possible reasons for using ChaCha20-Poly1305 (which is a stream cipher based authenticated encryption algorithm) over AES-GCM (which is an authenticated block cipher algorithm) are:
ChaCha20-Poly1305 is almost 3 times faster than AES when the CPU does not provide dedicated AES instructions. Intel processors provide AES-NI instruction set [1]
ChaCha20-Poly1305 does not need the nonce to be unpredictable / random unlike the IV of AES-GCM. Thus the overhead for running pseudo random number generator can be avoided [2]
ChaCha20 is not vulnerable to cache-collision timing attacks unlike AES [1]
package com.sapbasu.javastudy;
import java.lang.reflect.Field;
import java.math.BigInteger;
import java.util.Arrays;
import java.util.Objects;
import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import javax.security.auth.Destroyable;
/**
*
* The possible reasons for using ChaCha20-Poly1305 which is a
* stream cipher based authenticated encryption algorithm
* 1. If the CPU does not provide dedicated AES instructions,
* ChaCha20 is faster than AES
* 2. ChaCha20 is not vulnerable to cache-collision timing
* attacks unlike AES
* 3. Since the nonce is not required to be random. There is
* no overhead for generating cryptographically secured
* pseudo random number
*
*/
public class CryptoChaCha20 {
private static final String ENCRYPT_ALGO = "ChaCha20-Poly1305/None/NoPadding";
private static final int KEY_LEN = 256;
private static final int NONCE_LEN = 12; //bytes
private static final BigInteger NONCE_MIN_VAL = new BigInteger("100000000000000000000000", 16);
private static final BigInteger NONCE_MAX_VAL = new BigInteger("ffffffffffffffffffffffff", 16);
private static BigInteger nonceCounter = NONCE_MIN_VAL;
public static byte[] encrypt(byte[] input, SecretKeySpec key)
throws Exception {
Objects.requireNonNull(input, "Input message cannot be null");
Objects.requireNonNull(key, "key cannot be null");
if (input.length == 0) {
throw new IllegalArgumentException("Length of message cannot be 0");
}
if (key.getEncoded().length * 8 != KEY_LEN) {
throw new IllegalArgumentException("Size of key must be 256 bits");
}
Cipher cipher = Cipher.getInstance(ENCRYPT_ALGO);
byte[] nonce = getNonce();
IvParameterSpec ivParameterSpec = new IvParameterSpec(nonce);
cipher.init(Cipher.ENCRYPT_MODE, key, ivParameterSpec);
byte[] messageCipher = cipher.doFinal(input);
// Prepend the nonce with the message cipher
byte[] cipherText = new byte[messageCipher.length + NONCE_LEN];
System.arraycopy(nonce, 0, cipherText, 0, NONCE_LEN);
System.arraycopy(messageCipher, 0, cipherText, NONCE_LEN,
messageCipher.length);
return cipherText;
}
public static byte[] decrypt(byte[] input, SecretKeySpec key)
throws Exception {
Objects.requireNonNull(input, "Input message cannot be null");
Objects.requireNonNull(key, "key cannot be null");
if (input.length == 0) {
throw new IllegalArgumentException("Input array cannot be empty");
}
byte[] nonce = new byte[NONCE_LEN];
System.arraycopy(input, 0, nonce, 0, NONCE_LEN);
byte[] messageCipher = new byte[input.length - NONCE_LEN];
System.arraycopy(input, NONCE_LEN, messageCipher, 0, input.length - NONCE_LEN);
IvParameterSpec ivParameterSpec = new IvParameterSpec(nonce);
Cipher cipher = Cipher.getInstance(ENCRYPT_ALGO);
cipher.init(Cipher.DECRYPT_MODE, key, ivParameterSpec);
return cipher.doFinal(messageCipher);
}
/**
*
* This method creates the 96 bit nonce. A 96 bit nonce
* is required for ChaCha20-Poly1305. The nonce is not
* a secret. The only requirement being it has to be
* unique for a given key. The following function implements
* a 96 bit counter which when invoked always increments
* the counter by one.
*
* #return
*/
public static byte[] getNonce() {
if (nonceCounter.compareTo(NONCE_MAX_VAL) == -1) {
return nonceCounter.add(BigInteger.ONE).toByteArray();
} else {
nonceCounter = NONCE_MIN_VAL;
return NONCE_MIN_VAL.toByteArray();
}
}
/**
*
* Strings should not be used to hold the clear text message or the key, as
* Strings go in the String pool and they will show up in a heap dump. For the
* same reason, the client calling these encryption or decryption methods
* should clear all the variables or arrays holding the message or the key
* after they are no longer needed. Since Java 8 does not provide an easy
* mechanism to clear the key from {#code SecretKeySpec}, this method uses
* reflection to clear the key
*
* #param key
* The secret key used to do the encryption
* #throws IllegalArgumentException
* #throws IllegalAccessException
* #throws NoSuchFieldException
* #throws SecurityException
*/
#SuppressWarnings("unused")
public static void clearSecret(Destroyable key)
throws IllegalArgumentException, IllegalAccessException,
NoSuchFieldException, SecurityException {
Field keyField = key.getClass().getDeclaredField("key");
keyField.setAccessible(true);
byte[] encodedKey = (byte[]) keyField.get(key);
Arrays.fill(encodedKey, Byte.MIN_VALUE);
}
}
And, here is a JUnit test:
package com.sapbasu.javastudy;
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.charset.Charset;
import java.security.SecureRandom;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import org.junit.jupiter.api.Test;
public class CryptoChaCha20Test {
private int KEY_LEN = 256; // bits
#Test
public void whenDecryptCalled_givenEncryptedTest_returnsDecryptedBytes()
throws Exception {
char[] input = {'e', 'n', 'c', 'r', 'y', 'p', 't', 'i', 'o', 'n'};
byte[] inputBytes = convertInputToBytes(input);
KeyGenerator keyGen = KeyGenerator.getInstance("ChaCha20");
keyGen.init(KEY_LEN, SecureRandom.getInstanceStrong());
SecretKey secretKey = keyGen.generateKey();
SecretKeySpec secretKeySpec = new SecretKeySpec(secretKey.getEncoded(),
"ChaCha20");
CryptoChaCha20.clearSecret(secretKey);
byte[] encryptedBytes = CryptoChaCha20.encrypt(inputBytes, secretKeySpec);
byte[] decryptedBytes = CryptoChaCha20.decrypt(encryptedBytes, secretKeySpec);
CryptoChaCha20.clearSecret(secretKeySpec);
assertArrayEquals(inputBytes, decryptedBytes);
}
private byte[] convertInputToBytes(char[] input) {
CharBuffer charBuf = CharBuffer.wrap(input);
ByteBuffer byteBuf = Charset.forName(Charset.defaultCharset().name())
.encode(charBuf);
byte[] inputBytes = byteBuf.array();
charBuf.clear();
byteBuf.clear();
return inputBytes;
}
}
The output of a cipher consists of random bits (generally limited by implementations to 8-bit bytes). Random bytes are likely to contain invalid characters in any character set. If you require a String, encode the ciphertext to base 64.
Furthermore, you re-generate the IV on decrypt. IV during encryption/decryption should match.

Custom String padding with zeroes to 256-bit size key for AES/ECB/PKCS7Padding

Question Description
I met a situation recently in which case I need to do a 256-AES cross platform encryption/decryption between iOS and Android with a pre-defined String key like this PreDefinedKey.
The AES implementation is done on iOS with this code, and all I need to do is to change the code on Android so that I can do "cross-platform" encryption/decryption.
NOTE: I'm aware of that the AES code on iOS has a severe security/memory problem, but its currently not my concern :-)
I was able to do the encryption/decryption on either Android and iOS individually. However it seemed the two AES implementations here have a trivial difference which prevented me to do the "cross-platform" encryption/decryption. For example, I put the Android encrypted String to iOS and it can't return a expected result (in this case, it returns null).
Question:
On both iOS and Android platform, I'm sure the algorithm is AES/ECB/PKCS7Padding, with 128-Rijndael algorithm for AES implementation.
Both platform should use 256-bit size key. And with a deeper look inside of the iOS AES code I found that it actually uses zeroes to pad the key to 256-bit.
Here's the zero paddings related code snippet on iOS:
// 'key' should be 32 bytes for AES256, will be null-padded otherwise
char keyPtr[kCCKeySizeAES256+1]; // room for terminator (unused)
bzero(keyPtr, sizeof(keyPtr)); // fill with zeroes (for padding)
And here's the AES parameters in that code (it uses Rijndael-128 Algorithm, 256-bit key size, NULL for Initial Vectors):
CCCryptorStatus cryptStatus = CCCrypt(kCCEncrypt, kCCAlgorithmAES128, kCCOptionPKCS7Padding,
keyPtr, kCCKeySizeAES256,
NULL /* initialization vector (optional) */,
[self bytes], dataLength, /* input */
buffer, bufferSize, /* output */
&numBytesEncrypted);
But on Android I don't know how to do the similar stuff, so could someone point out the right way for me?
Code I'm Using
On Android platform, I use the code below to do AES implementation:
private static final String AES_SECRET = "PreDefinedKey";
/**
* Method for AES encryption
* #param raw
* #param plain
* #return
* #throws Exception
*/
private static byte[] encrypt(byte[] raw, byte[] plain) throws Exception {
SecretKeySpec skeySpec = new SecretKeySpec(raw, "AES/ECB/PKCS7Padding");
Cipher cipher = Cipher.getInstance("AES/ECB/PKCS7Padding");
cipher.init(Cipher.ENCRYPT_MODE, skeySpec);
byte[] encrypted = cipher.doFinal(plain);
return encrypted;
}
/**
* AES decryption
* #param encryptMsg
* #return
* #throws Exception
*/
public static String AESDecrypt(String encryptMsg)
throws Exception {
byte[] rawKey = getRawKey(AES_SECRET.getBytes());
//byte[] enc = toByte(encryptMsg);
byte[] enc = Base64.decode(encryptMsg, 0);
byte[] result = decrypt(rawKey, enc);
return new String(result);
}
/**
* Method for AES decryption
* #param raw
* #param encrypted
* #return
* #throws Exception
*/
private static byte[] decrypt(byte[] raw, byte[] encrypted) throws Exception {
SecretKeySpec keySpec = new SecretKeySpec(raw, "AES/ECB/PKCS7Padding");
Cipher cipher = Cipher.getInstance("AES/ECB/PKCS7Padding");
cipher.init(Cipher.DECRYPT_MODE, keySpec);
byte[] decrypted = cipher.doFinal(encrypted);
return decrypted;
}
public static byte[] getRawKey(byte[] seed) throws Exception {
KeyGenerator kgen = KeyGenerator.getInstance("AES");
SecureRandom sr = SecureRandom.getInstance("SHA1PRNG");
sr.setSeed(seed);
//Init for 256bit AES key
kgen.init(256);
SecretKey secret = kgen.generateKey();
//Get secret raw key
byte[] raw = secret.getEncoded();
return seed;
}
In the method of getRawKey(), it uses SHA1PRNG to generate random paddings to make the AES key to 256-bit size which is different from the iOS implementation (it uses zeroes to pad the key to 256-bit).
So, how do I change this method so that I can use my pre-defined string key which is padded with zeroes to 256-bit?
Please let me know if you require more info. Thx!
Find whoever came up with this zero-padded scheme and have them fired. Then have the app reviewed.
As for your question, simply create a byte array of length 32 and copy the key bytes a the beginning, the use it to initialize SecretKeySpec. The KeyGenerator will generate a random key, and the whole 'fixed seed' idea is flawed and doesn't work on latest Android version. Here's some code:
// zeros by default
byte[] rawKey = new byte[32];
// if you don't specify the encoding you might get weird results
byte[] keyBytes = AES_SECRET.getBytes("ASCII");
System.arraycopy(keyBytes, 0, rawKey, 0, keyBytes.length);
SecretKey key = new SecretKeySpec(rawKey, "AES");
Cipher cipher = ...
// rest of your decryption code

Encryption error on Android 4.2

The following code is working on all the versions of android except the latest 4.2
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.KeyGenerator;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
/**
* Util class to perform encryption/decryption over strings. <br/>
*/
public final class UtilsEncryption
{
/** The logging TAG */
private static final String TAG = UtilsEncryption.class.getName();
/** */
private static final String KEY = "some_encryption_key";
/**
* Avoid instantiation. <br/>
*/
private UtilsEncryption()
{
}
/** The HEX characters */
private final static String HEX = "0123456789ABCDEF";
/**
* Encrypt a given string. <br/>
*
* #param the string to encrypt
* #return the encrypted string in HEX
*/
public static String encrypt( String cleartext )
{
try
{
byte[] result = process( Cipher.ENCRYPT_MODE, cleartext.getBytes() );
return toHex( result );
}
catch ( Exception e )
{
System.out.println( TAG + ":encrypt:" + e.getMessage() );
}
return null;
}
/**
* Decrypt a HEX encrypted string. <br/>
*
* #param the HEX string to decrypt
* #return the decrypted string
*/
public static String decrypt( String encrypted )
{
try
{
byte[] enc = fromHex( encrypted );
byte[] result = process( Cipher.DECRYPT_MODE, enc );
return new String( result );
}
catch ( Exception e )
{
System.out.println( TAG + ":decrypt:" + e.getMessage() );
}
return null;
}
/**
* Get the raw encryption key. <br/>
*
* #param the seed key
* #return the raw key
* #throws NoSuchAlgorithmException
*/
private static byte[] getRawKey()
throws NoSuchAlgorithmException
{
KeyGenerator kgen = KeyGenerator.getInstance( "AES" );
SecureRandom sr = SecureRandom.getInstance( "SHA1PRNG" );
sr.setSeed( KEY.getBytes() );
kgen.init( 128, sr );
SecretKey skey = kgen.generateKey();
return skey.getEncoded();
}
/**
* Process the given input with the provided mode. <br/>
*
* #param the cipher mode
* #param the value to process
* #return the processed value as byte[]
* #throws InvalidKeyException
* #throws IllegalBlockSizeException
* #throws BadPaddingException
* #throws NoSuchAlgorithmException
* #throws NoSuchPaddingException
*/
private static byte[] process( int mode, byte[] value )
throws InvalidKeyException, IllegalBlockSizeException, BadPaddingException, NoSuchAlgorithmException,
NoSuchPaddingException
{
SecretKeySpec skeySpec = new SecretKeySpec( getRawKey(), "AES" );
Cipher cipher = Cipher.getInstance( "AES" );
cipher.init( mode, skeySpec );
byte[] encrypted = cipher.doFinal( value );
return encrypted;
}
/**
* Decode an HEX encoded string into a byte[]. <br/>
*
* #param the HEX string value
* #return the decoded byte[]
*/
protected static byte[] fromHex( String value )
{
int len = value.length() / 2;
byte[] result = new byte[len];
for ( int i = 0; i < len; i++ )
{
result[i] = Integer.valueOf( value.substring( 2 * i, 2 * i + 2 ), 16 ).byteValue();
}
return result;
}
/**
* Encode a byte[] into an HEX string. <br/>
*
* #param the byte[] value
* #return the HEX encoded string
*/
protected static String toHex( byte[] value )
{
if ( value == null )
{
return "";
}
StringBuffer result = new StringBuffer( 2 * value.length );
for ( int i = 0; i < value.length; i++ )
{
byte b = value[i];
result.append( HEX.charAt( ( b >> 4 ) & 0x0f ) );
result.append( HEX.charAt( b & 0x0f ) );
}
return result.toString();
}
}
Here's a small unit test that i've created to reproduce the error
import junit.framework.TestCase;
public class UtilsEncryptionTest
extends TestCase
{
/** A random string */
private static String ORIGINAL = "some string to test";
/**
* The HEX value corresponds to ORIGINAL. <br/>
* If you change ORIGINAL, calculate the new value on one of this sites:
* <ul>
* <li>http://www.string-functions.com/string-hex.aspx</li>
* <li>http://www.yellowpipe.com/yis/tools/encrypter/index.php</li>
* <li>http://www.convertstring.com/EncodeDecode/HexEncode</li>
* </ul>
*/
private static String HEX = "736F6D6520737472696E6720746F2074657374";
public void testToHex()
{
String hexString = UtilsEncryption.toHex( ORIGINAL.getBytes() );
assertNotNull( "The HEX string should not be null", hexString );
assertTrue( "The HEX string should not be empty", hexString.length() > 0 );
assertEquals( "The HEX string was not encoded correctly", HEX, hexString );
}
public void testFromHex()
{
byte[] stringBytes = UtilsEncryption.fromHex( HEX );
assertNotNull( "The HEX string should not be null", stringBytes );
assertTrue( "The HEX string should not be empty", stringBytes.length > 0 );
assertEquals( "The HEX string was not encoded correctly", ORIGINAL, new String( stringBytes ) );
}
public void testWholeProcess()
{
String encrypted = UtilsEncryption.encrypt( ORIGINAL );
assertNotNull( "The encrypted result should not be null", encrypted );
assertTrue( "The encrypted result should not be empty", encrypted.length() > 0 );
String decrypted = UtilsEncryption.decrypt( encrypted );
assertNotNull( "The decrypted result should not be null", decrypted );
assertTrue( "The decrypted result should not be empty", decrypted.length() > 0 );
assertEquals( "Something went wrong", ORIGINAL, decrypted );
}
}
The line throwing the exception is:
byte[] encrypted = cipher.doFinal( value );
The full stack trace is:
W/<package>.UtilsEncryption:decrypt(16414): pad block corrupted
W/System.err(16414): javax.crypto.BadPaddingException: pad block corrupted
W/System.err(16414): at com.android.org.bouncycastle.jcajce.provider.symmetric.util.BaseBlockCipher.engineDoFinal(BaseBlockCipher.java:709)
W/System.err(16414): at javax.crypto.Cipher.doFinal(Cipher.java:1111)
W/System.err(16414): at <package>.UtilsEncryption.process(UtilsEncryption.java:117)
W/System.err(16414): at <package>.UtilsEncryption.decrypt(UtilsEncryption.java:69)
W/System.err(16414): at <package>.UtilsEncryptionTest.testWholeProcess(UtilsEncryptionTest.java:74)
W/System.err(16414): at java.lang.reflect.Method.invokeNative(Native Method)
W/System.err(16414): at java.lang.reflect.Method.invoke(Method.java:511)
W/System.err(16414): at junit.framework.TestCase.runTest(TestCase.java:168)
W/System.err(16414): at junit.framework.TestCase.runBare(TestCase.java:134)
W/System.err(16414): at junit.framework.TestResult$1.protect(TestResult.java:115)
W/System.err(16414): at junit.framework.TestResult.runProtected(TestResult.java:133)
D/elapsed ( 588): 14808
W/System.err(16414): at junit.framework.TestResult.run(TestResult.java:118)
W/System.err(16414): at junit.framework.TestCase.run(TestCase.java:124)
W/System.err(16414): at android.test.AndroidTestRunner.runTest(AndroidTestRunner.java:190)
W/System.err(16414): at android.test.AndroidTestRunner.runTest(AndroidTestRunner.java:175)
W/System.err(16414): at android.test.InstrumentationTestRunner.onStart(InstrumentationTestRunner.java:555)
W/System.err(16414): at android.app.Instrumentation$InstrumentationThread.run(Instrumentation.java:1661)
Does anybody have a clue of what might be happening ?
Is anybody aware of a breaking change on android 4.2 in any of the referenced classes?
Thanks a lot
From the Android Jellybean page:
Modified the default implementations of SecureRandom and Cipher.RSA to use OpenSSL
They changed the default provider for SecureRandom to use OpenSSL instead of the previous Crypto provider.
The following code will produce two different outputs on pre-Android 4.2 and Android 4.2:
SecureRandom rand = SecureRandom.getInstance("SHA1PRNG");
Log.i(TAG, "rand.getProvider(): " + rand.getProvider().getName());
On pre-4.2 devices:
rand.getProvider: Crypto
On 4.2 devices:
rand.getProvider: AndroidOpenSSL
Fortunately, it's easy to revert to the old behavior:
SecureRandom sr = SecureRandom.getInstance( "SHA1PRNG", "Crypto" );
To be sure, it's dangerous to be calling SecureRandom.setSeed at all in light of the Javadocs which state:
Seeding SecureRandom may be insecure
A seed is an array of bytes used to bootstrap random number generation. To produce cryptographically secure random numbers, both the seed and the algorithm must be secure.
By default, instances of this class will generate an initial seed using an internal entropy source, such as /dev/urandom. This seed is unpredictable and appropriate for secure use.
You may alternatively specify the initial seed explicitly with the seeded constructor or by calling setSeed(byte[]) before any random numbers have been generated. Specifying a fixed seed will cause the instance to return a predictable sequence of numbers. This may be useful for testing but it is not appropriate for secure use.
However, for writing unit tests, as you are doing, using setSeed may be okay.
As Brigham pointed out that, in Android 4.2, there was a security enhancement, which updated the default implementation of SecureRandom from Crypto to OpenSSL
Cryptography - Modified the default implementations of SecureRandom and Cipher.RSA to use OpenSSL. Added SSL Socket support
for TLSv1.1 and TLSv1.2 using OpenSSL 1.0.1
bu Brigham's answer is a temporary solution and not recommended, because although it resolves the issue, its still doing the wrong way.
The recommended way (check Nelenkov’s tutorial) is to use proper key derivations PKCS (Public Key Cryptography Standard), which defines two key derivation functions, PBKDF1 and PBKDF2, of which PBKDF2 is more recommended.
This is how you should get the key,
int iterationCount = 1000;
int saltLength = 8; // bytes; 64 bits
int keyLength = 256;
SecureRandom random = new SecureRandom();
byte[] salt = new byte[saltLength];
random.nextBytes(salt);
KeySpec keySpec = new PBEKeySpec(seed.toCharArray(), salt,
iterationCount, keyLength);
SecretKeyFactory keyFactory = SecretKeyFactory
.getInstance("PBKDF2WithHmacSHA1");
byte[] raw = keyFactory.generateSecret(keySpec).getEncoded();
So what you are trying is to use pseudo random generator as a key derivation function. This is bad for the following reasons:
PRNG are by design non-deterministic and you are relying on it to be deterministic
Relying on a bug and deprecated implementations will break your App some day
PRNG are not designed to be good KDFs
More precisely Google deprecated the use of the Crypto provider in Android N (SDK 24)
Here are some better methods:
Hashed Message Authentication Code (HMAC)-based key derivation function (HKDF)
Using this library:
String userInput = "this is a user input with bad entropy";
HKDF hkdf = HKDF.fromHmacSha256();
//extract the "raw" data to create output with concentrated entropy
byte[] pseudoRandomKey = hkdf.extract(staticSalt32Byte, userInput.getBytes(StandardCharsets.UTF_8));
//create expanded bytes for e.g. AES secret key and IV
byte[] expandedAesKey = hkdf.expand(pseudoRandomKey, "aes-key".getBytes(StandardCharsets.UTF_8), 16);
//Example boilerplate encrypting a simple string with created key/iv
SecretKey key = new SecretKeySpec(expandedAesKey, "AES"); //AES-128 key
PBKDF2 (Password-Based Key Derivation Function 2)
has key stretching which makes it more expensive to brute force the key. Use this for weak key input (like user password):
SecretKeyFactory secretKeyFactory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
KeySpec keySpec = new PBEKeySpec(passphraseOrPin, salt, iterations, outputKeyLength);
SecretKey secretKey = secretKeyFactory.generateSecret(keySpec);
return secretKey;
There are more KDFs like BCrypt, scrypt and Argon2

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

Categories

Resources