jjzjj

c# - 为 SqlCipher 数据库存储加密 key 的正确方法

coder 2023-11-22 原文

我有一个 Xamarin 应用程序,并已设法将我的数据从我的服务器下载到我的设备。我还对其进行了设置,以便它可以使用 SqlCipher 加密 key 来加密数据。

我的问题是存储我用来加密此数据的 key 的正确位置在哪里?是给你的 KeyStore/KeyChain 吗?我应该使用哪些单声道类?

最佳答案

由于这个问题很受欢迎,我将发布我的实现:

PCL 界面

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

安卓

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);
        }
    }

首先在Android应用的主activity中调用Create Store。 - 这可能会得到改进,并通过在保存和删除中检查 ks == null 并在为真时调用该方法来从界面中删除 CreateStrore()

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();
    }
}

工作人员

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();
    }
}

这是对 Xamarin.Auth 库 (Found Here) 的改编,但从 Xamarin.Auth 库中删除了依赖项以通过 PCL 中的接口(interface)提供跨平台使用。出于这个原因,我将其简化为只保存一个字符串。这可能不是最好的实现,但它适用于我的情况。随意扩展这个

关于c# - 为 SqlCipher 数据库存储加密 key 的正确方法,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/28429004/

有关c# - 为 SqlCipher 数据库存储加密 key 的正确方法的更多相关文章

  1. ruby - 如何使用 Nokogiri 的 xpath 和 at_xpath 方法 - 2

    我正在学习如何使用Nokogiri,根据这段代码我遇到了一些问题:require'rubygems'require'mechanize'post_agent=WWW::Mechanize.newpost_page=post_agent.get('http://www.vbulletin.org/forum/showthread.php?t=230708')puts"\nabsolutepathwithtbodygivesnil"putspost_page.parser.xpath('/html/body/div/div/div/div/div/table/tbody/tr/td/div

  2. ruby - 如何从 ruby​​ 中的字符串运行任意对象方法? - 2

    总的来说,我对ruby​​还比较陌生,我正在为我正在创建的对象编写一些rspec测试用例。许多测试用例都非常基础,我只是想确保正确填充和返回值。我想知道是否有办法使用循环结构来执行此操作。不必为我要测试的每个方法都设置一个assertEquals。例如:describeitem,"TestingtheItem"doit"willhaveanullvaluetostart"doitem=Item.new#HereIcoulddotheitem.name.shouldbe_nil#thenIcoulddoitem.category.shouldbe_nilendend但我想要一些方法来使用

  3. ruby - 为什么我可以在 Ruby 中使用 Object#send 访问私有(private)/ protected 方法? - 2

    类classAprivatedeffooputs:fooendpublicdefbarputs:barendprivatedefzimputs:zimendprotecteddefdibputs:dibendendA的实例a=A.new测试a.foorescueputs:faila.barrescueputs:faila.zimrescueputs:faila.dibrescueputs:faila.gazrescueputs:fail测试输出failbarfailfailfail.发送测试[:foo,:bar,:zim,:dib,:gaz].each{|m|a.send(m)resc

  4. ruby - Facter::Util::Uptime:Module 的未定义方法 get_uptime (NoMethodError) - 2

    我正在尝试设置一个puppet节点,但ruby​​gems似乎不正常。如果我通过它自己的二进制文件(/usr/lib/ruby/gems/1.8/gems/facter-1.5.8/bin/facter)在cli上运行facter,它工作正常,但如果我通过由ruby​​gems(/usr/bin/facter)安装的二进制文件,它抛出:/usr/lib/ruby/1.8/facter/uptime.rb:11:undefinedmethod`get_uptime'forFacter::Util::Uptime:Module(NoMethodError)from/usr/lib/ruby

  5. Ruby 方法() 方法 - 2

    我想了解Ruby方法methods()是如何工作的。我尝试使用“ruby方法”在Google上搜索,但这不是我需要的。我也看过ruby​​-doc.org,但我没有找到这种方法。你能详细解释一下它是如何工作的或者给我一个链接吗?更新我用methods()方法做了实验,得到了这样的结果:'labrat'代码classFirstdeffirst_instance_mymethodenddefself.first_class_mymethodendendclassSecond使用类#returnsavailablemethodslistforclassandancestorsputsSeco

  6. ruby - 解析 RDFa、微数据等的最佳方式是什么,使用统一的模式/词汇(例如 schema.org)存储和显示信息 - 2

    我主要使用Ruby来执行此操作,但到目前为止我的攻击计划如下:使用gemsrdf、rdf-rdfa和rdf-microdata或mida来解析给定任何URI的数据。我认为最好映射到像schema.org这样的统一模式,例如使用这个yaml文件,它试图描述数据词汇表和opengraph到schema.org之间的转换:#SchemaXtoschema.orgconversion#data-vocabularyDV:name:namestreet-address:streetAddressregion:addressRegionlocality:addressLocalityphoto:i

  7. ruby-on-rails - Rails 3.2.1 中 ActionMailer 中的未定义方法 'default_content_type=' - 2

    我在我的项目中添加了一个系统来重置用户密码并通过电子邮件将密码发送给他,以防他忘记密码。昨天它运行良好(当我实现它时)。当我今天尝试启动服务器时,出现以下错误。=>BootingWEBrick=>Rails3.2.1applicationstartingindevelopmentonhttp://0.0.0.0:3000=>Callwith-dtodetach=>Ctrl-CtoshutdownserverExiting/Users/vinayshenoy/.rvm/gems/ruby-1.9.3-p0/gems/actionmailer-3.2.1/lib/action_mailer

  8. ruby - Highline 询问方法不会使用同一行 - 2

    设置:狂欢ruby1.9.2高线(1.6.13)描述:我已经相当习惯在其他一些项目中使用highline,但已经有几个月没有使用它了。现在,在Ruby1.9.2上全新安装时,它似乎不允许在同一行回答提示。所以以前我会看到类似的东西:require"highline/import"ask"Whatisyourfavoritecolor?"并得到:Whatisyourfavoritecolor?|现在我看到类似的东西:Whatisyourfavoritecolor?|竖线(|)符号是我的终端光标。知道为什么会发生这种变化吗? 最佳答案

  9. ruby-on-rails - 如何使用 instance_variable_set 正确设置实例变量? - 2

    我正在查看instance_variable_set的文档并看到给出的示例代码是这样做的:obj.instance_variable_set(:@instnc_var,"valuefortheinstancevariable")然后允许您在类的任何实例方法中以@instnc_var的形式访问该变量。我想知道为什么在@instnc_var之前需要一个冒号:。冒号有什么作用? 最佳答案 我的第一直觉是告诉你不要使用instance_variable_set除非你真的知道你用它做什么。它本质上是一种元编程工具或绕过实例变量可见性的黑客攻击

  10. ruby - 主要 :Object when running build from sublime 的未定义方法 `require_relative' - 2

    我已经从我的命令行中获得了一切,所以我可以运行rubymyfile并且它可以正常工作。但是当我尝试从sublime中运行它时,我得到了undefinedmethod`require_relative'formain:Object有人知道我的sublime设置中缺少什么吗?我正在使用OSX并安装了rvm。 最佳答案 或者,您可以只使用“require”,它应该可以正常工作。我认为“require_relative”仅适用于ruby​​1.9+ 关于ruby-主要:Objectwhenrun

随机推荐