2009-04-27 207 views
7

我有幾個NSString對象表示一個RSA公鑰 - 私鑰對(不是由SecKeyCreatePair生成,而是由外部加密庫生成)。我如何從這些NSString對象創建SecKeyRef對象(這是SecKeyDecrypt/Encrypt方法所必需的)?將RSA密鑰導入iPhone鑰匙串?

我需要先將它們導入鑰匙串嗎?如果是這樣,怎麼樣?

謝謝!

+1

我們很早就想到了 - 如果傳遞正確的字典屬性,SecItemAdd()將起作用。請參閱http://hg.mozilla.org/services/fx-home/file/tip/Sources/NetworkAndStorage/CryptoUtils.m#l931 – Anant 2011-03-14 19:10:41

+0

所有的魔法都在keyData參數中,您從某處(downloadPrivateKeyBundle)獲取並解密。這個NSData blob的格式是什麼? – Uri 2012-08-20 09:21:32

回答

1

我從the MYcrypto library挖這個代碼(BSD許可證)。它似乎是做你想做的。

SecKeyRef importKey(NSData *data, 
        SecExternalItemType type, 
        SecKeychainRef keychain, 
        SecKeyImportExportParameters *params) { 
    SecExternalFormat inputFormat = (type==kSecItemTypeSessionKey) ?kSecFormatRawKey :kSecFormatUnknown; 
    CFArrayRef items = NULL; 

    params->version = SEC_KEY_IMPORT_EXPORT_PARAMS_VERSION; 
    params->flags |= kSecKeyImportOnlyOne; 
    params->keyAttributes |= CSSM_KEYATTR_EXTRACTABLE; 
    if (keychain) { 
     params->keyAttributes |= CSSM_KEYATTR_PERMANENT; 
     if (type==kSecItemTypeSessionKey) 
      params->keyUsage = CSSM_KEYUSE_ENCRYPT | CSSM_KEYUSE_DECRYPT; 
     else if (type==kSecItemTypePublicKey) 
      params->keyUsage = CSSM_KEYUSE_ENCRYPT | CSSM_KEYUSE_VERIFY | CSSM_KEYUSE_WRAP; 
     else if (type==kSecItemTypePrivateKey) 
      params->keyUsage = CSSM_KEYUSE_DECRYPT | CSSM_KEYUSE_SIGN; 
    } 
    if (!check(SecKeychainItemImport((CFDataRef)data, NULL, &inputFormat, &type, 
            0, params, keychain, &items), 
       @"SecKeychainItemImport")) 
     return nil; 
    if (!items || CFArrayGetCount(items) != 1) 
     return nil; 
    SecKeyRef key = (SecKeyRef)CFRetain(CFArrayGetValueAtIndex(items,0)); 
    CFRelease(items); 
    return key; // caller must CFRelease 
} 
3

因此,在iOS中,鑰匙串是沙盒,AFAIK。這意味着,除非您另行指定,否則無論您放入鑰匙串,只能通過您的應用程序和您的應用程序訪問。您必須在項目設置中啓用鑰匙串共享功能

既然這樣,您肯定可以導入數據。由於它們是NSString對象,因此您首先必須將其轉換爲NSData對象才能正確導入它們。最有可能的,他們在編碼爲Base64,所以你必須要扭轉:

NSData *decodedData = [[NSData alloc] initWithBase64EncodedString:base64String options:0]; 

既然這樣做了,你可以用這種方法既密鑰保存到鑰匙鏈,並獲得SecKeyRef:

/** 
* key: the data you're importing 
* keySize: the length of the key (512, 1024, 2048) 
* isPrivate: is this a private key or public key? 
*/ 
- (SecKeyRef)saveKeyToKeychain:(NSData *)key keySize:(NSUInteger)keySize private:(BOOL)isPrivate { 
    OSStatus sanityCheck = noErr; 
    NSData *tag; 
    id keyClass; 

    if (isPrivate) { 
     tag = privateTag; 
     keyClass = (__bridge id) kSecAttrKeyClassPrivate; 
    } 
    else { 
     tag = publicTag; 
     keyClass = (__bridge id) kSecAttrKeyClassPublic; 
    } 

    NSDictionary *saveDict = @{ 
      (__bridge id) kSecClass : (__bridge id) kSecClassKey, 
      (__bridge id) kSecAttrKeyType : (__bridge id) kSecAttrKeyTypeRSA, 
      (__bridge id) kSecAttrApplicationTag : tag, 
      (__bridge id) kSecAttrKeyClass : keyClass, 
      (__bridge id) kSecValueData : key, 
      (__bridge id) kSecAttrKeySizeInBits : [NSNumber numberWithUnsignedInteger:keySize], 
      (__bridge id) kSecAttrEffectiveKeySize : [NSNumber numberWithUnsignedInteger:keySize], 
      (__bridge id) kSecAttrCanDerive : (__bridge id) kCFBooleanFalse, 
      (__bridge id) kSecAttrCanEncrypt : (__bridge id) kCFBooleanTrue, 
      (__bridge id) kSecAttrCanDecrypt : (__bridge id) kCFBooleanFalse, 
      (__bridge id) kSecAttrCanVerify : (__bridge id) kCFBooleanTrue, 
      (__bridge id) kSecAttrCanSign : (__bridge id) kCFBooleanFalse, 
      (__bridge id) kSecAttrCanWrap : (__bridge id) kCFBooleanTrue, 
      (__bridge id) kSecAttrCanUnwrap : (__bridge id) kCFBooleanFalse 
    }; 

    SecKeyRef savedKeyRef = NULL; 
    sanityCheck = SecItemAdd((__bridge CFDictionaryRef) saveDict, (CFTypeRef *)&savedKeyRef); 
    if (sanityCheck != errSecSuccess) { 
     LOGGING_FACILITY1(sanityCheck != noErr, @"Problem saving the key to keychain, OSStatus == %d.", sanityCheck); 
    } 

    return savedKeyRef; 
} 

以後,如果你想從鑰匙串獲取SecKeyRef,您可以使用此:

- (SecKeyRef)getKeyRef:(BOOL)isPrivate { 
    OSStatus sanityCheck = noErr; 
    NSData *tag; 
    id keyClass; 
    if (isPrivate) { 
     if (privateKeyRef != NULL) { 
      // already exists in memory, return 
      return privateKeyRef; 
     } 
     tag = privateTag; 
     keyClass = (__bridge id) kSecAttrKeyClassPrivate; 
    } 
    else { 
     if (publicKeyRef != NULL) { 
      // already exists in memory, return 
      return publicKeyRef; 
     } 
     tag = publicTag; 
     keyClass = (__bridge id) kSecAttrKeyClassPublic; 
    } 

    NSDictionary *queryDict = @{ 
      (__bridge id) kSecClass : (__bridge id) kSecClassKey, 
      (__bridge id) kSecAttrKeyType : (__bridge id) kSecAttrKeyTypeRSA, 
      (__bridge id) kSecAttrApplicationTag : tag, 
      (__bridge id) kSecAttrKeyClass : keyClass, 
      (__bridge id) kSecReturnRef : (__bridge id) kCFBooleanTrue 
    }; 

    SecKeyRef keyReference = NULL; 
    sanityCheck = SecItemCopyMatching((__bridge CFDictionaryRef) queryDict, (CFTypeRef *) &keyReference); 
    if (sanityCheck != errSecSuccess) { 
     NSLog(@"Error trying to retrieve key from server. isPrivate: %d. sanityCheck: %li", isPrivate, sanityCheck); 
    } 

    if (isPrivate) { 
     privateKeyRef = keyReference; 
    } 
    else { 
     publicKeyRef = keyReference; 
    } 
    return keyReference; 
} 
3

編輯:使用下面的方法,我們能夠導入密鑰高達4096尺寸的任何RSA鍵更大這似乎被鑰匙扣拒絕了。我們取得了成功的地位,但我們沒有得到關鍵的參考。

只是關於導入RSA私鑰/公鑰的快速說明。就我而言,我需要導入由OpenSSL生成的私鑰。

This project做了我想要的大部分,只要把它放入鑰匙串。正如你所看到的,它只是有一個關鍵數據,你可以將關鍵數據放到關鍵數據中,鑰匙串可以從關鍵字中計算出數據塊的大小等。鑰匙串支持ASN.1編碼密鑰。

將密鑰導出到文件時,很可能是PEM文件。一個PEM文件只是一個base64編碼的DER結構。 DER結構是一個通用結構,但在OpenSSL的情況下,它通常是ASN.1編碼的私鑰或公鑰。

ASN.1結構顯示得很好here。請在閱讀並理解如何閱讀ASN.1結構之前,嘗試並擺弄它,否則導入其他大小的密鑰將失敗。

我顯然沒有足夠的「聲望」發佈超過2個鏈接。因此,對於下面的示例粘貼base64信息(除了--- BEGIN * KEY ---和--- END * KEY --- ---之外的所有內容:lapo.it/asn1js

如果你看起來像iOS項目我鏈接了,你會看到它們包含了示例密鑰,將私鑰粘貼到ASN.1解碼器中,你會注意到你有一個SEQUENCE標記,後面跟着幾個INTEGER值

現在粘貼在公鑰中。你會注意到公鑰與私鑰有兩個共同的信息:模數和指數,私鑰是第二個和第三個INTEGER值,它的頂部也有一些信息。 2個額外的SEQUENCE,一個OBJECT ID,NULL和BIT STRING標籤。

您還會在項目中注意到他調用了一個特殊函數來處理該公鑰。它所做的是去除所有標題信息,直到它到達最內層的SEQUENCE標記。此時,他將其視爲完全像私鑰,並將其放入鑰匙串中。

爲什麼要這樣做,而不是其他?查看頁眉和頁腳文本。私鑰說--- BEGIN RSA PRIVATE KEY ---,公鑰說--- BEGIN PUBLIC KEY ---。您將在公鑰中看到的對象ID是:1.2.840.113549.1.1.1。這是一個ID,它是一個靜態標記,用於將所包含的密鑰標識爲RSA類型密鑰。

由於私鑰在前導碼中有RSA,因此它假定它是一個RSA密鑰,並且該頭ASN.1信息不需要標識該密鑰。公鑰只是一個通用密鑰,所以需要標頭來標識它是什麼類型的密鑰。

鑰匙串不會導入帶有此ASN.1標頭的RSA密鑰。您需要將它一直剝離到最後一個SEQUENCE。此時你可以將它放入鑰匙串中,鑰匙串可以派生出塊大小和其他關鍵屬性。

因此,如果BEGIN RSA PRIVATE KEY在那裏,則不需要進行剝離。如果是 - BEGIN PRIVATE KEY ---,則需要在將這些首標頭放入鑰匙串之前將其剝掉。

就我而言,我還需要公鑰。一旦我們把私鑰成功放入(我們可能錯過了某些東西),我們無法想象我們能從鑰匙串中獲得它,所以我們實際上使用私鑰創建了一個ASN.1公鑰,並將其導入到keycahin中。在私鑰(在ASN.1頭部剝離之後),你將有一個SEQUENCE標記,隨後是3個INTEGER標記(在這之後有更多的INTEGERS,但前3個是我們所關心的)。

第一個是VERSION標籤。第二個是模數,第三個是公開指數。

查看公鑰(在ASN.1標頭剝離後)您會看到SEQUENCE後跟2個INTEGERS。你猜對了,這是私鑰的模數和公開指數。

因此,所有你需要做的是:

  1. 抓住從私有密鑰
  2. 模數和公開指數在緩衝區中創建一個序列標籤及其長度設定爲[模長度] + [指數長度]。 (將這些字節寫入緩衝區時,您很可能需要反轉字節的字節序,至少我做了。)
  3. 添加您的私鑰
  4. 抓起模量數據添加你的私鑰

抓起指數數據這就是所有你需要做的,創建你的私有密鑰導入了公鑰。似乎沒有太多的信息用於導入您不在設備上生成的RSA密鑰,並且我聽說設備上生成的密鑰不包含這些ASN.1標頭,但我從未試過。我們的鑰匙非常大,需要很長時間才能生成。我發現的唯一選擇是使用OpenSSL,在那裏你必須編譯你自己的iOS版本。我寧願在可能的情況下使用安全框架。

我還是比較新的iOS開發,我敢肯定有人知道一個簡單的函數,做所有這些,我找不到,我看。這似乎工作正常,直到一個更簡單的API可用於處理密鑰。

最後一點:項目中包含的私鑰具有BIT STRING標籤,但是我從一個OpenSSL生成的私鑰導入的私鑰具有標籤OCTET STRING。