I am trying to utilize Android's Account Manager to store user's app credentials.
Although I am not saving the user's password, I wanted to save other security keys to the account's UserData. According to the documentation quoted below, this should NOT be accessible by applications with different UID.
public String getUserData (Account account, String key)
Gets the user data named by "key" associated with the account. This is intended for authenticators and related code to store arbitrary metadata along with accounts. The meaning of the keys and values is up to the authenticator for the account.
It is safe to call this method from the main thread.
This method requires the caller to hold the permission AUTHENTICATE_ACCOUNTS and to have the same UID as the account's authenticator.
Parameters
account - The account to query for user data
Returns
The user data, null if the account or key doesn't exist
To test this, I created an application that creates an account and saves some contents to UserData. I also created another application that accesses the accounts of the first app. Below are the snippets:
First app:
AccountManager am = (AccountManager) context.getSystemService(Context.ACCOUNT_SERVICE);
final Account account = new Account("Account Name", "my.account.type");
am.addAccountExplicitly(account, null, null);
am.setAuthToken(account, "my.account.type", "some auth token");
am.setUserData(account, "key.for.secure.user.data", "some secure data");
Second app:
AccountManager am = (AccountManager)context.getSystemService(Context.ACCOUNT_SERVICE);
Account[] accountsFromFirstApp = am.getAccountsByType("my.account.type");
for(Account acct: accountsFromFirstApp){
printToLogs(acct.name);
printToLogs(am.getUserData(acct, "key.for.secure.user.data"));
}
Based on the documentation above, I expected the second app's getUserData() to return an exception for not having the same UID as the owner app. Surprisingly, I was able to access the user data of the first app with no errors.
But when I tried to access accounts from google by using "com.google" as the accountType, I got the expected exception.
What was wrong with my implementation? Did I miss some configuration that was not stated in Android's documentation? Any help will be very much appreciated.
On the second thought, If these user data can just be accessed that easily (assuming that the other applications know my app's account type), then what's the difference of storing strings in UserData from storing them in SharedPreferences instead?
From the AccountManager documentation:
This class provides access to a centralized registry of the user's online accounts. The user enters credentials (username and password) once per account, granting applications access to online resources with "one-click" approval.
Accounts managed by the AccountManager are centralized and reusable, e.g. all Google apps can use the same account, and not every app has to create its own Google account.
So one of the ideas of the AccountManager, as far as i understand, is to have reusable accounts, accessible from different apps.
But as the stored credentials are accessible from different places, you shouldn't store any plain text passwords in the AccountManager.
May this topic is interesting for you: What should I use AccountManager for?
If your data is Personal data, you can encrypt it, so no other apps can read them.
I.E using AES Encryption like this:
public class AESCryptor
{
private static final String ALGORITHM = "AES";
// 16-bit Key for encryption (Change to yours)
private static final String KEY = "XXXXXXXXXXXXXXX";
public static String encrypt(String value) throws Exception
{
Key key = generateKey();
#SuppressLint("GetInstance") Cipher cipher = Cipher.getInstance(AESCryptor.ALGORITHM);
cipher.init(Cipher.ENCRYPT_MODE, key);
byte [] encryptedByteValue = cipher.doFinal(value.getBytes("utf-8"));
return Base64.encodeToString(encryptedByteValue, Base64.DEFAULT);
}
public static String decrypt(String value) throws Exception
{
Key key = generateKey();
#SuppressLint("GetInstance") Cipher cipher = Cipher.getInstance(AESCryptor.ALGORITHM);
cipher.init(Cipher.DECRYPT_MODE, key);
byte[] decryptedValue64 = Base64.decode(value, Base64.DEFAULT);
byte [] decryptedByteValue = cipher.doFinal(decryptedValue64);
return new String(decryptedByteValue,"utf-8");
}
private static Key generateKey() {
return new SecretKeySpec(AESCryptor.KEY.getBytes(),AESCryptor.ALGORITHM);
}
}
I use it in my apps, and it works!
This maybe doesn't answer the question
User data in account manager is accessible by other applications
But its answered by #thomas-s-e
Regards,
#developerfromjokela
Related
hai i am trying to gather user details like userid,username,email etc stored in accountmanager by social applications from my currently developing application.
I was able to get basic details like
{name=myemail#gmail.com,type=com.google}
{name =Facebook,type=com.facebook.auth.logn}
As i need more information i thought of using getUserdata() what is the key i should pass in the below method? Also will i be able gather these data?
public String getUserData (Account account,
String key)
While going through documentation i saw that This method requires the caller to have a signature match with the authenticator that owns the specified account.
We used Anroid Keystore to store some confidential data and set up a password for Keystore. This passwords are used in conjunction with the KeyStore class in the load, getKey and setKeyEntry methods.
The Keystore itself is encrypted and app can only view and query its own data so we can say that data are somewhat secure inside Keystore but how we can secure the password that associated with keystore account? I found many example online and most of them having hardcoded password in code or use null parameter.
Please see in below example. I want to know what is the best approach to secure hardcoded password?
Want to find a safe way in android device itself to store this hardcoded password. Assume that moving it to external place like database, service call etc. options are NOT available.
Context context;
KeyStore ks;
KeyStore.PasswordProtection prot;
static readonly object fileLock = new object ();
const string FileName = "Xamarin.Social.Accounts";
static readonly char[] Password = "3295043EA18CA264B2C40E0B72051DEF2D07AD2B4593F43DDDE1515A7EC32617".ToCharArray ();
public AndroidAccountStore (Context context)
{
this.context = context;
ks = KeyStore.GetInstance (KeyStore.DefaultType);
**prot = new KeyStore.PasswordProtection (Password);**
try {
lock (fileLock) {
using (var s = context.OpenFileInput (FileName)) {
ks.Load (s, Password);
}
}
}
catch (FileNotFoundException) {
//ks.Load (null, Password);
LoadEmptyKeyStore (Password);
}
}
Assume that moving it to external place like database, service call etc. is NOT possible
You want to securely store sensitive information on the local user's machine.
The only way to do that is encrypting it. The most popular encryption algorithm is AES, and luckily Microsoft included an implementation of it in C#.
However, encryption uses a secret key to encrypt/decrypt the data, so we're basically moving the problem back - now we need to store that encryption key securely.
You could hard-code that key in the app, but a dedicated attacker could still get it and decrypt the password.
Instead, get that password from the user. Ask them to provide a password, hash it (using e.g. SHA256) and use the hash as the key for the encryption.
I'm trying to get my app to upload to Fusion Tables without the need to grant every user write permissions to the table beforehand. This is because of security reasons and just to make the system simpler.
Currently, to create an instance of com.google.api.services.fusiontables.Fusiontables, I do:
private static Fusiontables constructFusionTables(Context context) {
GoogleAccountCredential credential = GoogleAccountCredential.usingOAuth2(context,
Arrays.asList("https://www.googleapis.com/auth/fusiontables"));
String username = "someuser#gmail.com";
credential.setSelectedAccountName(username);
return fusiontables = new Fusiontables.Builder(HTTP_TRANSPORT,
JSON_FACTORY, credential).setApplicationName("someappname")
.build();
}
When I try to use a service account email ( XXXXXXX#developer.gserviceaccount.com ), all the Fusion Tables queries made with that FusionTables instance fail telling me that name must not be null.
As a quick workaround for testing, I tried to hardcode a username ( someuser#gmail.com ) that is not on the device's Google Account list, but it gives me the same error.
The error doesn't appear when I use normal user#gmail.com accounts that exist on the phone.
Could you guide me on how to implement a Service Account to Fusion Tables in Android?
I am trying to get my head around Android AccountManager and OAuth.
What i would like to do is not let the phone have access to the password. (That is what Google suggests: "Be Smart About Security!")
So i checkout the Google sample application SampleSyncAdapter and start reading through the code. then i see this happen in AuthenticatorActivity:
private AccountManager mAccountManager;
private String mPassword;
/**
* ... Sets the
* AccountAuthenticatorResult which is sent back to the caller. We store the
* authToken that's returned from the server as the 'password' for this
* account - so we're never storing the user's actual password locally.
*
* #param result the confirmCredentials result.
*/
public void handleLogin(View view) {
....
mPassword = mPasswordEdit.getText().toString();
....
Log.d(TAG, "mPassword set to Account:" + mAccountManager.getPassword(account));
}
private void finishLogin(String authToken) {
....
mAccountManager.addAccountExplicitly(account, mPassword, null);
....
}
This Log message is "mPassword set to Account:test".
This is in some way understandable when you read the rest because of this
protected String doInBackground(Void... params) {
....
return NetworkUtilities.authenticate(mUsername, mPassword);
....
}
if the password was a token this would not work.
Also i would expect the rest of the code to work differently in Authenticator on getAuthToken()
I Assume i am completely wrong about something but i just want to use AccountManager to store the result of an OAuth "Dance" so that i can use this Account to authenticate my JSON RESTful service.
Can any one shine a light on this?
From the documentation we can read this:
It's important to understand that AccountManager is not an encryption service or a keychain. It stores account credentials just as you pass them, in plain text. On most devices, this isn't a particular concern, because it stores them in a database that is only accessible to root. But on a rooted device, the credentials would be readable by anyone with adb access to the device.
Thus, as I understand, here is a problem of misuse of the words (password and token). I guess the procedure is the following:
You ask a user to provide a login and password.
In your application you somehow send this login and password to your server.
Basing on this information your server generates a token and sends back to your application.
AccountManager stores this token in plain text and then this token is used to authenticate your user.
My application connects with the server, which uses OAuth authorization.
How should I store such accounts in Account Manager?
In case I have login and pass, it can look like below:
Account account = new Account("user1", context.getString(R.string.ACCOUNT_TYPE));
AccountManager am = AccountManager.get(context);
if (am.addAccountExplicitly(account, "pass1", null)) {
result = new Bundle();
Log.i(TAG, "account: "+account.name+", "+account.type);
result.putString(AccountManager.KEY_ACCOUNT_NAME, account.name);
result.putString(AccountManager.KEY_ACCOUNT_TYPE, account.type);
activity.setAccountAuthenticatorResult(result);
But what should be passed instead of user name and pass in case of OAuth-account?
And where should I store OAuth-secret? OAuth token should be stored in KEY_AUTHTOKEN?
Don't use the password you could setAuthToken, put the OAuth token there instead:
It's important to understand that AccountManager is not an encryption service or a keychain. It stores account credentials just as you pass them, in plain text. On most devices, this isn't a particular concern, because it stores them in a database that is only accessible to root. But on a rooted device, the credentials would be readable by anyone with adb access to the device.
With this in mind, you shouldn't pass the user's actual password to AccountManager.addAccountExplicitly(). Instead, you should store a cryptographically secure token that would be of limited use to an attacker. If your user credentials are protecting something valuable, you should carefully consider doing something similar.
http://developer.android.com/training/id-auth/custom_auth.html