我可以从 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 公钥)。

但是,我有一个函数可以给出modulusexponent,它只会计算公钥是什么。我已经通过传入从上述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) 函数 SecKeyCopyAttributesSecKey 中提取 CFDictionary。 一个有用的属性键来源是SecItemConstants.c

检查这本词典的内容,我们找到了一个条目"v_Data" : &lt;binary&gt;。它的内容是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 对象中获取模数或指数吗?的主要内容,如果未能解决你的问题,请参考以下文章

从 base64 RSA 公钥生成 SecKeyRef

iOS - 从指数+模数创建 SecKeyRef

将 SecKeyRef 设备生成的公钥/私钥对保存在磁盘上

RSA加密如何让字符串NSString如何转成SecKeyRef?

尝试从 swift 2 中的 JSON 文件中读取重载错误出现

从核心数据中检索 Swift 中的坐标时遇到问题