10

I have a Xamarin application and have managed to download my data from my server to my device. I have also got it set up so that it can take a SqlCipher Encryption key to encrypt the data.

My question is where is the correct location to store my key that I use to encrypt this data? Is it to you KeyStore / KeyChain? Which mono classes should I be looking to use?

JKennedy
  • 18,150
  • 17
  • 114
  • 198
  • As far as I know your best options are Keychain for iOS and KeyStore for Android. [Mono example for Keychain](https://github.com/xamarin/monotouch-samples/tree/master/Keychain) – Wizche Feb 12 '15 at 18:40
  • And also a [Mono example for KeyStore](https://github.com/escfrya/Locator/blob/3297318c781aabc67b6d303cddaa792af2dcf6c4/Xamarin.Auth/Xamarin.Auth.Android/AndroidAccountStore.cs) – Wizche Feb 12 '15 at 18:48
  • @Wizche I have looked at the android mono example and it seems to be using the nuget Xamarin.Auth. Because I can't reference Xamarin.Auth in my PCL this means I can't use the methods from my PCL via an interface using dependency injection. I would therefore say this is not a good way to store encryption keys using Xamarin – JKennedy Feb 16 '15 at 11:44
  • As far as I know Xamarin.Auth is using Keystore and keychain for storing auth tokens, you will need a custom implementation for your goal and the code above should give you some hints on how to use such native APIs in mono. – Wizche Feb 16 '15 at 16:45

2 Answers2

7

Due to the popularity of this question I am going to post my implementation of this:

PCL interface

public interface IAuth
{
    void CreateStore();
    IEnumerable<string> FindAccountsForService(string serviceId);
    void Save(string pin,string serviceId);
    void Delete(string serviceId);
}

Android

public class IAuthImplementation : IAuth
{
    Context context;
    KeyStore ks;
    KeyStore.PasswordProtection prot;

    static readonly object fileLock = new object();

    const string FileName = "MyProg.Accounts";
    static readonly char[] Password = null;

    public void CreateStore()
    {

        this.context = Android.App.Application.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 (Java.IO.FileNotFoundException)
        {
            //ks.Load (null, Password);
            LoadEmptyKeyStore(Password);
        }
    }

    public IEnumerable<string> FindAccountsForService(string serviceId)
    {
        var r = new List<string>();

        var postfix = "-" + serviceId;

        var aliases = ks.Aliases();
        while (aliases.HasMoreElements)
        {
            var alias = aliases.NextElement().ToString();
            if (alias.EndsWith(postfix))
            {
                var e = ks.GetEntry(alias, prot) as KeyStore.SecretKeyEntry;
                if (e != null)
                {
                    var bytes = e.SecretKey.GetEncoded();
                    var password = System.Text.Encoding.UTF8.GetString(bytes);
                    r.Add(password);
                }
            }
        }
        return r;
    }

    public void Delete(string serviceId)
    {
        var alias = MakeAlias(serviceId);

        ks.DeleteEntry(alias);
        Save();
    }

    public void Save(string pin, string serviceId)
    {
        var alias = MakeAlias(serviceId);

        var secretKey = new SecretAccount(pin);
        var entry = new KeyStore.SecretKeyEntry(secretKey);
        ks.SetEntry(alias, entry, prot);

        Save();
    }

    void Save()
    {
        lock (fileLock)
        {
            using (var s = context.OpenFileOutput(FileName, FileCreationMode.Private))
            {
                ks.Store(s, Password);
            }
        }
    }

    static string MakeAlias(string serviceId)
    {
        return "-" + serviceId;
    }

    class SecretAccount : Java.Lang.Object, ISecretKey
    {
        byte[] bytes;
        public SecretAccount(string password)
        {
            bytes = System.Text.Encoding.UTF8.GetBytes(password);
        }
        public byte[] GetEncoded()
        {
            return bytes;
        }
        public string Algorithm
        {
            get
            {
                return "RAW";
            }
        }
        public string Format
        {
            get
            {
                return "RAW";
            }
        }
    }

    static IntPtr id_load_Ljava_io_InputStream_arrayC;

    void LoadEmptyKeyStore(char[] password)
    {
        if (id_load_Ljava_io_InputStream_arrayC == IntPtr.Zero)
        {
            id_load_Ljava_io_InputStream_arrayC = JNIEnv.GetMethodID(ks.Class.Handle, "load", "(Ljava/io/InputStream;[C)V");
        }
        IntPtr intPtr = IntPtr.Zero;
        IntPtr intPtr2 = JNIEnv.NewArray(password);
        JNIEnv.CallVoidMethod(ks.Handle, id_load_Ljava_io_InputStream_arrayC, new JValue[]
            {
                new JValue (intPtr),
                new JValue (intPtr2)
            });
        JNIEnv.DeleteLocalRef(intPtr);
        if (password != null)
        {
            JNIEnv.CopyArray(intPtr2, password);
            JNIEnv.DeleteLocalRef(intPtr2);
        }
    }

Call Create Store in the main activity of Android app first. - This could possibly be improved and remove CreateStrore() from the interface by checking if ks == null in Save and Delete and calling the method if true

iOS

public class IAuthImplementation : IAuth
{
    public IEnumerable<string> FindAccountsForService(string serviceId)
    {
        var query = new SecRecord(SecKind.GenericPassword);
        query.Service = serviceId;

        SecStatusCode result;
        var records = SecKeyChain.QueryAsRecord(query, 1000, out result);

        return records != null ?
            records.Select(GetAccountFromRecord).ToList() :
            new List<string>();
    }

    public void Save(string pin, string serviceId)
    {
        var statusCode = SecStatusCode.Success;
        var serializedAccount = pin;
        var data = NSData.FromString(serializedAccount, NSStringEncoding.UTF8);

        //
        // Remove any existing record
        //
        var existing = FindAccount(serviceId);

        if (existing != null)
        {
            var query = new SecRecord(SecKind.GenericPassword);
            query.Service = serviceId;

            statusCode = SecKeyChain.Remove(query);
            if (statusCode != SecStatusCode.Success)
            {
                throw new Exception("Could not save account to KeyChain: " + statusCode);
            }
        }

        //
        // Add this record
        //
        var record = new SecRecord(SecKind.GenericPassword);
        record.Service = serviceId;
        record.Generic = data;
        record.Accessible = SecAccessible.WhenUnlocked;

        statusCode = SecKeyChain.Add(record);

        if (statusCode != SecStatusCode.Success)
        {
            throw new Exception("Could not save account to KeyChain: " + statusCode);
        }
    }

    public void Delete(string serviceId)
    {
        var query = new SecRecord(SecKind.GenericPassword);
        query.Service = serviceId;

        var statusCode = SecKeyChain.Remove(query);

        if (statusCode != SecStatusCode.Success)
        {
            throw new Exception("Could not delete account from KeyChain: " + statusCode);
        }
    }

    string GetAccountFromRecord(SecRecord r)
    {
        return NSString.FromData(r.Generic, NSStringEncoding.UTF8);
    }

    string FindAccount(string serviceId)
    {
        var query = new SecRecord(SecKind.GenericPassword);
        query.Service = serviceId;

        SecStatusCode result;
        var record = SecKeyChain.QueryAsRecord(query, out result);

        return record != null ? GetAccountFromRecord(record) : null;
    }

    public void CreateStore()
    {
        throw new NotImplementedException();
    }
}

WP

public class IAuthImplementation : IAuth
{
    public IEnumerable<string> FindAccountsForService(string serviceId)
    {
        using (var store = IsolatedStorageFile.GetUserStoreForApplication())
        {
            string[] auths = store.GetFileNames("MyProg");
            foreach (string path in auths)
            {
                using (var stream = new BinaryReader(new IsolatedStorageFileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read, store)))
                {
                    int length = stream.ReadInt32();
                    byte[] data = stream.ReadBytes(length);

                    byte[] unprot = ProtectedData.Unprotect(data, null);
                    yield return Encoding.UTF8.GetString(unprot, 0, unprot.Length);
                }
            }
        }
    }

    public void Save(string pin, string serviceId)
    {
        byte[] data = Encoding.UTF8.GetBytes(pin);
        byte[] prot = ProtectedData.Protect(data, null);

        var path = GetAccountPath(serviceId);

        using (var store = IsolatedStorageFile.GetUserStoreForApplication())
        using (var stream = new IsolatedStorageFileStream(path, FileMode.Create, FileAccess.Write, FileShare.None, store))
        {
            stream.WriteAsync(BitConverter.GetBytes(prot.Length), 0, sizeof(int)).Wait();
            stream.WriteAsync(prot, 0, prot.Length).Wait();
        }
    }

    public void Delete(string serviceId)
    {
        var path = GetAccountPath(serviceId);
        using (var store = IsolatedStorageFile.GetUserStoreForApplication())
        {
            store.DeleteFile(path);
        }
    }

    private string GetAccountPath(string serviceId)
    {
        return String.Format("{0}", serviceId);
    }

    public void CreateStore()
    {
        throw new NotImplementedException();
    }
}

This is an adaptation of the Xamarin.Auth library (Found Here) but removes the dependency from the Xamarin.Auth library to provide cross platform use through the interface in the PCL. For this reason I have simplified it to only save one string. This is probably not the best implementation but it works in my case. Feel free to expand upon this

JKennedy
  • 18,150
  • 17
  • 114
  • 198
  • Thanks for sharing this, just out of interest how do you handle the pin? Is this a user entered value? Where do you store that value? Thanks again. – Jammer Jun 07 '15 at 15:35
  • @Jammer yes the pin is a user entered value. This code stores the value in the `KeyStore` on Android, `KeyChain` on iOS and `IsolatedStorage` on WP, they are all basically the same thing and all 3 are encrypted making it secure – JKennedy Jun 08 '15 at 07:38
  • I understood that bit. I'm building something similar at the moment. My question was really about whether you then store this pin or prompt for it any time it's needed? – Jammer Jun 08 '15 at 07:41
  • @Jammer In my case ask the user to create a pin initially and store it. Then on application start up I prompt the user for the pin and compare it the the stored version to unlock my application – JKennedy Jun 08 '15 at 07:49
  • Gotcha, that does seem to be a common approach. Thanks for your time chap. – Jammer Jun 08 '15 at 08:09
  • Thanks for sharing. How do you handle when someone tries to unlock an existing Keystore with the wrong password? – AhmedW Jul 14 '16 at 09:51
2

There is a nuget package called KeyChain.NET that encapsulated this logic for iOs, Android and Windows Phone.

It's open source and you have find sample at its github repository

More info at this blog post

Vackup
  • 652
  • 4
  • 11