c#如何在iOS钥匙串[Xamarin]上存储RSA密钥对?

时间:2018-03-20 10:10:44

标签: c# xamarin.forms xamarin.ios keychain touch-id

我正在开发一个Xamarin Forms应用程序,在iOS中我想创建一个RSA密钥对,并能够将密钥对存储在iOS KeyChain中,并随时从iOS KeyChain检索publicKey。然而,由于互联网上缺乏关于此问题的文档,我面临一些问题。 所以我以这种方式创建了我的RSA密钥对,因为我必须将DER格式的公钥发送到服务器(使用BouncyCastle)。 `

`        private void StoreKeysInKeychain(string key, byte[] value)
        {
        SecRecord record = new SecRecord(SecKind.GenericPassword)
                    {
                        ValueData = NSData.FromArray(value),
                        Generic = NSData.FromString(key)
                    };
        record.AccessControl = new SecAccessControl(SecAccessible.WhenUnlocked, SecAccessControlCreateFlags.TouchIDAny);
        SecStatusCode  err = SecKeyChain.Add(record);
    }`

`

然后我需要将密钥存储在KeyChain上,但我找不到任何可以帮助我的东西,所以我尝试了这种方式(byte [] value = RsaBytes):

         NSData GetRecordsFromKeychain(string key)
                {
                    SecStatusCode res;
                    var rec = new SecRecord(SecKind.GenericPassword)
                    {
                        Generic = NSData.FromString(key)
                    };

                    SecRecord match = SecKeyChain.QueryAsRecord(rec, out res);
                    if (match != null)
                    {
                        // nsdata object :  match.ValueData;
                        return match.ValueData;
                    }
                    return null;
                }

首先,我不知道这是否是存储iOS KeyChain的最佳方式。 其次,当我从KeyChain获取'ValueData'时,我无法将其转换回byte [],因此我再也无法再获取publicKey了。 我使用以下方法获取密钥

    byte[] marshalBytes = new byte[nsdata.Length];
    System.Runtime.InteropServices.Marshal.Copy(nsdata.Bytes, marshalBytes, 0, Convert.ToInt32(nsdata.Length));
    byte[] storedBytes = ToByte(nsdata);
    byte[] convertedBytes = nsdata.ToArray();

我尝试使用以下方法将其转换回代表我的公钥的byte []:

    bool result1 = RsaBytes.SequenceEqual(storedBytes);         // FALSE
    bool result2 = RsaBytes.SequenceEqual(convertedBytes);  // FALSE
    bool result3 = storedBytes.SequenceEqual(convertedBytes); // TRUE
    bool result4 = marshalBytes.SequenceEqual(RsaBytes);    // FALSE
    bool result5 = marshalBytes.SequenceEqual(storedBytes);     // TRUE

其中没有一个与RsaBytes相同:

//------------------- definitions
DataTable datatable = new DataTable("Points");
this.DATAGRID.DataSource = datatable; //connect data to DATAGRID set in designer

//adding button column
if (DATAGRID.Columns.Contains("Button_column") == false) //I want to add button column only once
{ 
    DataGridViewButtonColumn button_column = new DataGridViewButtonColumn();
    button_column.HeaderText = "ON/OFF";
    button_column.Text = "Click";
    button_column.Name = "Button_column";
    button_column.UseColumnTextForButtonValue = true;
    DATAGRID.Columns.Add(button_column);
}    

//add next columns
datatable.Columns.Add("id", typeof(int));
datatable.Columns.Add("Date", typeof(string));
datatable.Columns.Add("Point", typeof(string));
datatable.Columns.Add("Status", typeof(string));

//set order for the user
DATAGRID.Columns["id"].DisplayIndex = 0; //will need id later
DATAGRID.Columns["id"].Visible = false; //but I hide it from user
DATAGRID.Columns["Date"].DisplayIndex = 1;
DATAGRID.Columns["Point"].DisplayIndex = 2;
DATAGRID.Columns["Status"].DisplayIndex = 3;
DATAGRID.Columns["Button_column"].DisplayIndex = 4;

//------------------- data
int i = 0;
while (r_dane_kontroli.Read())
{

    //I add data here
    datatable.Rows.Add(1, "Date", "Point"); //adding value to the button here won't work (error "to many columns")

    //I TRY TO CHANGE BUTTON TEXT HERE - IN THE LOOP
    //this doesn't work no matter if I adress the row or cell via index or name (tried other indexes too...)
    if(status == "ON")
        DATAGRID.Rows[i].Cells[0].Value = "OFF";
    if(status == "OFF")
        DATAGRID.Rows[i].Cells[0].Value = "ON";

    i++;
}

所以,总结所有问题

1 - 我不知道在iOS中创建RSA密钥对并获得publicKey DER编码的最佳方法(所以我使用了BouncyCastle)

2 - 不知道如何在KeyChain上正确存储它

3 - 我可以访问我存储的信息,但是因为我无法将其转换回publicKey而无用

4 - 在尝试访问信息时,用户应始终提示使用TouchID。

我在互联网上找不到这样的样本...我真的很感激一些帮助。 非常感谢大家。

1 个答案:

答案 0 :(得分:1)

我仍然在努力让SecAccessControl以我想要的方式工作,所以我只能用1-3来帮助你。

在我的iOS应用中,我使用 SecKey.CreateRandomKey 功能存储密钥对。 要使用此功能,您必须创建一个NSDictionary,其中包含用于生成密钥的属性。

private NSDictionary BuildKeyPairAttributes(int keySize)
    {
        IList<object> keyBuilder = new List<object>();
        IList<object> valueBuilder = new List<object>();

        keyBuilder.Add(IOSConstants.Preloaded.constKSecAttrIsPermanent);
        keyBuilder.Add(IOSConstants.Preloaded.constKSecAttrApplicationTag);
        keyBuilder.Add(IOSConstants.Preloaded.constKSecAttrAccessible);

        valueBuilder.Add(NSNumber.FromBoolean(true));
        valueBuilder.Add(KeyAlias);
        valueBuilder.Add(IOSConstants.Preloaded.constKSecAccessibleWhenPasscodeSetThisDeviceOnly);

        NSDictionary privateKeyAttr = NSDictionary.FromObjectsAndKeys(valueBuilder.ToArray(), keyBuilder.ToArray());

        keyBuilder.Clear();
        valueBuilder.Clear();

        keyBuilder.Add(IOSConstants.Preloaded.constKSecAttrKeyType);
        keyBuilder.Add(IOSConstants.Preloaded.constKSecAttrKeySize);
        keyBuilder.Add(IOSConstants.Preloaded.constKSecPrivateKeyAttrs);

        valueBuilder.Add(IOSConstants.Preloaded.constKSecAttrKeyTypeRSA);
        valueBuilder.Add(keySize);
        valueBuilder.Add(privateKeyAttr);

        return NSDictionary.FromObjectsAndKeys(valueBuilder.ToArray(), keyBuilder.ToArray());
    }

public bool CreateNewRSAKey(int keySize)
    {
        if (!Delete())
        {
            return false;
        }

        var keyGenerationAttributes = BuildKeyPairAttributes(keySize);
        var privateKey = SecKey.CreateRandomKey(keyGenerationAttributes, out NSError errCode);

        if (privateKey == null || errCode != null)
        {
            //Handle error
            return false;
        }
        return true;
    }

您可以在Apples文档中找到属性的可能键和值。 要获取字符串常量的值,可以使用:

var handle = Dlfcn.dlopen(Constants.SecurityLibrary, 0);
constKSecAttrApplicationTag = Dlfcn.GetStringConstant(handle, "kSecAttrApplicationTag"); //replace with whatever constant you need
Dlfcn.dlclose(handle);

以下是具有已使用属性的IOSConstants类:

class IOSConstants
{
    private static IOSConstants _singleton;

    public static IOSConstants Preloaded
    {
        get
        {
            if(_singleton == null)
            {
                _singleton = new IOSConstants();
            }
            return _singleton;
        }
    }

    public readonly NSString constKSecAttrKeyType;
    public readonly NSString constKSecAttrKeySize;
    public readonly NSString constKSecAttrKeyTypeRSA;
    public readonly NSString constKSecAttrIsPermanent;
    public readonly NSString constKSecAttrApplicationTag;
    public readonly NSString constKSecPrivateKeyAttrs;
    public readonly NSString constKSecClass;
    public readonly NSString constKSecClassKey;
    public readonly NSString constKSecPaddingPKCS1;
    public readonly NSString constKSecAccessibleWhenPasscodeSetThisDeviceOnly;
    public readonly NSString constKSecAttrAccessible;

    public IOSConstants()
    {
        var handle = Dlfcn.dlopen(Constants.SecurityLibrary, 0);

        try
        {
            constKSecAttrApplicationTag = Dlfcn.GetStringConstant(handle, "kSecAttrApplicationTag");
            constKSecAttrKeyType = Dlfcn.GetStringConstant(handle, "kSecAttrKeyType");
            constKSecAttrKeyTypeRSA = Dlfcn.GetStringConstant(handle, "kSecAttrKeyTypeRSA");
            constKSecAttrKeySize = Dlfcn.GetStringConstant(handle, "kSecAttrKeySizeInBits");
            constKSecAttrIsPermanent = Dlfcn.GetStringConstant(handle, "kSecAttrIsPermanent");
            constKSecPrivateKeyAttrs = Dlfcn.GetStringConstant(handle, "kSecPrivateKeyAttrs");
            constKSecClass = Dlfcn.GetStringConstant(handle, "kSecClass");
            constKSecClassKey = Dlfcn.GetStringConstant(handle, "kSecClassKey");
            constKSecPaddingPKCS1 = Dlfcn.GetStringConstant(handle, "kSecPaddingPKCS1");
            constKSecAccessibleWhenPasscodeSetThisDeviceOnly = Dlfcn.GetStringConstant(handle, "kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly");
            constKSecAttrAccessible = Dlfcn.GetStringConstant(handle, "kSecAttrAccessible");
        }
        finally
        {
            Dlfcn.dlclose(handle);
        }
    }
}

使用此方法创建密钥对时,可以使用 SecKey.GetPublicKey()获取公钥.GetExternalRepresentation()

 private SecKey GetKeyFromKeyChain()
    {
        var foundKey = SecKeyChain.QueryAsConcreteType(
            new SecRecord(SecKind.Key)
            {
                ApplicationTag = KeyAlias
            }, out SecStatusCode errCode);

        if (foundKey == null || errCode != SecStatusCode.Success)
        {
            //Handle error
            return null;
        }
        return foundKey as SecKey;
    }

 public byte[] GetPublicKey()
    {
        NSError errCode = null;

        var foundKey = GetKeyFromKeyChain();
        var publicKey = foundKey?.GetPublicKey();
        var publicKeyExternalFormat = publicKey?.GetExternalRepresentation(out errCode);

        if (publicKeyExternalFormat == null || errCode != null)
        {
            //Handle error
            return null;
        }
        return publicKeyExternalFormat.ToArray();
    }

在我的iPhone 5s上,返回的公钥是一个简单的asn1序列,包含模数和指数,以使其与弹性城堡一起工作,我将其转换为pkcs#8公钥格式。

 var pkcs8PublicKey = new DerSequence(
     new DerSequence(
         new DerObjectIdentifier("1.2.840.113549.1.1.1"),
         DerNull.Instance),
     new DerBitString(publicKeyFromKeyChain)
 ).GetDerEncoded();

修改

使用此属性,iOS每次要访问密钥时都会询问我的触摸ID

//in BuildKeyPairAttributes
keyBuilder.Add(IOSConstants.Preloaded.constKSecAttrIsPermanent);
keyBuilder.Add(IOSConstants.Preloaded.constKSecAttrApplicationTag);  
//constKSecAttrAccessControl = Dlfcn.GetStringConstant(handle, "kSecAttrAccessControl");
keyBuilder.Add(IOSConstants.Preloaded.constKSecAttrAccessControl);

valueBuilder.Add(NSNumber.FromBoolean(true));
valueBuilder.Add(KeyAlias);
valueBuilder.Add(new SecAccessControl(SecAccessible.WhenPasscodeSetThisDeviceOnly, SecAccessControlCreateFlags.UserPresence));

NSDictionary privateKeyAttr = NSDictionary.FromObjectsAndKeys(valueBuilder.ToArray(), keyBuilder.ToArray());