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

c# How to store RSA key-pair on iOS keychain [Xamarin]?

我正在开发 Xamarin Forms 应用程序,在 iOS 中,我想创建一个 RSA 密钥对并能够将密钥对存储在 iOS KeyChain 中并检索public来自 iOS KeyChain 的钥匙,我想随时都可以。但是,由于互联网上缺少有关此问题的文档,我遇到了一些问题。 所以我以这种方式创建了我的 RSA 密钥对,因为我必须将 DER 格式的 public 密钥发送到服务器(使用 BouncyCastle)。 `

        RsaKeyPairGenerator generator = new RsaKeyPairGenerator();

        generator.Init(new KeyGenerationParameters(new SecureRandom(), 2048));

        AsymmetricCipherKeyPair keyPair = generator.GenerateKeyPair();

        RsaKeyParameters keyParam = (RsaKeyParameters)keyPair.Public;

        var info = SubjectPublicKeyInfoFactory.CreateSubjectPublicKeyInfo(keyParam);

        byte[] RsaBytes = info.GetEncoded();

`

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

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

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

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

我尝试使用以下方法将它转换回代表我的 public 密钥的字节[]:

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

没有一个与 RsaBytes 相同:

    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

所以,总结所有问题

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

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

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

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

我在互联网上找不到这方面的样本...非常感谢您的帮助。 非常感谢大家。

我仍在努力让 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;
    }

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

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

这里是 IOSConstants class 和使用的属性:

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()[=16 获取 public 密钥=]

 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 上,返回的 public 键是一个简单的 asn1 序列,包含模数和指数,为了让它与充气城堡一起工作,我将其转换为 pkcs#8 public密钥格式。

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