简体   繁体   English

如何从DER / PEM文件中获取SecKeyRef

[英]How can I get SecKeyRef from DER/PEM file

I need to integrate my iPhone app with a system, and they require to encrypt data by a given public key, there are 3 files in 3 different format .xml .der and .pem, I have researched and found some articles about getting SecKeyRef from DER/PEM, but they are always return nil. 我需要将我的iPhone应用程序与系统集成,它们需要通过给定的公钥加密数据,有3种不同格式的文件.xml .der和.pem,我研究过并发现了一些关于从中获取SecKeyRef的文章DER / PEM,但它们总是返回零。 Below is my code: 以下是我的代码:

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;

Problem happens at SecCertificateCreateWithData, it always return nil even through read file is ok. 问题发生在SecCertificateCreateWithData,它总是返回nil,即使通过读取文件也没问题。 Anybody has done this please help me, thanks! 有人这样做请帮助我,谢谢!

EDIT: The cert file was MD5 signature. 编辑:证书文件是MD5签名。

I struggled a lot with the same problem and finally found a solution. 我在同样的问题上苦苦挣扎,终于找到了解决方案。 My problem was that I needed to use both an external private and public key for encrypting/decrypting data in an iOS app and didn't want to use the keychain. 我的问题是我需要使用外部私钥和公钥来加密/解密iOS应用程序中的数据,并且不想使用钥匙串。 It turns out you also need a signed certificate for the iOS security library to be able to read the key data and of course the files have to be in the correct format. 事实证明,您还需要一个用于iOS安全库的签名证书才能读取密钥数据,当然文件必须采用正确的格式。 The procedure is basically as follows: 程序基本如下:

Say you have a private key in PEM format (with the -----BEGIN RSA PRIVATE KEY----- and -----END RSA PRIVATE KEY----- markers): rsaPrivate.pem 假设你有一个PEM格式的私钥(使用----- BEGIN RSA私钥-----和-----结束RSA私钥-----标记):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

Now you have two files which are compatible with the iOS security framework: rsaCert.der (public key) and rsaPrivate.p12 (private key). 现在您有两个与iOS安全框架兼容的文件:rsaCert.der(公钥)和rsaPrivate.p12(私钥)。 The code below reads in the public key assuming the file is added to your bundle: 假设文件已添加到您的包中,下面的代码将读入公钥:

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

To read in the private key use the following code: 要读入私钥,请使用以下代码:

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

Starting with iOS 10, it is actually possible to import PEM private keys w/o converting them to PKCS#12 (which is a very universal container format for everything related to cryptography) and thus also w/o using OpenSSL on command line or statically linking apps with it. 从iOS 10开始,实际上可以导入PEM私钥, 无需将它们转换为PKCS#12 (这是一种非常通用的容器格式,用于与加密相关的所有内容),因此也无需在命令行或静态上使用OpenSSL将应用与其关联。 On macOS it's even possible since 10.7 using a different function than the ones mentioned here (but so far it doesn't exist for iOS). 在macOS上,甚至可以使用与此处提到的功能不同的10.7(但到目前为止iOS不存在)。 Exactly the way described below will also work on macOS 10.12 and later, though. 但是,下面描述的方式也适用于macOS 10.12及更高版本。

To import a certificate, it's enough to just strip the 要导入证书,只需删除证书即可

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

and

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

lines, then run base64 decoding over the data left, the result is a certificate in standard DER format, which can just be fed to SecCertificateCreateWithData() to get a SecCertificateRef . 然后对剩下的数据运行base64解码,结果是标准DER格式的证书,可以将其提供给SecCertificateCreateWithData()以获取SecCertificateRef This has always been working, also prior to iOS 10. 这在iOS 10之前一直有效。

To import a private key, a little bit of extra work may be required. 要导入私钥,可能需要一些额外的工作。 If the private key is wrapped with 如果私钥被包装

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

then it is very easy. 那很容易。 Again, the first and last line needs to be stripped, the remaining data needs to be base64 decoded and the result is a RSA key in PKCS#1 format. 同样,需要剥离第一行和最后一行,其余数据需要进行base64解码,结果是PKCS#1格式的RSA密钥。 This format can only hold RSA keys and it is directly readable, just feed the decoded data into SecKeyCreateWithData() to obtain a SecKeyRef . 这种格式只能保存RSA密钥并且可以直接读取,只需将解码后的数据输入SecKeyCreateWithData()即可获得SecKeyRef The attributes dictionary just need the following key/value pairs: attributes字典只需要以下键/值对:

  • kSecAttrKeyType : kSecAttrKeyTypeRSA kSecAttrKeyTypekSecAttrKeyTypeRSA
  • kSecAttrKeyClass : kSecAttrKeyClassPrivate kSecAttrKeyClasskSecAttrKeyClassPrivate
  • kSecAttrKeySizeInBits : CFNumberRef with then number of bits in the key (eg 1024, 2048, etc.) If not known, this information can actually be read from the raw key data, which is ASN.1 data (it's a bit beyond the scope of this answer, but I will provide some helpful links below about how to parse that format). kSecAttrKeySizeInBitsCFNumberRef ,然后是密钥中的位数(例如CFNumberRef等)。如果不知道,这个信息实际上可以从原始密钥数据中读取,这是ASN.1数据(它有点超出了这个答案,但我将在下面提供一些有关如何解析该格式的有用链接)。 This value is maybe optional! 这个值可能是可选的! In my tests it was actually not necessary to set this value; 在我的测试中,实际上没有必要设置这个值; if absent, the API determined the value on its own and it was always set correctly later on. 如果不存在,API会自行确定该值,并且以后总是正确设置。

In case the private key is wrapped by -----BEGIN PRIVATE KEY----- , then the base64 encoded data is not in PKCS#1 format but in PKCS#8 format, however, this is a just a more generic container that can also hold non-RSA keys but for RSA keys the inner data of that container is equal to PKCS#1 , so one could say for RSA keys PKCS#8 is PKCS#1 with an extra header and all you need to do is stripping that extra header. 如果私钥被-----BEGIN PRIVATE KEY-----包装,那么base64编码数据不是PKCS#1格式,而是PKCS#8格式,但是,这只是一个更通用的也可以容纳非RSA密钥的容器,但是对于RSA密钥,该容器的内部数据等于PKCS#1 ,因此可以说RSA密钥PKCS#8PKCS#1 ,带有额外的头,所有你需要做的正在剥离额外的标题。 Just strip the first 26 bytes of the base64 decoded data and you have PKCS#1 again. 只需删除base64解码数据的前26个字节,然后再次使用PKCS#1 Yes, it's really that simple. 是的,它真的那么简单。

To learn more about PKCS#x formats in PEM encodings, have a look at this site . 要了解有关PEM编码中PKCS#x格式的更多信息,请查看此站点 To learn more about ASN.1 format, here's a good site for that . 要了解有关ASN.1格式的更多信息, 这里有一个很好的网站 And if you need a simple, yet powerful and interactive online ASN.1 parser to play around with different formats, one that can directly read PEM data, as well as ASN.1 in base64 and hexdump, try this site . 如果您需要一个简单但功能强大且交互式的在线ASN.1解析器来使用不同的格式,可以直接读取PEM数据,以及base64和hexdump中的ASN.1,请尝试使用此站点

Very important: When adding a private key to keychain, that you created as above, please be aware that such a private key doesn't contain a public key hash, yet a public key hash is important for they keychain API to form an identity ( SecIdentityRef ), as using the public key hash is how the API finds the correct private key that belongs to an imported certificate (a SecIdentityRef is just a SecKeyRef of a private key and a SecCertificateRef of a cert forming a combined object and it's the public key hash, that binds them together). 非常重要:在向上面创建的钥匙串添加私钥时,请注意这样的私钥不包含公钥哈希,但公钥哈希对于他们的钥匙串API形成身份很重要( SecIdentityRef ),因为使用公钥哈希是API如何找到属于导入证书的正确私钥( SecIdentityRef只是私钥的SecKeyRef和形成组合对象的证书的SecCertificateRef ,它是公钥哈希,将它们绑在一起)。 So when you plan to add the private key to keychain, be sure to set a public key hash manually, otherwise you won't ever be able to get an identity for it and without that you cannot use keychain API for tasks like signing or decrypting data. 因此,当您计划将私钥添加到钥匙串时,请确保手动设置公钥哈希,否则您将无法获得该身份的标识,如果没有,您就无法使用钥匙串API执行签名或解密等任务数据。 The public key hash must be stored in an attribute named kSecAttrApplicationLabel (stupid name, I know, but it's really not a label and nothing the user can ever see, check out the documentation). 公钥哈希必须存储在名为kSecAttrApplicationLabel的属性中(愚蠢的名称,我知道,但它实际上不是标签,用户也看不到任何内容,请查看文档)。 Eg: 例如:

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

After hours of effort researching online with the help of this post, I finally get it working perfectly. 经过这篇文章的帮助,经过数小时的努力研究,我终于完美地完成了它。 Here is the notes with working Swift code of the most current version. 以下是使用最新版本的Swift代码的注释。 I hope it can help someone! 我希望它可以帮助别人!

  1. Received a certificate in the base64 encoded string sandwiched between header and tail like this (PEM format): 收到了base64编码的字符串中的证书,这个字符串夹在标题和尾部之间(PEM格式):

     -----BEGIN CERTIFICATE----- -----END CERTIFICATE----- 
  2. strip out the header and the tail, such as 剥掉头部和尾部,如

     // 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. decode base64 string to NSData: 将base64字符串解码为NSData:

     let data = NSData(base64Encoded: cerStr, options:NSData.Base64DecodingOptions.ignoreUnknownCharacters)! 
  4. Convert it from NSdata format to SecCertificate: 将其从NSdata格式转换为SecCertificate:

     let cert = SecCertificateCreateWithData(kCFAllocatorDefault, data) 
  5. Now, this cert can be used to compare with the certificate received from the urlSession trust: 现在,此证书可用于与从urlSession信任收到的证书进行比较:

     certificateFromUrl = SecTrustGetCertificateAtIndex(...) if cert == certificate { } 

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 iPhone:如何从公共密钥文件(PEM)创建SecKeyRef - iPhone: How to create a SecKeyRef from a public key file (PEM) 如何获取证书公钥字符串“将SecKeyRef转换为NSString”iOS,如果此键字串在所有平台上相似? - How to get the certificate Public Key String “Convert SecKeyRef from to NSString” iOS, and if this key string similar in all platforms? iPhone:如何将包含公钥位的SecKeyRef或NSData导出为PEM格式? - iPhone: How do you export a SecKeyRef or an NSData containing public key bits to the PEM format? 如何从文件中获取CGImageRef? - How can I get a CGImageRef from a file? 如何从公钥的指数和模数创建SeckeyRef并在SecKeyEncrypt方法中使用 - How to Create SeckeyRef from exponent and modulus of Public key and use in SecKeyEncrypt method iOS - 从指数+模数创建SecKeyRef - iOS - Creating SecKeyRef from exponent+modulus 如何从文档文件夹中的文件中获取数据 - How Can i get data from the file in the documents folder 如何知道.pem文件是否正确 - How to know whether .pem file is correct or not 我有一个库文件(.a)。 我如何从中倒退以获得一些伪源代码? - I've got a library file (.a). How can I go backwards from this to get some pseudo source code? 在LINUX w下使用PHP从pushnotification创建.pem文件 - Creating .pem file from pushnotification with PHP under LINUX w
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM