Can I get the modulus or exponent from a SecKeyRef object in Swift?

后端 未结 9 2010
灰色年华
灰色年华 2020-12-05 13:34

In Swift, I created a SecKeyRef object by calling SecTrustCopyPublicKey on some raw X509 certificate data. This is what this SecKeyRef object looks like.

<
相关标签:
9条回答
  • 2020-12-05 14:12

    I wrote this one base on some other's answer in stackoverflow. Currently I am using it in my production but I am happy to use another solution that doesn't require to write into keychain.

    - (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;
    }
    
    0 讨论(0)
  • 2020-12-05 14:13

    I found how to get data for a SecKey.

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

    This seems to work well and I have been able to successfully compare public keys.

    This is in Swift 3 (Xcode 8 beta 3)

    0 讨论(0)
  • 2020-12-05 14:14

    It is indeed possible to extract modulus and exponent using neither keychains nor private API.

    There is the (public but undocumented) function SecKeyCopyAttributes which extracts a CFDictionary from a SecKey. A useful source for attribute keys is SecItemConstants.c

    Inspecting the content of this dictionary, we find an entry "v_Data" : <binary>. Its content is DER-encoded ASN for

    SEQUENCE {
        modulus           INTEGER, 
        publicExponent    INTEGER
    }
    

    Be aware that integers are padded with a zero byte if they are positive and have a leading 1-bit (so as not to confuse them with a two-complement negative number), so you may find one byte more than you expect. If that happens, just cut it away.

    You can implement a parser for this format or, knowing your key size, hard-code the extraction. For 2048 bit keys (and 3-byte exponent), the format turns out to be:

    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>
    

    For a total of 10 + 1? + 256 + 3 = 269 or 270 bytes.

    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)
    }
    

    (I ended up writing a full ASN parser, so this code is not tested, beware!)


    Note that you can extract details of private keys in very much the same way. Using DER terminology, this is the format of 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
     }
    

    Parsing this by hand is probably ill-advised since any of the integers may have been padded.


    Nota bene: The format of the public key is different if the key has been generated on macOS; the structure given above is wrapped like so:

    SEQUENCE {
        id              OBJECTID,
        PublicKey       BITSTRING
    }
    

    The bit-string is DER-encoded ASN of the form above.

    0 讨论(0)
  • 2020-12-05 14:14

    From 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
    }
    

    But if you want to avoid adding to keychain, you can use Mirror:

    let mirrorKey = Mirror(reflecting: secKey)
    let exponent = mirrorKey.descendant("exponent")
    let modulus = mirrorKey.descendant("modulus");
    

    [edit: Mirror not working according to Josh]

    0 讨论(0)
  • 2020-12-05 14:23

    Update The answer below might result in your app being rejected due to usage of non-public API's.

    The answer lies in the SecRSAKey.h file from Apple's opensource website (Security is part of the code that Apple opensourced). The file is not big, and among other stuff it declares the following two important functions:

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

    You can add those functions to your bridging header to be able to call them from Swift, also while doing this you can switch from CFDataRef to NSData* as the two types toll-free bridged:

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

    Demo Swift usage:

    let key = bytesToPublicKey(keyData)
    let modulus = SecKeyCopyModulus(key)
    let exponent = SecKeyCopyExponent(key)
    print(modulus, exponent)
    

    This is a private API though, and there might be a chance it will no longer be available at some point, however I looked over the versions of Security made public (http://www.opensource.apple.com/source/Security), and looks like the two functions are present in all of them. More, since Security is a critical component of the OS, it's unlikely Apple will do major changes over it.

    Tested on iOS 8.1, iOS 9.2, and OSX 10.10.5, and the code works on all three platforms.

    0 讨论(0)
  • 2020-12-05 14:30

    SecKeyRefis a struct so there is a chance that it can be reflected with Mirror() to retrieve the wanted values.

    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
    */
    
    0 讨论(0)
提交回复
热议问题