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());
我正在开发 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());