/*
* Copyright 2012 Nikolay Elenkov (https://github.com/nelenkov/android-pbe/blob/master/LICENSE)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.orange.familylink.util;
import java.io.UnsupportedEncodingException;
import java.security.GeneralSecurityException;
import java.security.SecureRandom;
import java.security.spec.KeySpec;
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;
import org.orange.familylink.BuildConfig;
import android.util.Base64;
import android.util.Log;
public class Crypto {
private static final String TAG = Crypto.class.getSimpleName();
private static final String PBKDF2_DERIVATION_ALGORITHM = "PBKDF2WithHmacSHA1";
private static final String CIPHER_ALGORITHM = "AES/CBC/PKCS5Padding";
private static String DELIMITER = "!";
private static int KEY_LENGTH = 256;
// minimum values recommended by PKCS#5, increase as necessary
private static int ITERATION_COUNT = 1000;
private static final int PKCS5_SALT_LENGTH = 16;
private static SecureRandom random = new SecureRandom();
private Crypto() {
}
public static SecretKey deriveKeyPbkdf2(byte[] salt, String password) {
try {
long start, elapsed;
if(BuildConfig.DEBUG) start = System.currentTimeMillis();
KeySpec keySpec = new PBEKeySpec(password.toCharArray(), salt,
ITERATION_COUNT, KEY_LENGTH);
SecretKeyFactory keyFactory = SecretKeyFactory
.getInstance(PBKDF2_DERIVATION_ALGORITHM);
//the output of generateSecret() is actually a PBEKey instance which does not
//contain an initialized IV -- the Cipher object expects that from a PBEKey and
//will throw an exception if it is not present.
//So don't use the SecretKey produced by the factory as is,
//but use its encoded value to create a new SecretKeySpec object
byte[] keyBytes = keyFactory.generateSecret(keySpec).getEncoded();
SecretKey result = new SecretKeySpec(keyBytes, "AES");
if(BuildConfig.DEBUG) {
elapsed = System.currentTimeMillis() - start;
Log.d(TAG, String.format("PBKDF2 key derivation took %d [ms].", elapsed));
}
return result;
} catch (GeneralSecurityException e) {
throw new RuntimeException(e);
}
}
private static byte[] generateIv(int length) {
byte[] b = new byte[length];
random.nextBytes(b);
return b;
}
private static byte[] generateSalt() {
byte[] b = new byte[PKCS5_SALT_LENGTH];
random.nextBytes(b);
return b;
}
public static String encrypt(String plaintext, SecretKey key, byte[] salt) {
try {
Cipher cipher = Cipher.getInstance(CIPHER_ALGORITHM);
byte[] iv = generateIv(cipher.getBlockSize());
if(BuildConfig.DEBUG) Log.d(TAG, "IV: " + toHex(iv));
IvParameterSpec ivParams = new IvParameterSpec(iv);
cipher.init(Cipher.ENCRYPT_MODE, key, ivParams);
if(BuildConfig.DEBUG) Log.d(TAG, "Cipher IV: "
+ (cipher.getIV() == null ? null : toHex(cipher.getIV())));
byte[] cipherText = cipher.doFinal(plaintext.getBytes("UTF-8"));
if (salt != null) {
return String.format("%s%s%s%s%s", toBase64(salt), DELIMITER,
toBase64(iv), DELIMITER, toBase64(cipherText));
}
return String.format("%s%s%s", toBase64(iv), DELIMITER,
toBase64(cipherText));
} catch (GeneralSecurityException e) {
throw new RuntimeException(e);
} catch (UnsupportedEncodingException e) {
throw new RuntimeException(e);
}
}
public static String decrypt(byte[] cipherBytes, SecretKey key, byte[] iv) {
try {
Cipher cipher = Cipher.getInstance(CIPHER_ALGORITHM);
IvParameterSpec ivParams = new IvParameterSpec(iv);
cipher.init(Cipher.DECRYPT_MODE, key, ivParams);
if(BuildConfig.DEBUG) Log.d(TAG, "Cipher IV: " + toHex(cipher.getIV()));
byte[] plaintext = cipher.doFinal(cipherBytes);
String plainrStr = new String(plaintext, "UTF-8");
return plainrStr;
} catch (GeneralSecurityException e) {
throw new RuntimeException(e);
} catch (UnsupportedEncodingException e) {
throw new RuntimeException(e);
}
}
public static String encrypt(String plaintext, String password) {
byte[] salt = generateSalt();
SecretKey key = deriveKeyPbkdf2(salt, password);
return encrypt(plaintext, key, salt);
}
public static String decrypt(String ciphertext, String password) {
String[] fields = ciphertext.split(DELIMITER);
if (fields.length != 3) {
throw new IllegalArgumentException("Invalid encypted text format");
}
byte[] salt = fromBase64(fields[0]);
byte[] iv = fromBase64(fields[1]);
byte[] cipherBytes = fromBase64(fields[2]);
SecretKey key = deriveKeyPbkdf2(salt, password);
return decrypt(cipherBytes, key, iv);
}
private static String toHex(byte[] bytes) {
StringBuffer buff = new StringBuffer();
for (byte b : bytes) {
buff.append(String.format("%02X", b));
}
return buff.toString();
}
private static String toBase64(byte[] bytes) {
return Base64.encodeToString(bytes, Base64.NO_WRAP | Base64.URL_SAFE | Base64.NO_PADDING);
}
private static byte[] fromBase64(String base64) {
return Base64.decode(base64, Base64.NO_WRAP | Base64.URL_SAFE | Base64.NO_PADDING);
}
}