1

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.

enter image description here

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.

Faizan Mubasher
  • 4,427
  • 11
  • 45
  • 81
  • I think Jetpack Security is more oriented towards encrypting files or "securing" the SharedPreferences by offering an encrypted store. For your use-case though, I'm not sure if it's possible out of the box (to use Jetpack Sec.). That being said, I don't think storing the string in an encrypted file is a bad idea, you can always remove it later, use temp storage, and open it again when you want to read it (if you want the encrypted "bytes" you can always read the file normally... would that be an issue in the way you use it? – Martin Marconcini Sep 18 '20 at 10:44
  • Well! I agree that Jetpack is more oriented towards encrypting files but here issue is **KEY** and **IV** are always static, resulting in generating same key every time. It is so confusing though. – Faizan Mubasher Sep 18 '20 at 10:54
  • Take a look at my answer, perhaps it can help you. I'd have to see your problem in detail to see if there's a better way. I am not super experienced with Jetpack security, I just used it as I put in the answer. Good luck! – Martin Marconcini Sep 18 '20 at 11:06
  • I did not find a way to encrypt Strings using Jetpack Security, but I wrote a little class that uses the same logic of `EncryptedFile` and `EncryptedSharedPreferences` to encrypt, decrypt, sign and verify `ByteArray`s (that can be used with strings a swell): https://stackoverflow.com/a/69957937/293878 – Roberto Leinardi Nov 13 '21 at 20:26

1 Answers1

2

I would take your signature:

public String encrypt(String data) and keep it that way but choose an approach:

  1. 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.)

  2. 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.

Martin Marconcini
  • 26,875
  • 19
  • 106
  • 144