package com.sromku.simple.storage;
import android.os.Build;
import android.util.Log;
import java.io.FileInputStream;
import java.io.UnsupportedEncodingException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.KeySpec;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.PBEKeySpec;
/**
* Each of the specific storage types (like External storage tool) need its own
* configurations. This configuration class is build and used in the storage
* classes.<br>
* <br>
*
* <b>Examples:</b><br>
* <br>
*
* Default unsecured configuration:<br>
*
* <pre>
* {@code
* SimpleStorageConfiguration configuration = new SimpleStorageConfiguration.Builder()
* .build()
* }
* </pre>
*
* Secured configuration:
*
* <pre>
* {
* @code
* final int CHUNK_SIZE = 16 * 1024;
* final String IVX = "1234567890123456";
* final String SECRET_KEY = "secret1234567890";
*
* SimpleStorageConfiguration configuration = new SimpleStorageConfiguration.Builder().setChuckSize(CHUNK_SIZE).setEncryptContent(IVX, SECRET_KEY).build();
* }
* </pre>
*
* @author Roman Kushnarenko - sromku (sromku@gmail.com)
*
*/
public class SimpleStorageConfiguration {
/**
* The best chunk size: <i>http://stackoverflow.com/a/237495/334522</i>
*/
private int mChunkSize;
private boolean mIsEncrypted;
private byte[] mIvParameter;
private byte[] mSecretKey;
private SimpleStorageConfiguration(Builder builder) {
mChunkSize = builder._chunkSize;
mIsEncrypted = builder._isEncrypted;
mIvParameter = builder._ivParameter;
mSecretKey = builder._secretKey;
}
/**
* Get chunk size. The chuck size is used while reading the file by chunks
* {@link FileInputStream#read(byte[], int, int)}.
*
* @return The chunk size
*/
public int getChuckSize() {
return mChunkSize;
}
/**
* Encrypt the file content.<br>
*
* @see <a
* href="https://en.wikipedia.org/wiki/Block_cipher_modes_of_operation">Block
* cipher mode of operation</a>
*/
public boolean isEncrypted() {
return mIsEncrypted;
}
/**
* Get secret key
*
* @return
*/
public byte[] getSecretKey() {
return mSecretKey;
}
/**
* Get iv parameter
*
* @return
*/
public byte[] getIvParameter() {
return mIvParameter;
}
/**
* Configuration Builder class. <br>
* Following Builder design pattern.
*
* @author sromku
*/
public static class Builder {
private int _chunkSize = 8 * 1024; // 8kbits = 1kbyte;
private boolean _isEncrypted = false;
private byte[] _ivParameter = null;
private byte[] _secretKey = null;
private static final String UTF_8 = "UTF-8";
public Builder() {
}
/**
* Build the configuration for storage.
*
* @return
*/
public SimpleStorageConfiguration build() {
return new SimpleStorageConfiguration(this);
}
/**
* Set chunk size. The chuck size is used while reading the file by
* chunks {@link FileInputStream#read(byte[], int, int)}. The preferable
* value is 1024xN bits. While N is power of 2 (like 1,2,4,8,16,...)<br>
* <br>
*
* The default: <b>8 * 1024</b> = 8192 bits
*
* @param chunkSize
* The chunk size in bits
* @return The {@link Builder}
*/
public Builder setChuckSize(int chunkSize) {
_chunkSize = chunkSize;
return this;
}
/**
* Encrypt and descrypt the file content while writing and reading
* to/from disc.<br>
*
*
* @param ivx
* This is not have to be secret. It used just for better
* randomizing the cipher. You have to use the same IV
* parameter within the same encrypted and written files.
* Means, if you want to have the same content after
* descryption then the same IV must be used.<br>
* <br>
*
* <b>Important: The length must be 16 long</b><br>
*
* <i>About this parameter from wiki:
* https://en.wikipedia.org
* /wiki/Block_cipher_modes_of_operation
* #Initialization_vector_.28IV.29</i><br>
* <br>
* @param secretKey
* Set the secret key for encryption of file content. <br>
* <br>
*
* <b>Important: The length must be 16 long</b> <br>
*
* <i>Uses SHA-256 to generate a hash from your key and trim
* the result to 128 bit (16 bytes)</i><br>
* <br>
* @see <a
* href="https://en.wikipedia.org/wiki/Block_cipher_modes_of_operation">Block
* cipher mode of operation</a>
*
*/
public Builder setEncryptContent(String ivx, String secretKey) {
_isEncrypted = true;
// Set IV parameter
try {
_ivParameter = ivx.getBytes(UTF_8);
}
catch (UnsupportedEncodingException e) {
Log.e("SimpleStorageConfiguration", "UnsupportedEncodingException", e);
}
// Set secret key
try {
/*
* We generate random salt and then use 1000 iterations to
* initialize secret key factory which in-turn generates key.
*/
int iterationCount = 1000; // recommended by PKCS#5
int keyLength = 128;
SecureRandom random = new SecureRandom();
byte[] salt = new byte[16]; // keyLength / 8 = salt length
random.nextBytes(salt);
KeySpec keySpec = new PBEKeySpec(secretKey.toCharArray(), salt, iterationCount, keyLength);
SecretKeyFactory keyFactory = null;
if (Build.VERSION.SDK_INT >= 19) {
// see:
// http://android-developers.blogspot.co.il/2013/12/changes-to-secretkeyfactory-api-in.html
// Use compatibility key factory -- only uses lower 8-bits
// of passphrase chars
keyFactory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1And8bit");
}
else {
// Traditional key factory. Will use lower 8-bits of
// passphrase chars on
// older Android versions (API level 18 and lower) and all
// available bits
// on KitKat and newer (API level 19 and higher).
keyFactory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
}
byte[] keyBytes = keyFactory.generateSecret(keySpec).getEncoded();
_secretKey = keyBytes;
}
catch (InvalidKeySpecException e) {
Log.e("SimpleStorageConfiguration", "InvalidKeySpecException", e);
}
catch (NoSuchAlgorithmException e) {
Log.e("SimpleStorageConfiguration", "NoSuchAlgorithmException", e);
}
return this;
}
}
}