1

How can one create two Android apps where app "A" creates files and only app "B" can read the files? At first glance, Context.grantUriPermission seems like the trick because it allows restricting access to certain packages like so:

//grant permision for app with package "packageName", eg. before starting other app via intent
context.grantUriPermission(packageName, uri, Intent.FLAG_GRANT_WRITE_URI_PERMISSION | Intent.FLAG_GRANT_READ_URI_PERMISSION);

(The above is from "How to use support FileProvider for sharing content to other apps?")

Unfortunately, anyone can create an app with a given package name. Therefore this method of restricting is not very secure.

Is there a more secure method for restricting file access? Perhaps by retrieving the public-key certificate of the calling application (app "B" in this case)?

Community
  • 1
  • 1
Brian Risk
  • 1,244
  • 13
  • 23

2 Answers2

2

There isn't a truly reliable way of doing what you are looking for.

  1. grantUriPermission seems to be a very simple way of doing so. It is a service the android system supports, so it's already there for you to use. As you stated, anyone can create an app with the required package name, though remember, they have to figure out what package name is required.

  2. Another approach can be plain old encryption. This method is very similar to the above method where the encryption key is the package name. It can end up being more secure because seeing what app can access the file might be easier that figuring out the encryption key.

  3. You can look into content providers. Basically, content providers are a method to share your database with other apps, and using code to control access to it. While we're talking about accessing a file, I assume whatever the file stores, a database can store as well, in a more neat and hidden way.

  4. The FileProvider has a feature of file request and approval. The documentation is here. This doesn't seem like the solution you a seeking because I'm not sure whether the stored file has any protections at all while stored (e.g. encryption), only access control.

Take note: it is surprisingly simple to decompile an app and access its code and resources, among them is the source code, encryption key, database login and more. Given the app is complex enough, it'll take a lot of time to reverse engineer what is going on in there but given enough time, and an attacker with enough patience, it can be done. Because of that, a 100% secure method with no vulnerabilities, simply does not exist.

Given enough time, any protection can be cracked. It is important to be able to identify the line between reasonable safeguards and paranoia. To give my take on it, I'd have gone for a combination of grantUriPermission, due to the fact that upon uninstall the app's database is deleted, and encryption that is scattered throughout the application and oddly named classes to make reverse engineering more difficult (call me paranoid, I know).

Good luck!

iMax531
  • 177
  • 1
  • 2
  • 15
2

Perhaps by retrieving the public-key certificate of the calling application (app "B" in this case)?

That is definitely possible:

  public static String getSignatureHash(Context ctxt, String packageName)                                                                     
    throws NameNotFoundException, NoSuchAlgorithmException {
    MessageDigest md=MessageDigest.getInstance("SHA-256");
    Signature sig=
        ctxt.getPackageManager()
            .getPackageInfo(packageName, PackageManager.GET_SIGNATURES).signatures[0];

    return(toHexStringWithColons(md.digest(sig.toByteArray())));
  }

  // based on https://stackoverflow.com/a/2197650/115145

  public static String toHexStringWithColons(byte[] bytes) {
    char[] hexArray=
        { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B',
            'C', 'D', 'E', 'F' };
    char[] hexChars=new char[(bytes.length * 3) - 1];
    int v;

    for (int j=0; j < bytes.length; j++) {
      v=bytes[j] & 0xFF;
      hexChars[j * 3]=hexArray[v / 16];
      hexChars[j * 3 + 1]=hexArray[v % 16];

      if (j < bytes.length - 1) {
        hexChars[j * 3 + 2]=':';
      }
    }

    return new String(hexChars);
  }

(from my SignatureUtils class)

The result of getSignatureHash() is the same format that you get from the JDK's keytool, so you can generate the appropriate hash, save it somewhere (e.g., string resource), and compare it to the one you determine on the fly. Then, if the app claiming to be B does not have the proper hash, you do not send it a Uri to use.

(note that I need to update this code to deal with APKs with multiple signatures, which is a thing now)

CommonsWare
  • 986,068
  • 189
  • 2,389
  • 2,491
  • Thanks for this! I'm not seeing immediately where FileProvider would receive the package name. Would you be up for elaborating a bit? – Brian Risk Feb 24 '17 at 19:04
  • @BrianRisk: I do not understand your question. You know your own application ID (`BuildConfig.APPLICATION_ID`). For this to work you also need to know the application ID of the other app. How you handle that (e.g., bake the value into your app) is up to you. – CommonsWare Feb 24 '17 at 19:18