我能从Swift中的SecKeyRef对象获取模数或指数吗?

28

在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日现有答案的评论)

这个方法不起作用:

let mirror = Mirror(reflecting: mySecKeyObject)

for case let (label?, value) in mirror.children {
    print (label, value)
}

这导致控制台输出以下内容:

Some <Raw SecKeyRef object>

不确定字符串 "Some" 的含义。

此外,mirror.descendant("exponent")(或 "modulus")的结果为 nil,即使在控制台中打印原始对象时,我可以清楚地看到这些属性存在,并且实际上它们是已填充的。

另外,如果可能的话,我想避免保存到钥匙串,以NSData读取,然后从钥匙串中删除。如悬赏说明所述,如果这是唯一可能的方法,请引用权威参考资料。感谢迄今为止提供的所有答案。


你看过这个答案了吗?或者任何列在这里的答案?它们是Obj-C,但翻译起来并不困难。 - Caleb
2
很不幸,我在这个主题上真的找不到其他任何信息,而且最近的任何东西都被忽略了。我对这个主题没有任何特定的知识。祝你好运,很抱歉我无法提供帮助! - Caleb
https://dev59.com/pIbca4cB1Zd3GeqPXZLq - Cœur
仅作为一则附注:在iOS应用中使用SecKeyCopyModulus将被苹果拒绝:我从iTunesConnect得到了以下信息: 我们发现您最近提交的“.....”存在一个或多个问题。为了处理您的提交,必须纠正以下问题: 非公共API使用: 该应用程序在.....中引用了非公共符号:_SecKeyCopyModulus,请注意。 - ingconti
我有一个函数,可以给出模数和指数,它会计算出公钥是什么 -- 模数和指数在一起就是公钥!其余的只是包装/装饰。 - Raphael
显示剩余8条评论
9个回答

阿里云服务器只需要99元/年,新老用户同享,点击查看详情
12

确实可以使用既不需要钥匙链也不需要私有API的方法提取模数和指数。

有一个(公开但未记录在文档中)函数 SecKeyCopyAttributes,它从 SecKey 中提取一个 CFDictionary。属性键的一个有用来源是 SecItemConstants.c

检查此字典的内容,我们发现一个条目 "v_Data" : <binary>。它的内容是 DER编码的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解析器,因此这段代码未经测试,请小心!)


请注意,您可以通过非常相似的方式提取私钥的详细信息。 使用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
 }

手动解析这个可能不太明智,因为任何一个整数都可能被填充。


Nota bene: 如果公钥是在 macOS 上生成的,则其格式与上面给出的结构有所不同,它会被包装如下:

SEQUENCE {
    id              OBJECTID,
    PublicKey       BITSTRING
}

比特串是上述DER编码ASN的形式。


请注意:如果您想更具攻击性地编程,SecKeyCopyExternalRepresentation(publicKey, nil) 可以直接给您提供 v_Data,正如 jamone 指出的 - Raphael
酷,感谢您的见解!您提到的完整ASN解析器是否可在某处获取? - dlggr
@dnlggr 到目前为止,谢谢提醒;我会问一下是否允许开源。 (如果我没记错的话,它不是完整或好的实现,但大多数类型都包括在内。) - Raphael
太好了!感谢您的关注。 - dlggr

9

更新 下面的答案可能会因使用非公共API而导致您的应用被拒绝。

答案在苹果开源网站的SecRSAKey.h文件中(安全性是苹果开源的代码的一部分)。该文件不大,除其他内容外,它声明了以下两个重要函数:

CFDataRef SecKeyCopyModulus(SecKeyRef rsaPublicKey);
CFDataRef SecKeyCopyExponent(SecKeyRef rsaPublicKey);
你可以将这些函数添加到桥接头文件中,以便从Swift中调用它们。在此过程中,你可以从CFDataRef切换到NSData*,因为这两种类型是Toll-Free Bridged的。
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是操作系统的关键组件,苹果公司不太可能对它进行重大更改。

已在iOS 8.1、iOS 9.2和OSX 10.10.5上进行了测试,并且代码在这三个平台上均可运行。


该注释适用于 SecKeyCreateRSAPublicKey,假设您自己创建密钥。在您的情况下,您使用 SecTrustCopyPublicKey 获得密钥,该方法从使用 SecCertificateCreateWithData 从文件加载的证书中提取密钥。 - Cristik
哇,我不确定我是怎么错过了这个。理论上,这是完全有道理的。我需要进行一些实际测试来看看它是否有效。再次感谢你的帮助。关于赏金,很遗憾我已经授予了它,没有意识到您提到的这个答案的额外部分。我的错。 - Josh Beam
@JoshBeam,期待你的结果,看看我的努力是否白费 :) - Cristik
@Cristik,你可能会对这个解决方案感兴趣,它不需要使用私有API。(话虽如此,我更喜欢使用一个单一函数而不是手动挖掘二进制数据...) - Raphael
1
想要通知大家,从今天开始,如果您使用那种方法,您的应用将被阻止。我刚刚遇到了这个问题。该应用程序引用了 ___ 中的非公共符号:_SecKeyCopyModulus。 - Patrick
显示剩余5条评论

7
我曾经尝试进行SSL公钥固定,但遇到了相同的问题。API基本上不存在,我找到的解决方案是将其放在Keychain中,然后可以检索为NSData(然后可以进行Base64编码)。这很糟糕,但是在大约一天的研究后(没有使用我的应用程序捆绑OpenSSL),这是我能找到的唯一东西。 我将我的一些代码移植到了Swift,但我没有进行过太多测试,所以我不能百分之百确定它是否有效:https://gist.github.com/chedabob/64a4cdc4a1194d815814 它基于这个Obj-C代码(我相信它可以工作,因为它在一个生产应用程序中):https://gist.github.com/chedabob/49eed109a3dfcad4bd41

谢谢您的回答,我会查看这些资源,并在使用它们时通知您。 - Josh Beam
你好,我已经颁发了悬赏奖励,因为这是目前为止最好的答案。然而,我又开了一个新的悬赏,希望能找到权威的参考资料和其他方法(如果您感兴趣,请查看新的悬赏描述)。谢谢。 - Josh Beam
这个答案从苹果方面来说可以吗?还是我使用它会被拒绝? - Oz Shabat

4

我找到了如何获取一个SecKey的数据。

let publicKey: SecKey = ...
let data = SecKeyCopyExternalRepresentation(publicKey, nil)

这似乎很有效,我已经成功地比较了公钥。

这是使用Swift 3(Xcode 8 beta 3)完成的。


1
谢谢回复。SecKeyCopyExternalRepresentation的返回值是什么? - Josh Beam
这是一个 CFData // 它们匹配 }``` - jamone
4
iOS 10 之前的版本是否有实现这个功能的方法? - Bocaxica

3

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内部的属性。 - Josh Beam
你真的测试过这个吗? - Raphael

3
我在一个废弃项目中找到了一个单一的Obj-c ASN.1解析器重新实现,它似乎可以工作。问题是,它使用了很多指针技巧,我不知道如何将其翻译成Swift(甚至不确定其中一些是否可能)。应该可以创建一个Swift友好的包装器来包装它,因为它只接受NSData作为输入。 网络上的所有内容都使用密钥链存储和检索技巧来获取公钥数据,即使是像TrustKit这样非常流行的库也是如此。我在Apple SecKeyRef文档中找到了根本原因的参考(我想是这样的): “存储在密钥链中的密钥的SecKeyRef对象可以安全地转换为SecKeychainItemRef,以进行密钥链项的操作。另一方面,如果SecKeyRef未存储在密钥链中,则将对象强制转换为SecKeychainItemRef并将其传递给Keychain Services函数会返回错误。”

由于目前iOS上没有可用的SecCertificateCopyValues,您只能限制于解析证书数据或执行钥匙串项目操作。


2

非常感谢您的回答,但这并不起作用。如果我可以提供任何其他信息,请告诉我。 - Josh Beam
到底是什么出了问题?我记得为了让它在Swift中运行,需要进行很多转换和弯曲。 - Tobi Nary
我可能有所误解。能否提供一个最小的示例,说明如何使用SecCertificateCopyData()SecKeyRef对象中提取公钥或指数/模数?谢谢。 - Josh Beam
很抱歉,看起来您需要一个 SecCertificateRef。但是在您从中复制 SecKeyRef 的信任中,已经有一个与您的公钥匹配的证书了。 - Tobi Nary

1

来自如何将未管理的<SecKey>编码为base64以发送到另一台服务器?

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
}

但是如果你想避免添加到钥匙串,你可以使用Mirror:

let mirrorKey = Mirror(reflecting: secKey)
let exponent = mirrorKey.descendant("exponent")
let modulus = mirrorKey.descendant("modulus");
[编辑:据Josh所说,镜像没有工作]


非常感谢您的回答。不幸的是,Mirror无法工作。这意味着mirrorKey.descendant("exponent")mirrorKey.descendant("modulus")都会返回nil - Josh Beam
@JoshBeam,我想提供帮助。我该如何获取SecTrust对象进行测试? - Cœur
嘿,非常感谢。我在我的问题底部添加了一些内容,在“编辑”下面显示了我用来生成对象的函数。基本上,我从原始证书字节流开始,然后从中创建一个SecKeyRef。目前,我唯一能够解析公钥的方法是逐字节地提取公钥(根据已经拥有其中一个证书的数据知道公钥应该是什么,进行反向工程)。更好的方法似乎是使用SecKeyRef,因此我提出了这个问题。 - Josh Beam
请告诉我是否需要更多信息。 - Josh Beam

0

我根据StackOverflow上其他人的答案编写了这个代码。目前我正在生产环境中使用它,但如果有另一种不需要写入钥匙串的解决方案,我也很乐意使用。

- (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上了?另外,你有没有找到一种不需要保存到钥匙串的答案?谢谢。 - Josh Beam
不,我还在寻找其他解决方案。你可以使用Swift来实现同样的功能。我只是检查密钥是否存在于密钥链中。我遇到了这样一种情况,即密钥已添加但未被删除。这会导致应用程序在此之后无法获取公钥数据而失败。 - sahara108
@sahara108 你可能会对我的回答感兴趣。从某种意义上说,通过走一条漫长的路线是有可能实现的。 - Raphael

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,