我可以从Swift中的SecKeyRef对象获得模数或指数吗?

时间:2015-12-22 05:10:42

标签: ios swift encryption cryptography public-key-encryption

在Swift中,我通过在一些原始X509证书数据上调用SecKeyRef来创建一个SecTrustCopyPublicKey对象。这就是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日对现有答案的评论)

这不起作用:

let mirror = Mirror(reflecting: mySecKeyObject)

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

这会在控制台中产生此输出:

Some <Raw SecKeyRef object>

不确定字符串&#34; Some&#34;装置

此外,mirror.descendant("exponent")(或&#34;模数&#34;)会产生nil,即使在控制台中打印原始对象时,我也可以清楚地看到这些属性存在,并且他们实际上是人口稠密的。

此外,如果可能,我希望避免必须保存到钥匙串,阅读为NSData,然后从钥匙串中删除。如赏金说明中所述,如果这是唯一可行的方法,请引用权威参考。感谢您提供的所有答案。

9 个答案:

答案 0 :(得分:7)

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

答案在于Apple的开源网站SecRSAKey.h文件(安全性是Apple开源的代码的一部分)。文件不大,除其他外,它声明了以下两个重要的功能:

CFDataRef SecKeyCopyModulus(SecKeyRef rsaPublicKey);
CFDataRef SecKeyCopyExponent(SecKeyRef rsaPublicKey);

您可以将这些功能添加到桥接标头中,以便能够从Swift中调用它们,同时执行此操作时,您可以从CFDataRef切换到NSData*两种类型的免费桥接:< / p>

NSData* SecKeyCopyModulus(SecKeyRef rsaPublicKey);
NSData* SecKeyCopyExponent(SecKeyRef rsaPublicKey);

Demo 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上测试过,代码适用于所有三个平台。

答案 1 :(得分:6)

我一直试图进行SSL公钥锁定。 API几乎不存在,我找到的解决方案是将它放在Keychain中,然后可以将其作为NSData检索(然后可以进行Base64编码)。这是可怕的,但是经过一天左右的研究后我才能找到唯一的东西(不需要在我的应用程序中捆绑OpenSSL)。

我将部分代码移植到Swift上,但我没有对它进行过多次测试,所以我不能100%确定它的工作原理:https://gist.github.com/chedabob/64a4cdc4a1194d815814

它基于这个Obj-C代码(我相信它在生产应用程序中有效):https://gist.github.com/chedabob/49eed109a3dfcad4bd41

答案 2 :(得分:6)

确实可以使用钥匙链私有API来提取模数和指数。

public but undocumented)函数SecKeyCopyAttributesCFDictionary中提取SecKey。 属性键的有用来源是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解析器,所以这段代码没有经过测试,要小心!)

请注意,您可以以非常相同的方式提取私有键的详细信息。使用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。

答案 3 :(得分: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
*/

答案 4 :(得分:3)

我在一个废弃的项目中找到了一个单独的Obj-c重新实现ASN.1解析器,这似乎有效。问题是,它使用了大量的指针技巧,我不知道如何转换成Swift(甚至不确定其中有些可能)。应该可以在它周围创建一个快速友好的包装器,因为它唯一的输入是NSData。

网上的所有东西都在使用商店并在Keychain技巧中检索以获取酒吧密钥数据,甚至是真正流行的lib,如TrustKit。我在Apple docs on SecKeyRef中找到了根本原因的引用(我认为):

  

存储在钥匙串中的密钥的SecKeyRef对象可以是   安全地转换为SecKeychainItemRef以作为钥匙串进行操作   项目。另一方面,如果SecKeyRef没有存储在钥匙串中,   将对象转换为SecKeychainItemRef并将其传递给Keychain   服务函数返回错误。

由于目前iOS上没有SecCertificateCopyValues,因此您只能解析证书数据或执行Keychain Item shuffle。

答案 5 :(得分:3)

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

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

这似乎运作良好,我已经能够成功比较公钥。

这是在Swift 3(Xcode 8 beta 3)

答案 6 :(得分:2)

您是否考虑过使用<script src="https://cdnjs.cloudflare.com/ajax/libs/prefixfree/1.0.7/prefixfree.min.js"></script> <h3>Red to White at 60%</h3> <div class='menu-60'>Text</div> <div class='menu-60'>Text</div> <div class='menu-60'>Text</div> <br/> <h3>Red to White at 40%</h3> <div class='menu-40'>Text</div> <div class='menu-40'>Text</div> <div class='menu-40'>Text</div> <br/> <h3>Red to White at 80%</h3> <div class='menu-80'>Text</div> <div class='menu-80'>Text</div> <div class='menu-80'>Text</div>?结果SecCertificateCopyData()是免费桥接的,我认为。

请参阅https://developer.apple.com/library/ios/documentation/Security/Reference/certifkeytrustservices/以查看API的相关文档。

答案 7 :(得分:1)

来自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");

[编辑:镜子根据乔希不工作]

答案 8 :(得分: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;
}