Skip to content

Instantly share code, notes, and snippets.

@Redth
Last active February 19, 2018 16:23
Show Gist options
  • Select an option

  • Save Redth/7cb75bb1d275dffeeaa7b66cab33cb68 to your computer and use it in GitHub Desktop.

Select an option

Save Redth/7cb75bb1d275dffeeaa7b66cab33cb68 to your computer and use it in GitHub Desktop.
using System;
using System.IO;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;
using System.Threading.Tasks;
#if __ANDROID__
using Java.Security;
using Javax.Crypto;
using Android.Content;
#elif __IOS__
using Security;
using Foundation;
#endif
namespace SecureStore
{
public class SecureStore
{
const string SSFILENAME = "XamarinSecureStorage";
public static string Load (string filename)
{
#if __ANDROID__
var keyStore = LoadKeyStore ();
var entry = keyStore.Item1.GetEntry (filename, keyStore.Item2) as KeyStore.SecretKeyEntry;
if (entry != null) {
var bytes = entry.SecretKey.GetEncoded ();
return System.Text.Encoding.UTF8.GetString (bytes);
}
#elif __IOS__
var kc = new KeyChain ();
return kc.ValueForKey (filename);
#else
try {
if (File.Exists (filename))
return File.ReadAllText (filename);
} catch { }
#endif
return null;
}
public static void Save (string data, string filename)
{
#if __ANDROID__
var ks = LoadKeyStore ();
ks.Item1.SetEntry (filename, new KeyStore.SecretKeyEntry (new SecretEntry (data)), ks.Item2);
SaveKeyStore (ks.Item1);
#elif __IOS__
var kc = new KeyChain ();
kc.SetValueForKey (data, filename);
#else
File.WriteAllText (filename, data);
#endif
}
#if __ANDROID__
static readonly object fileLock = new object ();
static Tuple<KeyStore, KeyStore.PasswordProtection> LoadKeyStore ()
{
var context = Xamarin.Forms.Forms.Context;
var secureKey = GetSecureKey (context);
var keyStore = KeyStore.GetInstance (KeyStore.DefaultType);
var prot = new KeyStore.PasswordProtection (secureKey);
try {
lock (fileLock) {
if (context.GetFileStreamPath (SSFILENAME)?.Exists () ?? false) {
using (var s = context.OpenFileInput (SSFILENAME))
keyStore.Load (s, secureKey);
} else {
keyStore.Load (null, secureKey);
}
}
} catch {
keyStore.Load (null, secureKey);
}
return Tuple.Create (keyStore, prot);
}
static void SaveKeyStore (KeyStore keyStore)
{
var context = Xamarin.Forms.Forms.Context;
lock (fileLock) {
using (var s = context.OpenFileOutput (SSFILENAME, FileCreationMode.Private)) {
keyStore.Store (s, GetSecureKey (context));
s.Flush ();
s.Close ();
}
}
}
static char [] GetSecureKey (Context context)
{
const string CACHEKEY_KEY = "XamarinSecureStorageCacheKey";
var cacheKey = string.Empty;
var prefs = context.GetSharedPreferences (SSFILENAME, FileCreationMode.Private);
if (prefs.Contains (CACHEKEY_KEY)) {
cacheKey = prefs.GetString (CACHEKEY_KEY, string.Empty);
if (!string.IsNullOrEmpty (cacheKey))
return cacheKey.ToCharArray ();
}
// Generate a 256-bit key
const int outputKeyLength = 256;
var secureRandom = new SecureRandom ();
// Do *not* seed secureRandom! Automatically seeded from system entropy.
var keyGenerator = KeyGenerator.GetInstance ("AES");
keyGenerator.Init (outputKeyLength, secureRandom);
var key = keyGenerator.GenerateKey ();
cacheKey = Convert.ToBase64String (key.GetEncoded ());
prefs.Edit ()
.PutString (CACHEKEY_KEY, cacheKey)
.Commit ();
return cacheKey.ToCharArray ();
}
class SecretEntry : Java.Lang.Object, ISecretKey
{
byte [] bytes;
public SecretEntry (string value)
{
bytes = System.Text.Encoding.UTF8.GetBytes (value);
}
public string Algorithm { get { return "RAW"; } }
public string Format { get { return "RAW"; } }
public byte [] GetEncoded ()
{
return bytes;
}
}
#endif
#if __IOS__
public class KeyChain
{
public string ValueForKey(string key)
{
var record = ExistingRecordForKey (key);
SecStatusCode resultCode;
var match = SecKeyChain.QueryAsRecord(record, out resultCode);
if (resultCode == SecStatusCode.Success)
return NSString.FromData (match.ValueData, NSStringEncoding.UTF8);
else
return String.Empty;
}
public void SetValueForKey(string value, string key)
{
var record = ExistingRecordForKey (key);
if (string.IsNullOrEmpty (value))
{
if (!string.IsNullOrEmpty (ValueForKey(key)))
RemoveRecord(record);
return;
}
// if the key already exists, remove it
if (!string.IsNullOrEmpty (ValueForKey(key)))
RemoveRecord(record);
var result = SecKeyChain.Add(CreateRecordForNewKeyValue(key, value));
if (result != SecStatusCode.Success)
{
throw new Exception(String.Format("Error adding record: {0}", result));
}
}
private SecRecord CreateRecordForNewKeyValue(string key, string value)
{
return new SecRecord(SecKind.GenericPassword)
{
Account = key,
Service = SSFILENAME,
Label = key,
ValueData = NSData.FromString(value, NSStringEncoding.UTF8),
};
}
private SecRecord ExistingRecordForKey(string key)
{
return new SecRecord(SecKind.GenericPassword)
{
Account = key,
Service = SSFILENAME,
Label = key,
};
}
private bool RemoveRecord(SecRecord record)
{
var result = SecKeyChain.Remove(record);
if (result != SecStatusCode.Success)
{
throw new Exception(String.Format("Error removing record: {0}", result));
}
return true;
}
}
#endif
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment