Currently we are encrypting our String as:
import android.util.Base64;
import java.security.Key;
import java.util.Arrays;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
public class Cipher {
private static final String TEXT_ENCODING_TYPE = "UTF-8";
private static final String ALGO = "AES";
private static final String TYPE = ALGO + "/CBC/PKCS5Padding";
private static final String KEY = "MY_STATIC_KEY";
private static final String IV = "MY_STATIC_VECTOR";
private static final String IV_PADDING = " ";
public static String encrypt(String data) {
try {
if (!data.isEmpty()) {
javax.crypto.Cipher cipher = javax.crypto.Cipher.getInstance(TYPE);
cipher.init(javax.crypto.Cipher.ENCRYPT_MODE, getKey(), getIV());
return Base64.encodeToString(cipher.doFinal((IV_PADDING + data).getBytes()), Base64.NO_WRAP).trim();
} else {
return data;
}
} catch (Exception e) {
return data;
}
}
return new String(cipher.doFinal(data)).trim();
} else {
return encryptedData;
}
} catch (Exception e) {
LogUtils.log(e, Cipher.class);
return encryptedData;
}
}
private static Key getKey() throws Exception {
return new SecretKeySpec(KEY.getBytes(TEXT_ENCODING_TYPE), ALGO);
}
private static IvParameterSpec getIV() throws Exception {
return new IvParameterSpec(IV.getBytes(TEXT_ENCODING_TYPE));
}
private static IvParameterSpec getIV(byte[] iv) {
return new IvParameterSpec(iv);
}
}
But we have received Security alert from Google Play Console:
Your app contains unsafe cryptographic encryption patterns.
And then we were redirected to this link: Remediation for Unsafe Cryptographic Encryption.
However, this link recommends to use Jetpack Security package in which I couldn't find how to encrypt string and generate safe KEY and IV for each of our Server request.
All the examples and links I have visited points to save your sensitive data to encrypted files and SharedPreferences.
So, what should I do now? Do I have to find secure key generation mechanism that can also be decoded on Server side (Java) and save that key in Secured SharedPreferences? Jetpack Security package is still in Beta mode.
Open for more clarification.
I would take your signature:
public String encrypt(String data) and keep it that way but choose an approach:
Is the data small enough that using Secure Shared Preferences enough to store something? (not the best idea due to the issues with Shared Pref.)
Can you keep the data in a File (temporary) and then return that?
You can do either, the difference shouldn't be too big since you're likely going to have some form of class YourCryptoImplementation where you're going to perform all this...
Using Shared Preferences
You can have a couple of methods (sorry, In Kotlin because it's shorter and I've already used similar code):
private fun getEncryptedPreferences() =
EncryptedSharedPreferences.create("your_shared_preferences", advancedKeyAlias,
context, EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM)
You're going to wonder what advancedKeyAlias is. That's just a private var advancedKeyAlias: String but the actual value... will be something like:
init {
val advancedSpec = KeyGenParameterSpec.Builder("your_master_key_name",
KeyProperties.PURPOSE_ENCRYPT or KeyProperties.PURPOSE_DECRYPT).apply {
setBlockModes(KeyProperties.BLOCK_MODE_GCM)
setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
setKeySize(256)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
val hasStrongBox = context.packageManager.hasSystemFeature(PackageManager.FEATURE_STRONGBOX_KEYSTORE)
if (hasStrongBox)
setIsStrongBoxBacked(true)
}
}.build()
advancedKeyAlias = MasterKeys.getOrCreate(advancedSpec)
}
So, now in your init() of this class, you ensure you have your key alias created.
You can use it to encrypt or decrypt.
Back to our SharedPref. example:
Let's say you want to store a string, you can offer:
fun encryptToSharedPref(String data) {
getEncryptedPrefs().edit().putString("the_key_you_want_to_use", data).apply()
}
And to "read" the value:
fun getValueFromSharedPreferencesWith(key: String) = getEncryptedPreferences().getString(key, null)
That would work, if the strings fit in SharedPref and if you don't care about other Shared Preferences issues...
What about FILES?
Not a huge difference, but assuming you're in the same class (that is, the advancedKeyAlias exists).
You're going to have a getEncryptedFile helper method:
private fun getEncryptedFile(file: File) = EncryptedFile.Builder(file, context, advancedKeyAlias,
EncryptedFile.FileEncryptionScheme.AES256_GCM_HKDF_4KB).build()
And you can decrypt a file like:
fun decryptFile(file: File): FileInputStream {
return getEncryptedFile(file).openFileInput()
}
Very simple, and you can obviously use it like
val rawData = yourCryptoClassAbove.decryptFile(File("path/to/file").readBytes()
val decryptedString = String(rawData)
Now to encrypt a file, you can use a FileOutputStream, that is a stream that outputs the bytes directly to a file... in our case, an encrypted file.
E.g.:
fun encryptFile(bytes: ByteArray, file: File) {
var outputStream: FileOutputStream? = null
try {
outputStream = getEncryptedFile(file).openFileOutput().apply {
write(bytes)
}
} catch (exception: IOException) {
Log.e(TAG, "output file already exists, please use a new file", exception)
} finally {
outputStream?.close()
}
}
You receive a ByteArray though, but that's not hard to obtain if you have a string...
var dataToEncrypt = ... //any "String"
yourCryptoClassAbove.encryptFile(File("path/to/file", dataToEncrypt.toByteArray())
And that's basically most of what you'd likely need. Obviously, you can have any method to generate your "advancedKey".
I don't know if this would help you, but it would certainly abstract the complexity of encrypting away from your code using it.
Disclaimer: some of these is code I've used, some is just "pseudo code" that gives you an Idea what I had in mind.
Related
I have recently started to receive a Play Store warning like this: “Your app contains unsafe cryptographic encryption patterns” and in order try to get rid of it (and having no idea what does exactly means) I created a "complex" structure regarding encription in my app as follows:
1) This next method (located in a class different than Cryptography one just in case) stores cipher preferences in two places, the "real" values in SharedPreferences, and the "default" values in application class setter called AppSettings ("default" means the default value that is required in order to get a SharedPreference if it fails to find one).
public static void setCryptPreferences()
{
Context context = AppSettings.getContext();
AppSettings appSettings = AppSettings.getInstance();
String[] defCryptoValues = new String[]{ "AES", "AES/CBC/PKCS5Padding", "UTF-8"};
appSettings.setDefCryptValues(defCryptoValues);
AWUtils.setSharedPreference(context, "CRYPT_ALGORITHM", "AES");
AWUtils.setSharedPreference(context, "PADDING", "AES/CBC/PKCS5Padding");
AWUtils.setSharedPreference(context, "CHAR_ENCODING", "UTF-8");
}
And I set them with this function on app startup.
2) Then, whenever I need to encrypt, I use them with this next method:
public static String cipherText(String plainText)
{
AppSettings appSettings = AppSettings.getInstance();
Cryptography crypto = new Cryptography();
String[] defCryptoValues = appSettings.getDefCryptValues();
String[] cryptoParams = Cryptography.getCryptoParams(defCryptoValues);
return crypto.encrypt(plainText, cryptoParams);
}
And finally this is the real crypt method:
private String encrypt(String text, String[] cryptedParams)
{
checkKeys();
if (text == null) return null;
String retVal = null;
try {
final SecretKeySpec key = new SecretKeySpec(mCryptKey.getBytes(cryptedParams[2]), cryptedParams[0]);
final IvParameterSpec iv = new IvParameterSpec(mCryptIV.getBytes(cryptedParams[2]));
final Cipher cipher = Cipher.getInstance(cryptedParams[1]);
cipher.init(Cipher.ENCRYPT_MODE, key, iv);
final byte[] encrypted = cipher.doFinal(text.getBytes(cryptedParams[2]));
retVal = new String(encodeHex(encrypted));
} catch (Exception e) {
e.printStackTrace();
}
return retVal;
}
I don't understand very clearly the warning, and I thought I is related of having the encrypt params
"CRYPT_ALGORITHM", "AES"
"PADDING", "AES/CBC/PKCS5Padding"
"CHAR_ENCODING", "UTF-8"
as plain text in the Cryptography class and so I created all this thinking on "hiding" them and thinking it could help me to get rid of it, but it hasn't. I have recently uploaded a new version of my app and the warning still remains there and I don't know what to do.
All my images are encrypted in Android file system. When I need to show them, I need to decrypt, generate the bitmap and then delete the file. I'm trying to use Picasso to load my images. I created an RequestHandler to decrypt and load image.
RequestHandler accepts two types of result:
1. the bitmap or 2. a stream.
I'm trying to return the stream. That way Picasso can load images using the best practices, prevent out of memory. I created an custom Stream class and override the Dispose() method to delete the decrypted file after use.
The problem is: The stream is not disposing, neither closing, after the image is loaded, and I can't for automatic dispose by GAC (I'm using Xamarin/C#). Any ideas? What can I do?
UPDATE (19/01/17): I found out a small bug in my code and after fixing it, my problem was solved. But here is my custom RequestHandler for future reference... EncryptedFileStream is my custom stream that wraps the original stream and delete the decrypted file on Dispose().
public class EncryptedFilenameRequestHandler : RequestHandler
{
private readonly Context _context;
private readonly ICriptoService _criptoService;
public EncryptedFilenameRequestHandler(Context context, ICriptoService criptoService)
{
if (context == null) throw new ArgumentNullException(nameof(context));
if (criptoService == null) throw new ArgumentNullException(nameof(criptoService));
_context = context;
_criptoService = criptoService;
}
public override bool CanHandleRequest(Request request)
{
var uri = request.Uri;
return string.Compare(uri.Scheme, Constantes.AppSchema, true) == 0 &&
string.Compare(uri.Authority, Constantes.Host, true) == 0 &&
string.Compare(uri.Path, "/loadimagem/filename/encrypted", true) == 0;
}
public override Result Load(Request request, int networkPolicy)
{
string password = request.Uri.GetQueryParameter("p");
string encryptedFilename = request.Uri.GetQueryParameter("f");
string decryptedFilename = System.IO.Path.Combine(AppEnviroment.GetTempDirectory(), Guid.NewGuid().ToString("N"));
if (string.IsNullOrWhiteSpace(encryptedFilename) || !File.Exists(encryptedFilename))
return null;
_criptoService.Decrypt(encryptedFilename, decryptedFilename, password);
//retorna um stream para leitura do arquivo descriptografado
var uri = Android.Net.Uri.FromFile(new Java.IO.File(decryptedFilename));
var stream = new EncryptedFileStream(decryptedFilename, _context.ContentResolver.OpenInputStream(uri));
return new Result(stream, Picasso.LoadedFrom.Disk);
}
}
I found out a small bug in my code and, after fixing it, my problem was solved. The code for EncryptedFilenameRequestHandler posted in the question is working without any problem.
Realm is using AES-256 for encryption and decryption. And, I am trying to use Android KeyStore to generate/store the keys, but as per this page - https://developer.android.com/training/articles/keystore.html#SecurityFeatures, Android supports this only on APIs 23 and above.
Can someone please point me to an example or any other related info on how I can use realm with encryption to support APIs 4.0 and above?
Thanks.
We recently ran into the same problem and decided to simply store the key in private Shared Preferences, because if the phone is not rooted, you will not be able to get it and if it is rooted, then there are some ways to get data even from secure keyStore.
We use next Realm configuration inside Application subclass:
RealmConfiguration config = new RealmConfiguration.Builder()
.deleteRealmIfMigrationNeeded()
.name(DB_NAME)
.encryptionKey(mKeyProvider.getRealmKey())
.build();
And mKeyProvider is our helper class that is used to get the key:
public class SharedPrefsKeyProvider implements KeyProvider {
private static final String REALM_KEY = "chats.realm_key";
SharedPreferences mAppSharedPrefs;
public SharedPrefsKeyProvider(SharedPreferences aAppSharedPrefs) {
mAppSharedPrefs = aAppSharedPrefs;
}
#Override
public byte[] getRealmKey() {
byte[] key;
String savedKey = getStringFromPrefs(REALM_KEY);
if (savedKey.isEmpty()) {
key = generateKey();
String keyString = encodeToString(key);
saveStringToPrefs(keyString);
} else {
key = decodeFromString(savedKey);
}
return key;
}
#Override
public void removeRealmKey() {
mAppSharedPrefs.edit().remove(REALM_KEY).apply();
}
#NonNull
private String getStringFromPrefs(String aKey) {
return mAppSharedPrefs.getString(aKey, "");
}
private void saveStringToPrefs(String aKeyString) {
mAppSharedPrefs.edit().putString(REALM_KEY, aKeyString).apply();
}
private String encodeToString(byte[] aKey) {
Timber.d("Encoding Key: %s", Arrays.toString(aKey));
return Base64.encodeToString(aKey, Base64.DEFAULT);
}
private byte[] decodeFromString(String aSavedKey) {
byte[] decoded = Base64.decode(aSavedKey, Base64.DEFAULT);
Timber.d("Decoded Key: %s", Arrays.toString(decoded));
return decoded;
}
private byte[] generateKey() {
byte[] key = new byte[64];
new SecureRandom().nextBytes(key);
return key;
}
}
A KeyProvider is just a custom interface. An example of KeyProvider can be:
package xxx.com;
interface KeyProvider {
byte[] getRealmKey();
void removeRealmKey();
}
AES 256 encryption is symmetric Encryption, try RSA encryption which is asymmetric. And if you are trying to encrypt sensitive user data to store in preferences or sqlite, i would suggest you try Android keystore system.
The Android Keystore system lets you store cryptographic keys in a container to make it more difficult to extract from the device. Once keys are in the keystore, they can be used for cryptographic operations with the key material remaining non-exportable.
check my sample gist to achieve this encryption and decryption here.
And better part is it works on android 18 and above.
I have successfully created a cloud endpoint model that allows for easy retrieval of information from App Engine. To reduce the roundtrips and provide a faster user experience, I have identified one instance I wish to store to local storage.
Throughout the rest of my app, I am using ObjectInputStream to read and write the objects such as:
FileInputStream fis = context.openFileInput("PRIVFILE");
ObjectInputStream ois = new ObjectInputStream(fis);
AppModelState s = (AppModelState) ois.readObject();
This obviously requires all data members to implement the Serializable interface. The Model class extends GenericJSON and is not "Serializable", as
public final class ModelClass extends GenericJson {}
I could manually create a serializable object that maps to the model; however, that seems very amateur due to the number of attributes.
The other alternative I considered was creating a Serializable Object wrapper that simply has the JSON string as a member and provides a setter/getter accepting the ModelClass as parameters, such as:
class AppModelState implements Serializable {
private String modelClassJSON;
public ModelClass getModelClass() {
// generate a new ModelClass from the JSON
}
public void setModelClass(ModelClass c) {
// extract the JSON for storage
}
.....
}
I feel like there must be a better way and this should have been solved a dozen times but I am not finding any resources. Please provide input.
I'm doing exactly the same as you say in your question.
Since Cloud Endpoints objects are already serialized for transmit over the wire, they are also serializable to be stored locally. As an added bonus, with Android 3.0 or later, you don't even need to import any libraries -- it's already there! For example:
import com.google.api.client.extensions.android.json.AndroidJsonFactory;
import com.google.api.client.json.GenericJson;
import com.google.api.client.json.JsonFactory;
private static final JsonFactory JSON_FACTORY = new AndroidJsonFactory();
public void putObject(String key, Object value) throws Exception {
byte[] outputbytes = null;
if (value instanceof GenericJson) {
outputbytes = JSON_FACTORY.toByteArray(value);
} else {
ByteArrayOutputStream output = new ByteArrayOutputStream();
ObjectOutputStream objectstream = new ObjectOutputStream(output);
objectstream.writeObject(value);
objectstream.close();
outputbytes = output.toByteArray();
}
// persist "outputbytes" ...
}
public <T> T getObject(String key, Class<T> outclass) throws Exception {
// retrieve saved bytes...
byte[] valuebytes = ...
if (valuebytes[0] == '{' && valuebytes[1] == '"' && valuebytes[valuebytes.length-1] == '}') {
// Looks like JSON...
return JSON_FACTORY.fromString(new String(valuebytes, "UTF-8"), outclass);
} else {
ByteArrayInputStream input = new ByteArrayInputStream(valuebytes);
ObjectInputStream objectstream = new ObjectInputStream(input);
Object value = objectstream.readObject();
objectstream.close();
return outclass.cast(value);
}
}
Note that the default AndroidJsonFactory (as of Android v4.3, anyway) is quite slow when serializing long strings. Create a new JacksonFactory instead if you have performance problems. Everything else stays the same.
Update: If you want to serialize a list of GenericJson objects, you just have to create a GenericJson object that includes a list of those objects. For example:
import com.google.api.client.json.GenericJson;
import com.google.api.client.util.Key;
public static class PersistantJson extends GenericJson {
#Key public int one;
#Key public String two;
}
public static class PersistantJsonList extends GenericJson {
#Key public List<PersistantJson> list = new ArrayList<PersistantJson>();
}
You can now add all your PersistantJson (i.e. some class created by "generate cloud endpoint client library") objects to the .list element of a PersistantJsonList variable and then pass that variable to putObject(). Note that this requires all objects in the list to be of the same class so that deserialization knows what the type is (because JSON serialization does not record the type). If you use List<Object> then what is read back is a List<Map<String, Object>> and you have to extract the fields manually.
I think that doing standard Java serialization of classes that will be used with Endpoints doesn't work very well. The problem is that serialization is binary, and HTTP comm is string.
If you were doing the HTTP comm yourself, rather then using endpoints, I think you would have the same problem. In order to send the object you would serialize it (converting an string members to binary) and then you would have to convert the binary back to string.
So, if the amount of data you are using is not too much, it would probably be easiest to store your objects as JSON.
I want to know:
Can we use Context.MODE_PRIVATE in SQLite while Database creating to protect from unwanted Database access.
I am not getting any example on google.
How to use this Context.MODE_PRIVATE in Database.
Please assist me. Provide any link or sample.
IN THIS LINK they are talking about file. so Database is also file.
How can i implement this?
As commonsware mentioned, SQLite databases on internal storage are private by default. But as mentioned by others rooted phone as always access to your file.
Rather you can use any encryption algorithm to save the data in DB which will help you to restrict the readability unless intruder know the encryption algorithm.
You cant set "Context.MODE_PRIVATE" flag in SQLite.
While creating database, following syntax is useful
openOrCreateDatabase(String path, int mode, SQLiteDatabase.CursorFactory factory)
For example,
openOrCreateDatabase("StudentDB",Context.MODE_PRIVATE,null);
See my tutorial on this site.
Option 1: Use SQLcipher.
Option 2: Secure Method Ever No Chance To Hack. It is not perfect, but it is better than nothing.
1) Insert data using this Function:
public static String getEncryptedString(String message) {
String cipherText = null;
try {
Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(("YOUR-SECURE-PASSWORD-KEY").getBytes(), "AES"));
byte[] bytes = cipher.doFinal(message.getBytes());
cipherText = Base64.encodeToString(bytes, Base64.DEFAULT);
} catch(Exception ex) {
cipherText = "Error in encryption";
Log.e(TAG , ex.getMessage());
ex.printStackTrace();
}
return cipherText;
}
2) Get data from the database and pass into this function parameter:
//This function returns output string
public static String getDecryptedString(String encoded) {
String decryptString = null;
try {
byte[] bytes = Base64.decode(encoded, Base64.DEFAULT);
Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
cipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(("YOUR-SECURE-PASSWORD-KEY").getBytes() , "AES"));
decryptString = new String(cipher.doFinal(bytes), "UTF-8");
} catch(Exception ex) {
decryptString = "Error in decryption";
ex.printStackTrace();
}
return decryptString;
}
3) Benefits of these methods:
- Not possible to decrypt without the right Key.
- AES Encryption is a very secure encryption method.
4) Store your AES key in the c++ file.