22

iPhoneアプリをシステムと統合する必要があり、特定の公開鍵でデータを暗号化する必要があります。3つの異なる形式の.xml .derと.pemの3つのファイルがあります。調査して、SecKeyRefの取得に関する記事をいくつか見つけました。 DER / PEMですが、常にnilを返します。以下は私のコードです:

NSString *pkFilePath = [[NSBundle mainBundle] pathForResource:@"PKFile" ofType:@"der"];
NSData *pkData = [NSData dataWithContentsOfFile:pkFilePath]; 

SecCertificateRef   cert; 
cert = SecCertificateCreateWithData(NULL, (CFDataRef) pkData);
assert(cert != NULL);

OSStatus err;

    if (cert != NULL) {
        err = SecItemAdd(
                         (CFDictionaryRef) [NSDictionary dictionaryWithObjectsAndKeys:
                                            (id) kSecClassCertificate,  kSecClass, 
                                            (id) cert,                  kSecValueRef,
                                            nil
                                            ], 
                         NULL
                         );
        if ( (err == errSecSuccess) || (err == errSecDuplicateItem) ) {
            CFArrayRef certs = CFArrayCreate(kCFAllocatorDefault, (const void **) &cert, 1, NULL); 
            SecPolicyRef policy = SecPolicyCreateBasicX509();
            SecTrustRef trust;
            SecTrustCreateWithCertificates(certs, policy, &trust);
            SecTrustResultType trustResult;
            SecTrustEvaluate(trust, &trustResult);
            if (certs) {
                CFRelease(certs);
            }
            if (trust) {
                CFRelease(trust);
            }
            return SecTrustCopyPublicKey(trust);
        }
    }
return NULL;

SecCertificateCreateWithDataで問題が発生し、ファイルの読み取りに問題がない場合でも常にnilが返されます。誰かがこれをやったことがあります、私を助けてください、ありがとう!

編集:証明書ファイルはMD5署名でした。

4

3 に答える 3

57

私は同じ問題で多くの苦労をし、最終的に解決策を見つけました。私の問題は、iOSアプリでデータを暗号化/復号化するために外部秘密鍵と公開鍵の両方を使用する必要があり、キーチェーンを使用したくないということでした。iOSセキュリティライブラリがキーデータを読み取れるようにするための署名付き証明書も必要であり、もちろんファイルは正しい形式である必要があります。手順は基本的に次のとおりです。

PEM形式の秘密鍵があるとします(----- BEGIN RSA PRIVATEKEY-----および-----ENDRSA PRIVATE KEY -----マーカー付き):rsaPrivate.pem

//Create a certificate signing request with the private key
openssl req -new -key rsaPrivate.pem -out rsaCertReq.csr

//Create a self-signed certificate with the private key and signing request
openssl x509 -req -days 3650 -in rsaCertReq.csr -signkey rsaPrivate.pem -out rsaCert.crt

//Convert the certificate to DER format: the certificate contains the public key
openssl x509 -outform der -in rsaCert.crt -out rsaCert.der

//Export the private key and certificate to p12 file
openssl pkcs12 -export -out rsaPrivate.p12 -inkey rsaPrivate.pem -in rsaCert.crt

これで、iOSセキュリティフレームワークと互換性のある2つのファイルができました。rsaCert.der(公開鍵)とrsaPrivate.p12(秘密鍵)です。以下のコードは、ファイルがバンドルに追加されていることを前提として、公開鍵を読み込みます。

- (SecKeyRef)getPublicKeyRef {

    NSString *resourcePath = [[NSBundle mainBundle] pathForResource:@"rsaCert" ofType:@"der"];
    NSData *certData = [NSData dataWithContentsOfFile:resourcePath];
    SecCertificateRef cert = SecCertificateCreateWithData(NULL, (CFDataRef)certData);
    SecKeyRef key = NULL;
    SecTrustRef trust = NULL;
    SecPolicyRef policy = NULL;

    if (cert != NULL) {
        policy = SecPolicyCreateBasicX509();
        if (policy) {
            if (SecTrustCreateWithCertificates((CFTypeRef)cert, policy, &trust) == noErr) {
                SecTrustResultType result;
                OSStatus res = SecTrustEvaluate(trust, &result);

                //Check the result of the trust evaluation rather than the result of the API invocation.
                if (result == kSecTrustResultProceed || result == kSecTrustResultUnspecified) {
                    key = SecTrustCopyPublicKey(trust);
                }
            }
        }
    }
    if (policy) CFRelease(policy);
    if (trust) CFRelease(trust);
    if (cert) CFRelease(cert);
    return key;
}

秘密鍵を読み取るには、次のコードを使用します。

SecKeyRef getPrivateKeyRef() {
    NSString *resourcePath = [[NSBundle mainBundle] pathForResource:@"rsaPrivate" ofType:@"p12"];
    NSData *p12Data = [NSData dataWithContentsOfFile:resourcePath];

    NSMutableDictionary * options = [[NSMutableDictionary alloc] init];

    SecKeyRef privateKeyRef = NULL;

    //change to the actual password you used here
    [options setObject:@"password_for_the_key" forKey:(id)kSecImportExportPassphrase];

    CFArrayRef items = CFArrayCreate(NULL, 0, 0, NULL);

    OSStatus securityError = SecPKCS12Import((CFDataRef) p12Data,
                                             (CFDictionaryRef)options, &items);

    if (securityError == noErr && CFArrayGetCount(items) > 0) {
        CFDictionaryRef identityDict = CFArrayGetValueAtIndex(items, 0);
        SecIdentityRef identityApp =
        (SecIdentityRef)CFDictionaryGetValue(identityDict,
                                             kSecImportItemIdentity);

        securityError = SecIdentityCopyPrivateKey(identityApp, &privateKeyRef);
        if (securityError != noErr) {
            privateKeyRef = NULL;
        }
    }
    [options release];
    CFRelease(items);
    return privateKeyRef;
}
于 2013-06-25T10:48:10.773 に答える
16

iOS 10以降、PEM秘密鍵をPKCS#12(暗号化に関連するすべてのものの非常に普遍的なコンテナー形式)に変換せずにインポートすることが実際に可能であり、したがって、コマンドラインまたは静的にOpenSSLを使用することもありませんアプリをそれにリンクします。macOSでは、10.7以降、ここで説明したものとは異なる機能を使用することも可能です(ただし、これまでのところ、iOSには存在しません)。ただし、以下に説明する方法は、macOS10.12以降でも機能します。

証明書をインポートするには、

-----BEGIN CERTIFICATE-----

-----END CERTIFICATE-----

行を入力し、残っているデータに対してbase64デコードを実行すると、標準のDER形式の証明書が作成されます。これをフィードしてSecCertificateCreateWithData()、を取得できますSecCertificateRef。これは、iOS 10より前でも、常に機能していました。

秘密鍵をインポートするには、少し余分な作業が必要になる場合があります。秘密鍵がでラップされている場合

-----BEGIN RSA PRIVATE KEY-----

それならとても簡単です。この場合も、最初と最後の行を削除する必要があり、残りのデータはbase64でデコードする必要があり、結果はPKCS#1形式のRSAキーになります。この形式はRSAキーのみを保持でき、直接読み取り可能です。デコードされたデータをにフィードするだけで、SecKeyCreateWithData()を取得できSecKeyRefます。attributes辞書に必要なのは、次のキーと値のペアだけです。

  • kSecAttrKeyTypekSecAttrKeyTypeRSA
  • kSecAttrKeyClasskSecAttrKeyClassPrivate
  • kSecAttrKeySizeInBitsCFNumberRefキーのビット数(1024、2048など)がわからない場合、この情報は、ASN.1データである生のキーデータから実際に読み取ることができます(この回答の範囲を少し超えています) 、しかし、そのフォーマットを解析する方法について、以下にいくつかの役立つリンクを提供します)。この値はオプションかもしれません!私のテストでは、実際にはこの値を設定する必要はありませんでした。存在しない場合、APIはそれ自体で値を決定し、後で常に正しく設定されました。

秘密鍵がでラップされている場合-----BEGIN PRIVATE KEY-----、base64でエンコードされたデータはPKCS#1形式ではなく、PKCS#8形式ですが、これは非RSA鍵を保持できるが、RSA鍵用のより一般的なコンテナーです。そのコンテナの内部データはPKCS#1に等しいため、RSAキーの場合PKCS#8は追加のヘッダーを持つPKCS#1であり、必要なのはその追加のヘッダーを削除することだけです。base64でデコードされたデータの最初の26バイトを削除するだけで、PKCS#1が再び得られます。はい、それは本当に簡単です。

PEMエンコーディングのPKCS#x形式の詳細については、このサイトを参照してください。ASN.1形式の詳細については、こちらのサイトをご覧ください。また、さまざまな形式で遊ぶために、シンプルでありながら強力でインタラクティブなオンラインASN.1パーサーが必要な場合は、base64およびhexdumpのASN.1と同様に、PEMデータを直接読み取ることができます。このサイトを試してください

非常に重要:上記のように作成した秘密鍵をキーチェーンに追加する場合、そのような秘密鍵には公開鍵ハッシュが含まれていないことに注意してください。ただし、公開鍵ハッシュは、キーチェーンAPIがIDを形成するために重要です(SecIdentityRef)、公開鍵ハッシュの使用は、APIがインポートされた証明書に属する正しい秘密鍵を見つける方法であるため(aSecIdentityRefSecKeyRef秘密鍵とSecCertificateRef結合されたオブジェクトを形成する証明書の場合、それらをバインドするのは公開鍵ハッシュです)。したがって、秘密鍵をキーチェーンに追加する場合は、必ず公開鍵ハッシュを手動で設定してください。そうしないと、そのIDを取得できず、署名や復号化などのタスクにキーチェーンAPIを使用できません。データ。公開鍵ハッシュは、という名前の属性に格納する必要がありますkSecAttrApplicationLabel(ばかげた名前ですが、実際にはラベルではなく、ユーザーが見ることのできるものは何もありません。ドキュメントを確認してください)。例えば:

OSStatus error = SecItemAdd(
    (__bridge CFDictionaryRef)@{
        (__bridge NSString *)kSecClass: 
            (__bridge NSString *)kSecClassKey,
        (__bridge NSString *)kSecAttrApplicationLabel: 
             hashOfPublicKey, // hashOfPublicKey is NSData *
#if TARGET_OS_IPHONE
        (__bridge NSString *)kSecValueRef: 
            (__bridge id)privateKeyToAdd, // privateKeyToAdd is SecKeyRef
#else
        (__bridge NSString *)kSecUseItemList: 
              @[(__bridge id)privateKeyToAdd], // privateKeyToAdd is SecKeyRef
              // @[ ... ] wraps it into a NSArray object,
              // as kSecUseItemList expects an array of items
#endif
     },
     &outReference // Can also be NULL,
                   // otherwise reference to added keychain entry
                   // that must be released with CFRelease()
);
于 2017-08-28T18:05:06.250 に答える
8

この投稿の助けを借りてオンラインで何時間も調査した後、私はついにそれを完全に機能させることができました。これは、最新バージョンの動作するSwiftコードに関するメモです。私はそれが誰かを助けることができることを願っています!

  1. 次のようにヘッダーとテールの間に挟まれたbase64エンコード文字列で証明書を受け取りました(PEM形式):

    -----BEGIN CERTIFICATE-----
    -----END CERTIFICATE-----
    
  2. ヘッダーとテールを取り除きます。

    // remove the header string  
    let offset = ("-----BEGIN CERTIFICATE-----").characters.count  
    let index = certStr.index(cerStr.startIndex, offsetBy: offset+1)  
    cerStr = cerStr.substring(from: index)  
    
    // remove the tail string 
    let tailWord = "-----END CERTIFICATE-----"   
    if let lowerBound = cerStr.range(of: tailWord)?.lowerBound {  
    cerStr = cerStr.substring(to: lowerBound)  
    }
    
  3. base64文字列をNSDataにデコードします。

    let data = NSData(base64Encoded: cerStr, 
       options:NSData.Base64DecodingOptions.ignoreUnknownCharacters)!  
    
  4. NSdata形式からSecCertificateに変換します。

    let cert = SecCertificateCreateWithData(kCFAllocatorDefault, data)
    
  5. これで、この証明書を使用して、urlSessionトラストから受信した証明書と比較できます。

    certificateFromUrl = SecTrustGetCertificateAtIndex(...)
    if cert == certificate {
    }
    
于 2017-09-21T21:29:32.997 に答える