我正在开发一个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。
我在互联网上找不到这样的样本...我真的很感激一些帮助。 非常感谢大家。
答案 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());