package devliving.online.securedpreferencestore;
import android.annotation.TargetApi;
import android.content.Context;
import android.content.SharedPreferences;
import android.os.Build;
import android.security.KeyPairGeneratorSpec;
import android.security.keystore.KeyGenParameterSpec;
import android.security.keystore.KeyProperties;
import android.util.Base64;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.math.BigInteger;
import java.security.GeneralSecurityException;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.KeyPairGenerator;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.SecureRandom;
import java.security.UnrecoverableEntryException;
import java.security.cert.CertificateException;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import java.util.List;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.CipherInputStream;
import javax.crypto.CipherOutputStream;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.KeyGenerator;
import javax.crypto.Mac;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.SecretKey;
import javax.crypto.spec.GCMParameterSpec;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import javax.security.auth.x500.X500Principal;
/**
* Created by Mehedi on 8/21/16.
*/
public class EncryptionManager {
private final int RSA_BIT_LENGTH = 2048;
private final int AES_BIT_LENGTH = 256;
private final int MAC_BIT_LENGTH = 256;
private final int GCM_TAG_LENGTH = 128;
private final static String DEFAULT_CHARSET = "UTF-8";
private final String KEYSTORE_PROVIDER = "AndroidKeyStore";
private final String SSL_PROVIDER = "AndroidOpenSSL";
private final String BOUNCY_CASTLE_PROVIDER = "BC";
private final String RSA_KEY_ALIAS = "sps_rsa_key";
private final String AES_KEY_ALIAS = "sps_aes_key";
private final String MAC_KEY_ALIAS = "sps_mac_key";
private final String DELIMITER = "]";
private static final String KEY_ALGORITHM_AES = "AES";
private static final String KEY_ALGORITHM_RSA = "RSA";
private static final String BLOCK_MODE_ECB = "ECB";
private static final String BLOCK_MODE_GCM = "GCM";
private static final String BLOCK_MODE_CBC = "CBC";
private static final String ENCRYPTION_PADDING_RSA_PKCS1 = "PKCS1Padding";
private static final String ENCRYPTION_PADDING_PKCS7 = "PKCS7Padding";
private static final String ENCRYPTION_PADDING_NONE = "NoPadding";
private static final String MAC_ALGORITHM_HMAC_SHA256 = "HmacSHA256";
private final String RSA_CIPHER = KEY_ALGORITHM_RSA + "/" +
BLOCK_MODE_ECB + "/" +
ENCRYPTION_PADDING_RSA_PKCS1;
private final String AES_CIPHER = KEY_ALGORITHM_AES + "/" +
BLOCK_MODE_GCM + "/" +
ENCRYPTION_PADDING_NONE;
private final String AES_CIPHER_COMPAT = KEY_ALGORITHM_AES + "/" +
BLOCK_MODE_CBC + "/" +
ENCRYPTION_PADDING_PKCS7;
private final String MAC_CIPHER = MAC_ALGORITHM_HMAC_SHA256;
private final String IS_COMPAT_MODE_KEY_ALIAS = "sps_data_in_compat";
private KeyStore mStore;
private SecretKey aesKey;
private SecretKey macKey;
private RSAPublicKey publicKey;
private RSAPrivateKey privateKey;
private boolean isCompatMode = false;
private Context mContext;
SharedPreferences mPrefs;
SecuredPreferenceStore.KeyStoreRecoveryNotifier mRecoveryHandler;
public EncryptionManager(Context context, SharedPreferences prefStore, SecuredPreferenceStore.KeyStoreRecoveryNotifier recoveryHandler)
throws IOException, NoSuchAlgorithmException, InvalidAlgorithmParameterException, NoSuchProviderException, NoSuchPaddingException, CertificateException, KeyStoreException, UnrecoverableEntryException, InvalidKeyException, IllegalStateException {
String isCompatKey = getHashed(IS_COMPAT_MODE_KEY_ALIAS);
isCompatMode = prefStore.getBoolean(isCompatKey, Build.VERSION.SDK_INT < Build.VERSION_CODES.M);
mRecoveryHandler = recoveryHandler;
mContext = context;
mPrefs = prefStore;
loadKeyStore();
boolean tryAgain = false;
try {
setup(context, prefStore);
} catch (Exception ex){
if(isRecoverableError(ex)) tryAgain = tryRecovery(ex);
else throw ex;
}
if(tryAgain){
setup(context, prefStore);
}
}
<T extends Exception> boolean isRecoverableError(T error){
return (error instanceof KeyStoreException)
|| (error instanceof UnrecoverableEntryException)
|| (error instanceof InvalidKeyException)
|| (error instanceof IllegalStateException)
|| (error instanceof IOException && (error.getCause() != null && error.getCause() instanceof BadPaddingException))
;
}
void setup(Context context, SharedPreferences prefStore) throws NoSuchPaddingException, InvalidKeyException, NoSuchAlgorithmException, KeyStoreException, UnrecoverableEntryException, NoSuchProviderException, InvalidAlgorithmParameterException, IOException {
generateKey(context, prefStore);
loadKey(prefStore);
}
<T extends Exception> boolean tryRecovery(T e){
return mRecoveryHandler != null && mRecoveryHandler.onRecoveryRequired(e, mStore, keyAliases());
}
List<String> keyAliases(){
return Arrays.asList(AES_KEY_ALIAS, RSA_KEY_ALIAS);
}
/**
* Tries to recover once if a Keystore error occurs
* @param bytes
* @return
* @throws NoSuchPaddingException
* @throws InvalidAlgorithmParameterException
* @throws NoSuchAlgorithmException
* @throws IOException
* @throws BadPaddingException
* @throws IllegalBlockSizeException
* @throws NoSuchProviderException
* @throws InvalidKeyException
*/
public EncryptedData tryEncrypt(byte[] bytes) throws NoSuchPaddingException, InvalidAlgorithmParameterException, NoSuchAlgorithmException, IOException, BadPaddingException, IllegalBlockSizeException, NoSuchProviderException, InvalidKeyException, KeyStoreException, UnrecoverableEntryException {
EncryptedData result = null;
boolean tryAgain = false;
try {
result = encrypt(bytes);
} catch (Exception ex){
if(isRecoverableError(ex)) tryAgain = tryRecovery(ex);
else throw ex;
}
if(tryAgain){
setup(mContext, mPrefs);
result = encrypt(bytes);
}
return result;
}
/**
* tries recovery once if a Keystore error occurs
* @param data
* @return
* @throws NoSuchPaddingException
* @throws InvalidAlgorithmParameterException
* @throws NoSuchAlgorithmException
* @throws KeyStoreException
* @throws UnrecoverableEntryException
* @throws NoSuchProviderException
* @throws InvalidKeyException
* @throws IOException
* @throws BadPaddingException
* @throws IllegalBlockSizeException
* @throws InvalidMacException
*/
public byte[] tryDecrypt(EncryptedData data) throws NoSuchPaddingException, InvalidAlgorithmParameterException, NoSuchAlgorithmException, KeyStoreException, UnrecoverableEntryException, NoSuchProviderException, InvalidKeyException, IOException, BadPaddingException, IllegalBlockSizeException, InvalidMacException {
byte[] result = null;
boolean tryAgain = false;
try{
result = decrypt(data);
}catch (Exception ex){
if(isRecoverableError(ex)) tryAgain = tryRecovery(ex);
else throw ex;
}
if(tryAgain){
setup(mContext, mPrefs);
result = decrypt(data);
}
return result;
}
/**
* @param bytes
* @return
* @throws NoSuchPaddingException
* @throws NoSuchAlgorithmException
* @throws InvalidKeyException
* @throws IOException
* @throws BadPaddingException
* @throws NoSuchProviderException
* @throws IllegalBlockSizeException
* @throws InvalidAlgorithmParameterException
*/
public EncryptedData encrypt(byte[] bytes) throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidKeyException, IOException, BadPaddingException, NoSuchProviderException, IllegalBlockSizeException, InvalidAlgorithmParameterException {
if (bytes != null && bytes.length > 0) {
byte[] IV = getIV();
if (isCompatMode)
return encryptAESCompat(bytes, IV);
else return encryptAES(bytes, IV);
}
return null;
}
/**
*
* @param data
* @return
* @throws IOException
* @throws NoSuchPaddingException
* @throws InvalidAlgorithmParameterException
* @throws NoSuchAlgorithmException
* @throws IllegalBlockSizeException
* @throws BadPaddingException
* @throws InvalidMacException
* @throws NoSuchProviderException
* @throws InvalidKeyException
*/
public byte[] decrypt(EncryptedData data) throws IOException, NoSuchPaddingException, InvalidAlgorithmParameterException, NoSuchAlgorithmException, IllegalBlockSizeException, BadPaddingException, InvalidMacException, NoSuchProviderException, InvalidKeyException {
if (data != null && data.encryptedData != null) {
if (isCompatMode)
return decryptAESCompat(data);
else return decryptAES(data);
}
return null;
}
/**
*
* @param text
* @return base64 encoded encrypted data
* @throws InvalidKeyException
* @throws NoSuchAlgorithmException
* @throws NoSuchPaddingException
* @throws IOException
* @throws IllegalBlockSizeException
* @throws InvalidAlgorithmParameterException
* @throws NoSuchProviderException
* @throws BadPaddingException
*/
String encrypt(String text) throws InvalidKeyException, NoSuchAlgorithmException, NoSuchPaddingException, IOException, IllegalBlockSizeException, InvalidAlgorithmParameterException, NoSuchProviderException, BadPaddingException, KeyStoreException, UnrecoverableEntryException {
if (text != null && text.length() > 0) {
EncryptedData encrypted = tryEncrypt(text.getBytes(DEFAULT_CHARSET));
return encodeEncryptedData(encrypted);
}
return null;
}
/**
*
* @param text base64 encoded encrypted data
* @return
* @throws InvalidKeyException
* @throws NoSuchAlgorithmException
* @throws NoSuchPaddingException
* @throws IOException
* @throws IllegalBlockSizeException
* @throws InvalidAlgorithmParameterException
* @throws NoSuchProviderException
* @throws BadPaddingException
*/
String decrypt(String text) throws IOException, NoSuchPaddingException, InvalidKeyException, NoSuchAlgorithmException, IllegalBlockSizeException, BadPaddingException, InvalidMacException, NoSuchProviderException, InvalidAlgorithmParameterException, KeyStoreException, UnrecoverableEntryException {
if (text != null && text.length() > 0) {
EncryptedData encryptedData = decodeEncryptedText(text);
byte[] decrypted = tryDecrypt(encryptedData);
return new String(decrypted, 0, decrypted.length, DEFAULT_CHARSET);
}
return null;
}
public static String getHashed(String text) throws NoSuchAlgorithmException, UnsupportedEncodingException {
final MessageDigest digest = MessageDigest.getInstance("SHA-256");
byte[] result = digest.digest(text.getBytes(DEFAULT_CHARSET));
return toHex(result);
}
static String toHex(byte[] data) {
StringBuilder sb = new StringBuilder();
for (byte b : data) {
sb.append(String.format("%02X", b));
}
return sb.toString();
}
public static String base64Encode(byte[] data) {
return Base64.encodeToString(data, Base64.NO_WRAP);
}
public static byte[] base64Decode(String text) {
return Base64.decode(text, Base64.NO_WRAP);
}
String encodeEncryptedData(EncryptedData data) {
if (data.mac != null) {
return base64Encode(data.IV) + DELIMITER + base64Encode(data.encryptedData) + DELIMITER + base64Encode(data.mac);
} else {
return base64Encode(data.IV) + DELIMITER + base64Encode(data.encryptedData);
}
}
EncryptedData decodeEncryptedText(String text) {
EncryptedData result = new EncryptedData();
String[] parts = text.split(DELIMITER);
result.IV = base64Decode(parts[0]);
result.encryptedData = base64Decode(parts[1]);
if (parts.length > 2) {
result.mac = base64Decode(parts[2]);
}
return result;
}
void loadKeyStore() throws KeyStoreException, CertificateException, NoSuchAlgorithmException, IOException {
mStore = KeyStore.getInstance(KEYSTORE_PROVIDER);
mStore.load(null);
}
byte[] getIV() throws UnsupportedEncodingException {
byte[] iv;
if (!isCompatMode) {
iv = new byte[12];
} else {
iv = new byte[16];
}
SecureRandom rng = new SecureRandom();
rng.nextBytes(iv);
return iv;
}
/**
*
* @param bytes
* @param IV
* @return IV & Encrypted data
* @throws NoSuchPaddingException
* @throws NoSuchAlgorithmException
* @throws InvalidAlgorithmParameterException
* @throws InvalidKeyException
* @throws BadPaddingException
* @throws IllegalBlockSizeException
* @throws UnsupportedEncodingException
*/
@TargetApi(Build.VERSION_CODES.KITKAT)
EncryptedData encryptAES(byte[] bytes, byte[] IV) throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidAlgorithmParameterException, InvalidKeyException, BadPaddingException, IllegalBlockSizeException, UnsupportedEncodingException {
Cipher cipher = Cipher.getInstance(AES_CIPHER);
cipher.init(Cipher.ENCRYPT_MODE, aesKey, new GCMParameterSpec(GCM_TAG_LENGTH, IV));
EncryptedData result = new EncryptedData();
result.IV = cipher.getIV();
result.encryptedData = cipher.doFinal(bytes);
return result;
}
/**
*
* @param encryptedData - IV & Encrypted data
* @return decrypted data
* @throws NoSuchPaddingException
* @throws NoSuchAlgorithmException
* @throws InvalidAlgorithmParameterException
* @throws InvalidKeyException
* @throws BadPaddingException
* @throws IllegalBlockSizeException
* @throws UnsupportedEncodingException
*/
@TargetApi(Build.VERSION_CODES.KITKAT)
byte[] decryptAES(EncryptedData encryptedData) throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidAlgorithmParameterException, InvalidKeyException, BadPaddingException, IllegalBlockSizeException, UnsupportedEncodingException {
Cipher cipher = Cipher.getInstance(AES_CIPHER);
cipher.init(Cipher.DECRYPT_MODE, aesKey, new GCMParameterSpec(GCM_TAG_LENGTH, encryptedData.IV));
return cipher.doFinal(encryptedData.encryptedData);
}
/**
*
* @param bytes
* @param IV
* @return IV, Encrypted Data & Mac
* @throws NoSuchPaddingException
* @throws NoSuchAlgorithmException
* @throws NoSuchProviderException
* @throws InvalidKeyException
* @throws BadPaddingException
* @throws IllegalBlockSizeException
* @throws UnsupportedEncodingException
* @throws InvalidAlgorithmParameterException
*/
EncryptedData encryptAESCompat(byte[] bytes, byte[] IV) throws NoSuchPaddingException, NoSuchAlgorithmException, NoSuchProviderException, InvalidKeyException, BadPaddingException, IllegalBlockSizeException, UnsupportedEncodingException, InvalidAlgorithmParameterException {
Cipher c = Cipher.getInstance(AES_CIPHER_COMPAT, BOUNCY_CASTLE_PROVIDER);
c.init(Cipher.ENCRYPT_MODE, aesKey, new IvParameterSpec(IV));
EncryptedData result = new EncryptedData();
result.IV = c.getIV();
result.encryptedData = c.doFinal(bytes);
result.mac = computeMac(result.getDataForMacComputation());
return result;
}
byte[] decryptAESCompat(EncryptedData encryptedData) throws UnsupportedEncodingException, NoSuchAlgorithmException, InvalidKeyException, NoSuchProviderException, NoSuchPaddingException, InvalidAlgorithmParameterException, BadPaddingException, IllegalBlockSizeException, InvalidMacException {
if (verifyMac(encryptedData.mac, encryptedData.getDataForMacComputation())) {
Cipher c = Cipher.getInstance(AES_CIPHER_COMPAT, BOUNCY_CASTLE_PROVIDER);
c.init(Cipher.DECRYPT_MODE, aesKey, new IvParameterSpec(encryptedData.IV));
return c.doFinal(encryptedData.encryptedData);
} else throw new InvalidMacException();
}
void loadKey(SharedPreferences prefStore) throws KeyStoreException, UnrecoverableEntryException, NoSuchAlgorithmException, NoSuchPaddingException, NoSuchProviderException, InvalidKeyException, IOException {
if (!isCompatMode) {
if (mStore.containsAlias(AES_KEY_ALIAS) && mStore.entryInstanceOf(AES_KEY_ALIAS, KeyStore.SecretKeyEntry.class)) {
KeyStore.SecretKeyEntry entry = (KeyStore.SecretKeyEntry) mStore.getEntry(AES_KEY_ALIAS, null);
aesKey = entry.getSecretKey();
}
} else {
aesKey = getFallbackAESKey(prefStore);
macKey = getMacKey(prefStore);
}
}
void generateKey(Context context, SharedPreferences prefStore) throws KeyStoreException, NoSuchProviderException, NoSuchAlgorithmException, InvalidAlgorithmParameterException, UnrecoverableEntryException, NoSuchPaddingException, InvalidKeyException, IOException {
if (!isCompatMode) {
generateAESKey();
} else {
generateRSAKeys(context);
loadRSAKeys();
generateFallbackAESKey(prefStore);
generateMacKey(prefStore);
}
}
@TargetApi(Build.VERSION_CODES.M)
boolean generateAESKey() throws KeyStoreException, NoSuchProviderException, NoSuchAlgorithmException, InvalidAlgorithmParameterException {
Calendar start = Calendar.getInstance();
Calendar end = Calendar.getInstance();
end.add(Calendar.YEAR, 25);
if (!mStore.containsAlias(AES_KEY_ALIAS)) {
KeyGenerator keyGen = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES, KEYSTORE_PROVIDER);
KeyGenParameterSpec spec = new KeyGenParameterSpec.Builder(AES_KEY_ALIAS, KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT)
.setCertificateSubject(new X500Principal("CN = Secured Preference Store, O = Devliving Online"))
.setCertificateSerialNumber(BigInteger.ONE)
.setKeySize(AES_BIT_LENGTH)
.setKeyValidityEnd(end.getTime())
.setKeyValidityStart(start.getTime())
.setBlockModes(KeyProperties.BLOCK_MODE_GCM)
.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
.setRandomizedEncryptionRequired(false) //TODO: set to true and let the Cipher generate a secured IV
.build();
keyGen.init(spec);
keyGen.generateKey();
return true;
}
return false;
}
boolean generateFallbackAESKey(SharedPreferences prefStore) throws IOException, NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, KeyStoreException, NoSuchProviderException, UnrecoverableEntryException {
String key = getHashed(AES_KEY_ALIAS);
if (!prefStore.contains(key)) {
KeyGenerator keyGen = KeyGenerator.getInstance(KEY_ALGORITHM_AES);
keyGen.init(AES_BIT_LENGTH);
SecretKey sKey = keyGen.generateKey();
byte[] encryptedData = RSAEncrypt(sKey.getEncoded());
String AESKey = base64Encode(encryptedData);
boolean result = prefStore.edit().putString(key, AESKey).commit();
String isCompatKey = getHashed(IS_COMPAT_MODE_KEY_ALIAS);
prefStore.edit().putBoolean(isCompatKey, true).apply();
return result;
}
return false;
}
boolean generateMacKey(SharedPreferences prefStore) throws NoSuchPaddingException, InvalidKeyException, NoSuchAlgorithmException, KeyStoreException, NoSuchProviderException, UnrecoverableEntryException, IOException {
String key = getHashed(MAC_KEY_ALIAS);
if (!prefStore.contains(key)) {
byte[] randomBytes = new byte[MAC_BIT_LENGTH / 8];
SecureRandom rng = new SecureRandom();
rng.nextBytes(randomBytes);
byte[] encryptedKey = RSAEncrypt(randomBytes);
String macKey = base64Encode(encryptedKey);
return prefStore.edit().putString(key, macKey).commit();
}
return false;
}
SecretKey getFallbackAESKey(SharedPreferences prefStore) throws IOException, NoSuchAlgorithmException, InvalidKeyException, NoSuchProviderException, NoSuchPaddingException {
String key = getHashed(AES_KEY_ALIAS);
String base64Value = prefStore.getString(key, null);
if (base64Value != null) {
byte[] encryptedData = base64Decode(base64Value);
byte[] keyData = RSADecrypt(encryptedData);
return new SecretKeySpec(keyData, "AES");
}
return null;
}
SecretKey getMacKey(SharedPreferences prefStore) throws IOException, NoSuchAlgorithmException, InvalidKeyException, NoSuchProviderException, NoSuchPaddingException {
String key = getHashed(MAC_KEY_ALIAS);
String base64 = prefStore.getString(key, null);
if (base64 != null) {
byte[] encryptedKey = base64Decode(base64);
byte[] keyData = RSADecrypt(encryptedKey);
return new SecretKeySpec(keyData, MAC_CIPHER);
}
return null;
}
void loadRSAKeys() throws KeyStoreException, UnrecoverableEntryException, NoSuchAlgorithmException {
if (mStore.containsAlias(RSA_KEY_ALIAS) && mStore.entryInstanceOf(RSA_KEY_ALIAS, KeyStore.PrivateKeyEntry.class)) {
KeyStore.PrivateKeyEntry entry = (KeyStore.PrivateKeyEntry) mStore.getEntry(RSA_KEY_ALIAS, null);
publicKey = (RSAPublicKey) entry.getCertificate().getPublicKey();
privateKey = (RSAPrivateKey) entry.getPrivateKey();
}
}
@SuppressWarnings("WrongConstant")
void generateRSAKeys(Context context) throws NoSuchProviderException, NoSuchAlgorithmException, InvalidAlgorithmParameterException, KeyStoreException {
if (!mStore.containsAlias(RSA_KEY_ALIAS)) {
Calendar start = Calendar.getInstance();
Calendar end = Calendar.getInstance();
end.add(Calendar.YEAR, 25);
KeyPairGenerator keyGen = KeyPairGenerator.getInstance(KEY_ALGORITHM_RSA, KEYSTORE_PROVIDER);
KeyPairGeneratorSpec spec;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
spec = new KeyPairGeneratorSpec.Builder(context)
.setAlias(RSA_KEY_ALIAS)
.setKeySize(RSA_BIT_LENGTH)
.setKeyType(KEY_ALGORITHM_RSA)
.setEndDate(end.getTime())
.setStartDate(start.getTime())
.setSerialNumber(BigInteger.ONE)
.setSubject(new X500Principal("CN = Secured Preference Store, O = Devliving Online"))
.build();
} else {
spec = new KeyPairGeneratorSpec.Builder(context)
.setAlias(RSA_KEY_ALIAS)
.setEndDate(end.getTime())
.setStartDate(start.getTime())
.setSerialNumber(BigInteger.ONE)
.setSubject(new X500Principal("CN = Secured Preference Store, O = Devliving Online"))
.build();
}
keyGen.initialize(spec);
keyGen.generateKeyPair();
}
}
byte[] computeMac(byte[] data) throws NoSuchAlgorithmException, InvalidKeyException {
Mac HmacSha256 = Mac.getInstance(MAC_CIPHER);
HmacSha256.init(macKey);
return HmacSha256.doFinal(data);
}
boolean verifyMac(byte[] mac, byte[] data) throws InvalidKeyException, NoSuchAlgorithmException {
if (mac != null && data != null) {
byte[] actualMac = computeMac(data);
if (actualMac.length != mac.length) {
return false;
}
int result = 0;
for (int i = 0; i < actualMac.length; i++) {
result |= actualMac[i] ^ mac[i];
}
return result == 0;
}
return false;
}
byte[] RSAEncrypt(byte[] bytes) throws KeyStoreException, UnrecoverableEntryException, NoSuchAlgorithmException, NoSuchProviderException, NoSuchPaddingException, InvalidKeyException, IOException {
Cipher cipher = Cipher.getInstance(RSA_CIPHER, SSL_PROVIDER);
cipher.init(Cipher.ENCRYPT_MODE, publicKey);
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
CipherOutputStream cipherOutputStream = new CipherOutputStream(outputStream, cipher);
cipherOutputStream.write(bytes);
cipherOutputStream.close();
return outputStream.toByteArray();
}
byte[] RSADecrypt(byte[] bytes) throws NoSuchPaddingException, NoSuchAlgorithmException, NoSuchProviderException, InvalidKeyException, IOException {
Cipher cipher = Cipher.getInstance(RSA_CIPHER, SSL_PROVIDER);
cipher.init(Cipher.DECRYPT_MODE, privateKey);
CipherInputStream cipherInputStream = new CipherInputStream(new ByteArrayInputStream(bytes), cipher);
ArrayList<Byte> values = new ArrayList<>();
int nextByte;
while ((nextByte = cipherInputStream.read()) != -1) {
values.add((byte) nextByte);
}
byte[] dbytes = new byte[values.size()];
for (int i = 0; i < dbytes.length; i++) {
dbytes[i] = values.get(i);
}
cipherInputStream.close();
return dbytes;
}
public static class EncryptedData {
byte[] IV;
byte[] encryptedData;
byte[] mac;
public EncryptedData() {
IV = null;
encryptedData = null;
mac = null;
}
public EncryptedData(byte[] IV, byte[] encryptedData, byte[] mac) {
this.IV = IV;
this.encryptedData = encryptedData;
this.mac = mac;
}
public byte[] getIV() {
return IV;
}
public void setIV(byte[] IV) {
this.IV = IV;
}
public byte[] getEncryptedData() {
return encryptedData;
}
public void setEncryptedData(byte[] encryptedData) {
this.encryptedData = encryptedData;
}
public byte[] getMac() {
return mac;
}
public void setMac(byte[] mac) {
this.mac = mac;
}
/**
* @return IV + CIPHER
*/
byte[] getDataForMacComputation() {
byte[] combinedData = new byte[IV.length + encryptedData.length];
System.arraycopy(IV, 0, combinedData, 0, IV.length);
System.arraycopy(encryptedData, 0, combinedData, IV.length, encryptedData.length);
return combinedData;
}
}
public class InvalidMacException extends GeneralSecurityException {
public InvalidMacException() {
super("Invalid Mac, failed to verify integrity.");
}
}
}