2009-04-27 42 views
7

Tôi có một vài đối tượng NSString đại diện cho một cặp khóa công khai RSA (không được tạo bởi SecKeyCreatePair, nhưng bởi một thư viện mật mã bên ngoài). Làm thế nào tôi có thể tạo các đối tượng SecKeyRef (được yêu cầu cho các phương thức SecKeyDecrypt/Encrypt) từ các đối tượng NSString này?Nhập khóa RSA vào iPhone keychain?

Tôi có cần phải nhập chúng vào Keychain trước không? Nếu vậy, làm thế nào?

Cảm ơn!

+1

Chúng tôi đã tìm ra điều này trong một thời gian trước - SecItemAdd() sẽ hoạt động nếu chuyển các thuộc tính từ điển chính xác. Xem http://hg.mozilla.org/services/fx-home/file/tip/Sources/NetworkAndStorage/CryptoUtils.m#l931 – Anant

+0

tất cả phép thuật nằm trong đối số keyData, mà bạn nhận được từ đâu đó (downloadPrivateKeyBundle) và giải mã . Định dạng của đốm màu NSData này là gì? – Uri

Trả lời

1

Tôi đào mã này lên (giấy phép BSD) từ the MYcrypto library. Nó dường như làm những gì bạn muốn.

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 
} 
+0

Mã không thực sự hoạt động trên iPhone SDK, mặc dù việc phát triển Mac OS X chung trên máy tính thực sự là tốt. Dù sao cũng cảm ơn bạn! – Anant

+0

Toss trong một số chỉ thị trình biên dịch '#if TARGET_IPHONE_SIMULATOR' và' # else' khác và bạn có thể nhận được thứ gì đó hoạt động trong trình mô phỏng và thiết bị. (Bạn đã có phiên bản Mac ở trên, chỉ cần truy tìm các tương tự của iPhone. Tôi đang ở giữa nhiệm vụ tương tự hoặc tôi chỉ tự trả lời.) – bbrown

+0

@bbrown: không hữu ích ở tất cả các anh chàng –

3

Vì vậy, trong iOS, keychain là hộp cát, AFAIK. Điều này có nghĩa là bất cứ điều gì bạn đưa vào keychain chỉ có thể truy cập bởi ứng dụng của bạn và ứng dụng của bạn một mình trừ khi bạn chỉ định khác. Bạn phải bật tính năng Chia sẻ Keychain theo Khả năng trong cài đặt dự án.

Bây giờ, đó là ngoài con đường, bạn chắc chắn có thể nhập dữ liệu. Vì chúng là các đối tượng NSString, trước tiên bạn phải chuyển đổi đối tượng đó thành các đối tượng NSData để nhập chúng một cách chính xác. Nhiều khả năng, họ đang mã hóa trong Base64, vì vậy bạn sẽ phải đảo ngược rằng:

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

Bây giờ đã xong, bạn có thể sử dụng phương pháp này để cả hai tiết kiệm chìa khóa của bạn để keychain và nhận được 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; 
} 

sau đó, nếu bạn muốn lấy lại SecKeyRef từ keychain, bạn có thể sử dụng này:

- (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

EDIT: sử dụng phương pháp dưới đây, chúng tôi đã có thể nhập các phím lên đến kích thước 4096. Bất kỳ RSA Phím số lớn hơn một điều này dường như bị từ chối bởi keychain. Chúng tôi lấy lại trạng thái thành công, nhưng chúng tôi không nhận được tham chiếu đến khóa.

Chỉ cần lưu ý nhanh về việc nhập khóa riêng tư/công khai RSA. Trong trường hợp của tôi, tôi cần nhập một khóa riêng do OpenSSL tạo ra.

This project thực hiện hầu hết những gì tôi muốn theo như đặt nó vào trong keychain. Như bạn có thể thấy nó chỉ có một keydata, nơi bạn shove dữ liệu quan trọng vào nó và keychain con số ra kích thước khối, vv từ khóa. Keychain hỗ trợ khóa được mã hóa ASN.1.

Khi bạn xuất một khóa vào một tệp, đó có thể là tệp PEM. Tệp PEM chỉ là cấu trúc DER được mã hóa base64. Cấu trúc DER là một cấu trúc tổng quát, nhưng trong trường hợp OpenSSL, nó thường là khóa riêng hoặc khóa công khai được mã hóa ASN.1.

Cấu trúc ASN.1 được hiển thị khá tốt here. XIN đọc và hiểu làm thế nào để đọc cấu trúc ASN.1 trước khi bạn cố gắng và fiddle với điều này, hoặc nhập một khóa của kích thước khác sẽ thất bại.

Tôi dường như không có đủ "danh tiếng" để đăng hơn 2 liên kết.Vì vậy, đối với ví dụ sau dán thông tin base64 (tất cả mọi thứ ngoại trừ phím --- BEGIN * KEY --- và --- END * KEY --- tại: lapo.it/asn1js.

Nếu bạn nhìn ay iOS Dự án tôi đã liên kết, bạn sẽ thấy chúng bao gồm các khóa mẫu. Dán khóa cá nhân vào bộ giải mã ASN.1.Bạn sẽ nhận thấy rằng bạn có một thẻ SEQUENCE được theo sau bởi một vài giá trị INTEGER. Bạn sẽ nhận thấy rằng khóa công khai có hai phần thông tin chung với khóa riêng. Mô-đun và số mũ Trong khóa riêng tư, đây là giá trị INTEGER thứ hai và thứ ba, nó cũng có một số thông tin ở trên cùng. 2 SEQUENCE bổ sung, thẻ OBJECT ID, NULL và BIT STRING.

Bạn cũng sẽ nhận thấy trong dự án rằng ông gọi một chức năng đặc biệt để xử lý khóa công khai đó. Những gì nó làm là dải tất cả các thông tin tiêu đề cho đến khi nó được vào thẻ SEQUENCE trong cùng. Tại thời điểm đó, ông xử lý nó chính xác như khóa riêng và có thể đặt nó vào keychain.

Tại sao điều này cho một và không phải là khác? Nhìn vào văn bản đầu trang và chân trang. Khóa riêng nói --- BEGIN RSA PRIVATE KEY ---, khóa công khai nói --- BEGIN PUBLIC KEY ---. ID đối tượng bạn sẽ thấy trong khóa công khai là: 1.2.840.113549.1.1.1. Đây là một ID là một thẻ tĩnh xác định khóa có chứa dưới dạng khóa loại RSA.

Vì khóa riêng có RSA trong phần mở đầu, nó được coi là khóa RSA và thông tin tiêu đề ASN.1 không cần thiết để xác định khóa. Khóa công khai chỉ là một khóa chung, do đó, một tiêu đề được yêu cầu để xác định loại khóa đó là gì.

Keychain sẽ KHÔNG nhập khóa RSA với tiêu đề ASN.1 này. Bạn cần phải tách nó tất cả các cách để SEQUENCE cuối cùng. Tại thời điểm đó bạn có thể đặt nó vào keychain, và keychain đã có thể lấy được kích thước khối và các thuộc tính quan trọng khác.

Vì vậy, nếu BEGIN RSA PRIVATE KEY có ở đó, bạn không cần phải thực hiện tước. Nếu nó là - BEGIN PRIVATE KEY ---, bạn sẽ cần phải loại bỏ các tiêu đề ban đầu trước khi đưa nó vào keychain.

Trong trường hợp của tôi, tôi cũng cần khóa công khai. Chúng tôi không thể tìm ra cách để lấy nó từ keychain khi chúng tôi đặt khóa cá nhân thành công (chúng tôi có thể vừa bỏ sót điều gì đó), vì vậy chúng tôi đã tạo khóa công cộng ASN.1 từ khóa riêng tư và nhập khóa đó vào keycahin.

Trong khóa riêng tư (Sau khi loại bỏ tiêu đề ASN.1), bạn sẽ có thẻ SEQUENCE theo sau là 3 thẻ INTEGER (có nhiều INTEGERS sau này, nhưng 3 đầu tiên là tất cả chúng ta quan tâm).

Thẻ đầu tiên là thẻ VERSION. Cái thứ hai là Modulus và phần thứ ba là số mũ công khai.

Nhìn vào khóa công khai (sau khi loại bỏ tiêu đề ASN.1) Bạn thấy SEQUENCE theo sau là 2 INTEGERS. Bạn đoán nó, đây là mô-đun và số mũ công khai từ khóa riêng.

Vì vậy, tất cả các bạn cần làm là:

  1. Grab các mô đun và số mũ công khai từ các tin quan trọng
  2. Tạo một thẻ SEQUENCE trong một bộ đệm và thiết lập chiều dài của nó tới [mô đun chiều dài] + [số mũ chiều dài]. (Khi viết các byte này vào bộ đệm, bạn rất có thể sẽ cần phải đảo ngược phần cuối của các byte.)
  3. Thêm dữ liệu mô đun mà bạn đã lấy từ các tin quan trọng
  4. Thêm dữ liệu số mũ mà bạn đã lấy từ các tin quan trọng

Đó là tất cả các bạn cần phải làm gì để tạo ra một khóa công khai từ khóa riêng của bạn, bạn nhập khẩu . Dường như không có nhiều thông tin để nhập khóa RSA mà bạn không tạo trên thiết bị và tôi đã nghe rằng các khóa được tạo trên thiết bị KHÔNG chứa các tiêu đề ASN.1 này, nhưng tôi chưa bao giờ thử . Khóa của chúng tôi khá lớn và mất một thiết bị quá lâu để tạo ra. Tùy chọn duy nhất mà tôi từng tìm thấy là sử dụng OpenSSL, nơi bạn phải biên dịch riêng cho iOS. Tôi muốn sử dụng khung bảo mật nếu có thể.

Tôi vẫn còn khá mới để phát triển iOS và tôi chắc rằng ai đó biết một chức năng đơn giản thực hiện tất cả những điều này mà tôi không thể tìm thấy và tôi ĐÃ XEM. Điều này dường như làm việc tốt cho đến khi một API dễ dàng hơn có sẵn để xử lý các khóa.

Lưu ý cuối cùng: Khóa cá nhân đi kèm với dự án có thẻ BIT STRING, nhưng khóa tôi nhập từ khóa riêng do OpenSSL tạo có thẻ OCTET STRING.

Các vấn đề liên quan