I want to protect some Strings in my Android application, it contain information that should not be viewed. The best idea I've had so far is to encrypt these strings using an AES algorithm or something and put the password in a Google Cloud Storage file that can only be viewed with authentication (by Firebase Auth), so in theory the application always accesses that file when need. This is a good idea?
I have already solved my question, I have these two methods that work very well:
public static String encrypt(String message, String key) {
String cipherText = null;
try {
Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(key.getBytes(), "AES"));
byte[] bytes = cipher.doFinal(message.getBytes("UTF-8"));
cipherText = Base64.encodeToString(bytes, Base64.DEFAULT);
} catch(Exception ex) {
ex.printStackTrace();
}
return cipherText;
}
public static String decrypt(String encoded, String key) {
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(key.getBytes(), "AES"));
decryptString = new String(cipher.doFinal(bytes), "UTF-8");
} catch(Exception ex) {
ex.printStackTrace();
}
return decryptString;
}
After the encrypt method encrypts the message in AES, it uses Base64 to make the byte[] into a readable String that can be stored in a strings.xml file or Java Class, and the decrypt method does the inverse. And my application only pick up the key online via Firebase Storage.
Now, if someone tries to reverse engineer my code, the only thing they can see is:
<string name="code_1">nuD559T1j8VSqjidiF3Yag==</string>
<string name="code_2">+4MTk9TaJJAJEV6D07K++Q==</string>
<string name="code_3">4GlPuHyAGhd48bjuSvcvQQ==</string>
<string name="code_4">yQnq3/tEIxJe67bhBuzoHw==</string>
<string name="code_5">p/sDptvxdi0ynsuybvfI+A==</string>
<string name="code_6">dE4aV0wG0aINh/dw0wwevQ==</string>
<string name="code_7">vxNaPmHvnbGsydOYXSOSUA==</string>
<string name="code_8">fClfcC/Eweh9tA8xz6ktGw==</string>
<string name="code_9">FxzAZpH+SJt5Lv6VFU/BEQ==</string>
<string name="code_10">qh3jFGHOGMzt50WOwTG4H4Y2Vbr7TzO433tbB3s6P34=</string>
<string name="code_11">u7kZjN/bxkMEqDws4nvbnQ==</string>
<string name="code_12">Ccf2u8FJGJ1lsiR7aX5OSw==</string>
<string name="code_13">E4XsWDHO28pOhV4ter/f2A==</string>
<string name="code_14">kgPr+Yz3t4S+Y5zQXjkvJA==</string>
<string name="code_15">19CpjUzKOw1fL8bZH8xkMg==</string>
You can refer about NDK :
Example:
#include <string.h>
#include <jni.h>
jstring Java_com_riis_sqlndk_MainActivity_invokeNativeFunction(JNIEnv* env,
jobject javaThis) {
return (*env)->NewStringUTF(env, "pass123");
}
And use in Android:
public class MainActivity extends Activity {
static {
System.loadLibrary("sqlndk"); // line 11
}
private native String invokeNativeFunction(); // line 14
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
String key = invokeNativeFunction(); // line 21
}
}
I hope it can help your problem!
It seems to be very good idea as long as you don't need this strings in offline mode. Otherwise use Keystore.
More information: https://developer.android.com/training/articles/keystore.html
Example: https://www.androidauthority.com/use-android-keystore-store-passwords-sensitive-information-623779/
In addition When you don't need this values in offline mode - You can store its in Keystore and store remotely only private key not all strings.
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.
I am trying to convert my private key string to a PrivateKey object in my Android application. I have read a lot of posts on StackOverflow about this topic, but I just can solve my issue.
I am using the following function to convert my key:
String privateKey = "MIGkAgEBBDCAHpFQ62QnGCEvYh/pE9QmR1C9aLcDItRbslbmhen/h1tt8AyMhskeenT+rAyyPhGgBwYFK4EEACKhZANiAAQLW5ZJePZzMIPAxMtZXkEWbDF0zo9f2n4+T1h/2sh/fviblc/VTyrv10GEtIi5qiOy85Pf1RRw8lE5IPUWpgu553SteKigiKLUPeNpbqmYZUkWGh3MLfVzLmx85ii2vMU=";
#Nullable
private static PrivateKey getKey(String key) {
try {
byte[] byteKey = Base64.decode(key.getBytes(), Base64.DEFAULT);
KeyFactory keyFactory = KeyFactory.getInstance("EC");
return keyFactory.generatePrivate(new PKCS8EncodedKeySpec(byteKey));
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
According to ASN.1 JavaScript decoder, I do have a valid private key.
Nonetheless, getKey() always fails with the following exception:
java.security.spec.InvalidKeySpecException: java.lang.RuntimeException: error:0c0000be:ASN.1 encoding routines:OPENSSL_internal:WRONG_TAG
I cannot make sense of this error message. Even after a lot of googling. Does anybody know, what I am doing wrong? Am I using the wrong key spec (though many of the answers here suggest the use of PKCS8EncodedKeySpec().
The private key you see in the code above was generated by jwt.io using an ES384 algorithm.
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 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.
I'm using a custom subclass of SharedPreferences to encrypt my saved settings in the app, similar to what's being done in the second response here: What is the most appropriate way to store user settings in Android application
The number of preferences I have to save is growing. Before I was just using a custom view to update these preferences but that is going to become cumbersome and I want to use PreferenceActivity or PreferenceFragment instead. Problem is, it does not seem that there is a way to have either of those classes access my data using my subclass, meaning that the data it pulls from the default preferences file is going to be gibberish as it wasn't decrypted.
I've found that some people have created custom implementations of Preference that encrypt the data there, but I'd prefer not to do that as the data is already being encrypted/decrypted in my SharedPreferences subclass and I'd like to keep it that way. I've also been looking over the source code of PreferenceActivity and PreferenceManager and I'm not sure the best way to approach this.
Has anyone else had any luck accomplishing something like this and have any suggestions as to where I might start?
I think by keeping your encryption in the SharedPrefs subclass that you already have, you limit the modularity and the separation of concerns.
So I would suggest reconsidering sub-classing the preference classes themselves (such as CheckBoxPreference) and perform your calculation there.
Ideally you could also use some type of composition or a static utility so that while you might have to subclass each type of preference you use, you can use a single place to perform the encryption/decryption calculations. This would also allow you more flexibility in the future if you need to encrypt or decrypt some other data or if the API changes.
For sub-classing maybe you could do this:
So for example:
class ListPreferenceCrypt extends ListPreference
{
ListPreferenceCrypt (Context context, AttributeSet attrs) {
super ( context, attrs );
}
ListPreferenceCrypt (Context context) {
super ( context );
}
#Override
public void setValue( String value )
{
//encrypt value
String encryptedVal = MyCryptUtil.encrypt(value);
super.setValue ( encryptedVal );
}
#Override
public String getValue( String key )
{
//decrypt value
String decryptedValue = MyCryptUtil.decrypt(super.getValue ( key ));
return decryptedValue;
}
}
NOTE the above is psuedo-code, there would be different methods to override
And your XML might look like this:
<PreferenceScreen
xmlns:android="http://schemas.android.com/apk/res/android">
<PreferenceCategory
android:title="#string/inline_preferences">
<com.example.myprefs.ListPreferenceCrypt
android:key="listcrypt_preference"
android:title="#string/title_listcrypt_preference"
android:summary="#string/summary_listcrypt_preference" />
</PreferenceCategory>
</PreferenceScreen>
EDIT
Caveats/Decompiling
As I thought about this more, I realized one of the caveats is that this method is not particularly difficult to bypass when decompiling an APK. This does give the full class names of overriden classes in the layouts (though that can be avoided by not using XML)
However, I don't think this is significantly less secure than sub-classing SharedPreferences. That too, is susceptible to decompiling. Ultimately, if you want stronger security, you should consider alternative storage methods. Perhaps OAuth or the AccountManager as suggested in your linked post.
How about this:
Store a byte[16] in a .SO. If you do not use a .SO then make one just for that purpose.
Use that byte array to crypt a new byte[16] then Base64 encode the result. Hardcode that in your class file.
Now that you've setup the keys let me explain:
Yes, potentially one could peek into the .SO and find the byte array ergo your key. But with the cyphered key2 being base64 encoded, he would need to decode it and reverse the encryption with the said key to extract key2 bytes. So far this only involves dissassembling the app.
When you want to store encrypted data, first do a AES pass with key1, then a AES/CBC/Padding5 pass with Key2 and an IV*
You can safely Base64 encode the IV and save it like that in your /data/data folder if you'd like to change the IV every time a new password is stored.
With these two steps disassembling the app is no longer the only thing required, as it's now required to also take control of your runtime to get to the crypted data. Which you have to say is pretty sufficient for a stored password.
Then you could simply store that into SharedPreferences :) That way if your SharedPreferences get compromised, the data is still locked away. I don't think subclassing it is really the right approach but since you already wrote your class - oh well.
Here's some code to further illustrate what I mean
//use to encrypt key
public static byte[] encryptA(byte[] value) throws GeneralSecurityException, IOException
{
SecretKeySpec sks = getSecretKeySpec(true);
System.err.println("encrypt():\t" + sks.toString());
Cipher cipher = Cipher.getInstance("AES");
cipher.init(Cipher.ENCRYPT_MODE, sks, cipher.getParameters());
byte[] encrypted = cipher.doFinal(value);
return encrypted;
}
//use to encrypt data
public static byte[] encrypt2(byte[] value) throws GeneralSecurityException, IOException
{
SecretKeySpec key1 = getSecretKeySpec(true);
System.err.println("encrypt():\t" + key1.toString());
Cipher cipher = Cipher.getInstance("AES");
cipher.init(Cipher.ENCRYPT_MODE, key1, cipher.getParameters());
byte[] encrypted = cipher.doFinal(value);
SecretKeySpec key2 = getSecretKeySpec(false);
System.err.println("encrypt():\t" + key2.toString());
cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, key2, new IvParameterSpec(getIV()));
byte[] encrypted2 = cipher.doFinal(encrypted);
return encrypted2;//Base64Coder.encode(encrypted2);
}
//use to decrypt data
public static byte[] decrypt2(byte[] message, boolean A) throws GeneralSecurityException, IOException
{
SecretKeySpec key1 = getSecretKeySpec(false);
System.err.println("decrypt():\t" + key1.toString());
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.DECRYPT_MODE, key1, new IvParameterSpec(getIV()));
byte[] decrypted = cipher.doFinal(message);
SecretKeySpec key2 = getSecretKeySpec(true);
System.err.println("decrypt():\t" + key2.toString());
cipher = Cipher.getInstance("AES");
cipher.init(Cipher.DECRYPT_MODE, key2);
byte[] decrypted2 = cipher.doFinal(decrypted);
return decrypted2;
}
//use to decrypt key
public static byte[] decryptKey(String message, byte[] key) throws GeneralSecurityException
{
SecretKeySpec sks = new SecretKeySpec(key, ALGORITHM);
System.err.println("decryptKey()");
Cipher cipher = Cipher.getInstance("AES");
cipher.init(Cipher.DECRYPT_MODE, sks);
byte[] decrypted = cipher.doFinal(Base64Coder.decode(message));
return decrypted;
}
//method for fetching keys
private static SecretKeySpec getSecretKeySpec(boolean fromSO) throws NoSuchAlgorithmException, IOException, GeneralSecurityException
{
return new SecretKeySpec(fromSO ? getKeyBytesFromSO() : getKeyBytesFromAssets(), "AES");
}
What do you think?
I realize it might be off topic since you're asking about using your own SharedPreferences but I'm giving you a working solution to the problem of storing sensitive data :)