我可以从 Swift 中的 SecKeyRef 对象中获取模数或指数吗?
Posted
技术标签:
【中文标题】我可以从 Swift 中的 SecKeyRef 对象中获取模数或指数吗?【英文标题】:Can I get the modulus or exponent from a SecKeyRef object in Swift? 【发布时间】:2016-03-28 07:16:55 【问题描述】:在 Swift 中,我通过在一些原始 X509 证书数据上调用 SecTrustCopyPublicKey
创建了一个 SecKeyRef
对象。这就是SecKeyRef
对象的样子。
Optional(<SecKeyRef algorithm id: 1,
key type: RSAPublicKey,
version: 3, block size: 2048 bits,
exponent: hex: 10001, decimal: 65537,
modulus: <omitted a bunch of hex data>,
addr: 0xsomeaddresshere>)
基本上,这个SecKeyRef
对象包含一大堆关于公钥的信息,但似乎没有办法将这个SecKeyRef
实际转换为字符串NSData
,或其他任何东西(这是我的目标,只是获取 base64 公钥)。
但是,我有一个函数可以给出modulus
和exponent
,它只会计算公钥是什么。我已经通过传入从上述SecKeyRef
记录的数据对其进行了测试。
但不知何故,我无法从 SecKeyRef
对象访问这些属性(我只能在控制台中看到整个对象;例如,我不能做 SecKeyRef.modulus
或类似的事情,看来)。
我的问题:如何访问SecKeyRef.modulus
,或者将这个SecKeyRef
转换为NSData
或类似的东西?谢谢
编辑
(更多信息)
我正在通过这个函数动态创建我的SecKeyRef
:
func bytesToPublicKey(certData: NSData) -> SecKeyRef?
guard let certRef = SecCertificateCreateWithData(nil, certData) else return nil
var secTrust: SecTrustRef?
let secTrustStatus = SecTrustCreateWithCertificates(certRef, nil, &secTrust)
if secTrustStatus != errSecSuccess return nil
var resultType: SecTrustResultType = UInt32(0) // result will be ignored.
let evaluateStatus = SecTrustEvaluate(secTrust!, &resultType)
if evaluateStatus != errSecSuccess return nil
let publicKeyRef = SecTrustCopyPublicKey(secTrust!)
return publicKeyRef
这样做是从证书中获取原始字节流(可以从使用 PKI 的硬件广播),然后将其转换为 SecKeyRef
。
编辑 2
(截至 2015 年 1 月 7 日现有答案的 cmets)
这不起作用:
let mirror = Mirror(reflecting: mySecKeyObject)
for case let (label?, value) in mirror.children
print (label, value)
这会在控制台中产生以下输出:
Some <Raw SecKeyRef object>
不确定字符串“Some”是什么意思。
此外,mirror.descendant("exponent")
(或“模数”)导致nil
,即使在控制台中打印原始对象时,我也可以清楚地看到这些属性存在,并且它们实际上已填充。
另外,如果可能的话,我希望避免保存到钥匙串,读取为NSData
,然后从钥匙串中删除。如赏金描述中所述,如果这是唯一可能的方法,请引用权威参考。感谢您迄今为止提供的所有答案。
【问题讨论】:
你看过这个answer了吗?或者列出here 的任何答案?他们是Obj-C
,但翻译起来并不难。
@Caleb,感谢您的评论。不幸的是,是的,我已经梳理了几个小时,但运气不佳。它们似乎并不完全是我正在寻找的东西(它们似乎主要处理生成密钥对)。也许我错了。
那很不幸。我真的找不到关于这个主题的任何其他内容,最近的任何内容都被忽略了。我对这个主题没有任何知识。祝你好运,很抱歉我帮不上忙!
***.com/questions/27758446/…
let mirrorKey = Mirror(reflecting: secKey); let exponent = mirrorKey.descendant("exponent"); let modulus = mirrorKey.descendant("modulus");
【参考方案1】:
我一直在尝试进行 SSL 公钥固定。 API 几乎不存在,我找到的解决方案是将它放在钥匙串中,然后您可以将其检索为 NSData(然后可以进行 Base64 编码)。这太可怕了,但经过一天左右的研究后我唯一能找到的东西(没有求助于将 OpenSSL 与我的应用程序捆绑在一起)。
我将我的一些代码移植到了 Swift,但我没有对它进行太多测试,所以我不能 100% 确定它是否有效:https://gist.github.com/chedabob/64a4cdc4a1194d815814
它基于这个 Obj-C 代码(我相信它可以在生产应用程序中工作):https://gist.github.com/chedabob/49eed109a3dfcad4bd41
【讨论】:
谢谢你的回答,我会看看那些资源,如果我有任何运气,我会告诉你。 您好,我授予了赏金,因为这是迄今为止最好的答案。不过,我又开了另一个赏金,因为我希望找到权威的参考资料和替代方法(如果你有兴趣,请参阅新的赏金描述)。谢谢。 我这个答案在苹果方面可以吗?还是我会因为使用它而被拒绝?【参考方案2】:SecKeyRef
是一个结构体,因此它有可能被 Mirror()
反射以检索想要的值。
struct myStruct
let firstString = "FirstValue"
let secondString = "SecondValue"
let testStruct = myStruct()
let mirror = Mirror(reflecting: testStruct)
for case let (label?, value) in mirror.children
print (label, value)
/**
Prints:
firstString FirstValue
secondString SecondValue
*/
【讨论】:
感谢您的回答,但是 Mirror 在这种情况下不起作用。看起来应该,但它实际上并没有显示 SecKey 中的属性。 你真的测试过这个吗?【参考方案3】:我根据 *** 中其他人的回答写了这篇文章。目前我在生产中使用它,但我很高兴使用另一个不需要写入钥匙串的解决方案。
- (NSData *)getPublicKeyBitsFromKey:(SecKeyRef)givenKey host:(NSString*)host
NSString *tag = [NSString stringWithFormat:@"%@.%@",[[NSBundle mainBundle] bundleIdentifier], host];
const char* publicKeyIdentifier = [tag cStringUsingEncoding:NSUTF8StringEncoding];
NSData *publicTag = [[NSData alloc] initWithBytes:publicKeyIdentifier length:strlen(publicKeyIdentifier) * sizeof(char)];
OSStatus sanityCheck = noErr;
// NSData * publicKeyBits = nil;
CFTypeRef publicKeyBits;
NSMutableDictionary * queryPublicKey = [[NSMutableDictionary alloc] init];
// Set the public key query dictionary.
[queryPublicKey setObject:(id)kSecClassKey forKey:(id)kSecClass];
[queryPublicKey setObject:publicTag forKey:(id)kSecAttrApplicationTag];
[queryPublicKey setObject:(id)kSecAttrKeyTypeRSA forKey:(id)kSecAttrKeyType];
[queryPublicKey setObject:[NSNumber numberWithBool:YES] forKey:(id)kSecReturnData];
[queryPublicKey setObject:(__bridge id)givenKey forKey:(__bridge id)kSecValueRef];
// Get the key bits.
NSData *data = nil;
sanityCheck = SecItemCopyMatching((CFDictionaryRef)queryPublicKey, &publicKeyBits);
if (sanityCheck == errSecSuccess)
data = CFBridgingRelease(publicKeyBits);
//I don't want to leak this information
(void)SecItemDelete((__bridge CFDictionaryRef) queryPublicKey);
else
sanityCheck = SecItemAdd((CFDictionaryRef)queryPublicKey, &publicKeyBits);
if (sanityCheck == errSecSuccess)
data = CFBridgingRelease(publicKeyBits);
(void)SecItemDelete((__bridge CFDictionaryRef) queryPublicKey);
return data;
【讨论】:
您好,非常感谢您的回答。这是在某个地方移植到 Swift 的吗?另外,您是否找到了不需要保存到钥匙串的答案?谢谢。 不,我仍在寻找其他解决方案。还有其他答案可以使用您可以使用的 swift 来做同样的事情。我只检查钥匙是否存在于钥匙串中。我有一个案例,密钥被添加并且没有被删除。之后会导致应用无法获取公钥数据。 @sahara108 您可能对my answer 感兴趣。这是可能的,从某种意义上说是走很长的路。【参考方案4】:来自How do I encode an unmanaged<SecKey> to base64 to send to another server?:
func convertSecKeyToBase64(inputKey: SecKey) ->String?
// Add to keychain
let tempTag = "net.example." + NSUUID().UUIDString
let addParameters :[String:AnyObject] = [
String(kSecClass): kSecClassKey,
String(kSecAttrApplicationTag): tempTag,
String(kSecAttrKeyType): kSecAttrKeyTypeRSA,
String(kSecValueRef): inputKey,
String(kSecReturnData):kCFBooleanTrue
]
var result: String?
var keyPtr: AnyObject?
if (SecItemAdd(addParameters, &keyPtr) == noErr)
let data = keyPtr! as! NSData
result = data.base64EncodedStringWithOptions(NSDataBase64EncodingOptions(rawValue: 0))
// Remove from Keychain:
SecItemDelete(addParameters)
return result
但是如果你想避免添加到钥匙串,你可以使用镜像:
let mirrorKey = Mirror(reflecting: secKey)
let exponent = mirrorKey.descendant("exponent")
let modulus = mirrorKey.descendant("modulus");
[编辑:根据乔希,镜子不工作]
【讨论】:
非常感谢您的回答。不幸的是,镜子不起作用。意思是,mirrorKey.descendant("exponent")
和 mirrorKey.descendant("modulus")
都导致 nil
。
@JoshBeam,我想帮忙。如何获取 SecTrust 对象进行测试?
嘿,非常感谢。我在“编辑”下的问题底部添加了一些内容,显示了我用来生成对象的函数。基本上,我从一个原始证书字节流开始,然后从中创建一个 SecKeyRef。目前,我能够从中解析公钥的唯一方法是逐字节提取公钥(基于知道公钥应该是什么进行逆向工程,因为我已经拥有该数据我的证书之一)。似乎更好的方法是使用 SecKeyRef,因此我问了这个问题。
如果我能提供更多信息,请告诉我。【参考方案5】:
您是否考虑过使用SecCertificateCopyData()
?我认为生成的CFData
是免费桥接的。
参考https://developer.apple.com/library/ios/documentation/Security/Reference/certifkeytrustservices/查看API的相关文档。
【讨论】:
非常感谢您的回答,但这不起作用。如果我可以为您提供任何其他信息,请告诉我。 到底是什么不工作?我记得很多铸造和弯曲才能使它与 swift 一起使用。 我可能误解了一些东西。能否提供一个最小示例,说明如何使用SecCertificateCopyData()
从 SecKeyRef 对象或指数/模数中提取公钥?谢谢。
对不起;为此,您似乎需要SecCertificateRef
。但是你有一个匹配你的公钥的信任,你正在复制SecKeyRef
。【参考方案6】:
我在一个废弃的项目中发现了 ASN.1 解析器的单个 Obj-c 重新实现,它似乎可以工作。问题是,它使用了大量我不知道如何翻译成 Swift 的指针技巧(甚至不确定其中一些是可能的)。应该可以围绕它创建一个快速友好的包装器,因为它需要的唯一输入是 NSData。
网络上的一切都在使用钥匙串技巧中的存储和检索来获取 pub 密钥数据,甚至是像 TrustKit 这样非常流行的库。我在Apple docs on SecKeyRef 中找到了根本原因的参考(我认为):
密钥链中存储的密钥的 SecKeyRef 对象可以是 安全地转换为 SecKeychainItemRef 以作为钥匙串进行操作 物品。另一方面,如果 SecKeyRef 没有存储在钥匙串中, 将对象转换为 SecKeychainItemRef 并将其传递给 Keychain 服务函数返回错误。
由于 SecCertificateCopyValues
目前在 iOS 上不可用,因此您只能解析证书数据或执行 Keychain Item shuffle。
【讨论】:
【参考方案7】:更新以下答案可能会导致您的应用因使用非公共 API 而被拒绝。
答案在于 Apple 开源网站的 SecRSAKey.h 文件(安全性是 Apple 开源代码的一部分)。该文件不大,其中声明了以下两个重要函数:
CFDataRef SecKeyCopyModulus(SecKeyRef rsaPublicKey);
CFDataRef SecKeyCopyExponent(SecKeyRef rsaPublicKey);
您可以将这些函数添加到您的桥接头中,以便能够从 Swift 调用它们,同时您还可以从 CFDataRef
切换到 NSData*
作为两种类型的免费桥接:
NSData* SecKeyCopyModulus(SecKeyRef rsaPublicKey);
NSData* SecKeyCopyExponent(SecKeyRef rsaPublicKey);
演示 Swift 用法:
let key = bytesToPublicKey(keyData)
let modulus = SecKeyCopyModulus(key)
let exponent = SecKeyCopyExponent(key)
print(modulus, exponent)
虽然这是一个私有 API,它可能会在某个时候不再可用,但是我查看了 Security
公开的版本 (http://www.opensource.apple.com/source/Security),看起来像这两个函数存在于所有这些中。此外,由于Security
是操作系统的关键组件,Apple 不太可能对其进行重大更改。
在 iOS 8.1、iOS 9.2 和 OSX 10.10.5 上测试,代码适用于所有三个平台。
【讨论】:
非常感谢您的回答 Cristik,我很欣赏对此进行的研究。但是,您提供的链接中的源代码中有一条注释:“给定编码形式的 RSA 公钥,返回代表该密钥的 SecKeyRef。”看起来,为了使用SecKeyCopyModulus
(例如),您必须使用uint8_t *keyData
创建您的SecKeyRef
对象,这意味着我必须已经拥有公钥数据(但是,我没有这个;我只需拥有整个证书的原始字节)。我忽略了什么吗?
该评论适用于SecKeyCreateRSAPublicKey
,以防您自己创建密钥。在您的情况下,您使用SecTrustCopyPublicKey
获取密钥,它从使用SecCertificateCreateWithData
的文件加载的证书中提取密钥。
所以你确实有密钥的字节,它们存储在SecKeyRef
opaque 结构中。请参阅SecKeyPriv.h - SecKeyRef
是 __SecKey
的别名,void *key;
字段保存密钥的原始数据。
@JoshBeam 让我知道最后 2 个 cmets 是否澄清了事情:)
想通知大家,从今天开始,如果您使用该方法,您的应用将被阻止。刚刚发生在我身上。 The app references non-public symbols in ___: _SecKeyCopyModulus
【参考方案8】:
我找到了如何获取 SecKey
的数据。
let publicKey: SecKey = ...
let data = SecKeyCopyExternalRepresentation(publicKey, nil)
这似乎运作良好,我已经能够成功地比较公钥。
这是在 Swift 3(Xcode 8 beta 3)中
【讨论】:
感谢您的回复。SecKeyCopyExternalRepresentation
的返回值是多少?
这是一个CFData
if let pinnedPublicKeyData = SecKeyCopyExternalRepresentation(pinnedPublicKey, nil), let publicKeyData = SecKeyCopyExternalRepresentation(publicKey, nil), pinnedPublicKeyData == publicKeyData // they match
有没有办法为 iOS 10 之前的 iOS 版本做到这一点?【参考方案9】:
确实可以不使用钥匙串也不私有API来提取模数和指数。
有 (public but undocumented) 函数 SecKeyCopyAttributes
从 SecKey
中提取 CFDictionary
。
一个有用的属性键来源是SecItemConstants.c
检查这本词典的内容,我们找到了一个条目"v_Data" : <binary>
。它的内容是DER-encoded ASN为
SEQUENCE
modulus INTEGER,
publicExponent INTEGER
请注意,如果整数是正数且具有前导 1 位,则用零字节填充(以免将它们与二补负数混淆),因此您可能会发现比预期多一个字节.如果发生这种情况,就把它剪掉。
您可以为这种格式实现解析器,或者在知道您的密钥大小的情况下对提取进行硬编码。对于 2048 位密钥(和 3 字节指数),格式为:
30|82010(a|0) # Sequence of length 0x010(a|0)
02|82010(1|0) # Integer of length 0x010(1|0)
(00)?<modulus>
02|03 # Integer of length 0x03
<exponent>
总共是 10 + 1? + 256 + 3 = 269 或 270 字节。
import Foundation
extension String: Error
func parsePublicSecKey(publicKey: SecKey) -> (mod: Data, exp: Data)
let pubAttributes = SecKeyCopyAttributes(publicKey) as! [String: Any]
// Check that this is really an RSA key
guard Int(pubAttributes[kSecAttrKeyType as String] as! String)
== Int(kSecAttrKeyTypeRSA as String) else
throw "Tried to parse non-RSA key as RSA key"
// Check that this is really a public key
guard Int(pubAttributes[kSecAttrKeyClass as String] as! String)
== Int(kSecAttrKeyClassPublic as String)
else
throw "Tried to parse non-public key as public key"
let keySize = pubAttributes[kSecAttrKeySizeInBits as String] as! Int
// Extract values
let pubData = pubAttributes[kSecValueData as String] as! Data
var modulus = pubData.subdata(in: 8..<(pubData.count - 5))
let exponent = pubData.subdata(in: (pubData.count - 3)..<pubData.count)
if modulus.count > keySize / 8 // --> 257 bytes
modulus.removeFirst(1)
return (mod: modulus, exp: exponent)
(我最终写了一个完整的 ASN 解析器,所以这段代码没有经过测试,小心!)
请注意,您可以以非常相似的方式提取 private 密钥的详细信息。使用 DER 术语,这是v_Data
的格式:
PrivateKey ::= SEQUENCE
version INTEGER,
modulus INTEGER, -- n
publicExponent INTEGER, -- e
privateExponent INTEGER, -- d
prime1 INTEGER, -- p
prime2 INTEGER, -- q
exponent1 INTEGER, -- d mod (p-1) (dmp1)
exponent2 INTEGER, -- d mod (q-1) (dmq1)
coefficient INTEGER, -- (inverse of q) mod p (coeff)
otherPrimeInfos OtherPrimeInfos OPTIONAL
手动解析它可能是不明智的,因为任何整数都可能已被填充。
注意事项:如果密钥是在macOS上生成的,则公钥的格式会有所不同;上面给出的结构是这样包装的:
SEQUENCE
id OBJECTID,
PublicKey BITSTRING
位串是上述形式的 DER 编码的 ASN。
【讨论】:
注意:如果你想编程更具攻击性,SecKeyCopyExternalRepresentation(publicKey, nil)
直接给你v_Data
,就像jamone notes。
酷,感谢您的见解!您提到的您编写的完整 ASN 解析器在某处可用吗?
@dnlggr 到目前为止,现在。感谢您的提醒;我会问是否允许我开源它。 (这不是一个完整或好的实现,iirc,但大多数类型都包括在内。)
甜蜜!感谢您的调查。以上是关于我可以从 Swift 中的 SecKeyRef 对象中获取模数或指数吗?的主要内容,如果未能解决你的问题,请参考以下文章
RSA加密如何让字符串NSString如何转成SecKeyRef?