web-dev-qa-db-ja.com

DER / PEMファイルからSecKeyRefを取得するにはどうすればよいですか

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署名でした。

21
Son Nguyen

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

PEM形式の秘密鍵があるとします(----- BEGIN RSA PRIVATE KEY -----および----- END RSA 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;
}
55

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#8PKCS#1であり、追加のヘッダーがあり、必要なものはすべてあります。行うことは、その余分なヘッダーを取り除くことです。 base64でデコードされたデータの最初の26バイトを削除するだけで、PKCS#1が再び得られます。はい、それは本当にとても簡単です。

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

非常に重要:上記のように作成した秘密鍵をキーチェーンに追加する場合、そのような秘密鍵には公開鍵が含まれていないことに注意してください公開鍵ハッシュを使用すると、APIがインポートされた証明書に属する正しい秘密鍵を見つける方法であるため(SecIdentityRef)、公開鍵ハッシュはキーチェーンAPIがID(SecIdentityRef)を形成するために重要です。 [$ var] _は秘密鍵のSecKeyRefと結合されたオブジェクトを形成する証明書の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()
);
_
10
Mecki

この投稿の助けを借りてオンラインで何時間も研究した後、私はついにそれを完全に機能させることができました。これが動作中のメモです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 {
    }
    
4
jingyul