如何在iOS中使用PKCS8 RSA DER私钥?

3
在运行时,我的iOS应用程序收到了由其他人的Java生成的公共-私有RSA密钥对的文件:
KeyPairGenerator keygenerator;
keygenerator = KeyPairGenerator.getInstance("RSA");
keygenerator.initialize(4096);

KeyPair keypair = keygenerator.generateKeyPair();
PrivateKey privateKey = keypair.getPrivate().getEncoded();
PublicKey publicKey = keypair.getPublic().getEncoded();

我已经成功读取并使用了公共密钥,使用this method方法,该方法从密钥中去除了一些前导内容。

现在我想使用私人密钥。相同的方法不起作用,我认为前导内容可能有所不同。该博客建议导入PKCS#1 PEM密钥,但随后又说它们是二进制的,因此我认为他们只是指Base64编码的DER密钥。我还发现,也许我拥有的密钥是PKCS#8编码的。

当然,我可以使用

openssl pkcs8 -nocrypt -inform der < pk8.der > pvt.pem

在一个样本私钥上,OpenSSL没有抱怨。

公钥是PKCS#1,私钥是PKCS#8,这有意义吗?

但我真的想使用CommonCrypto和Security Framework,而不是链接到OpenSSL,如果可能的话。在Mac OS中,libsecurity中有函数可以读取PKCS#8,但这还没有到iOS。老实说,我尝试阅读源代码,但我无法确定他们实际上从哪里剥离密钥。

[TL;DR] 我如何使用CommonCrypto或一些C/C++/ObjC剥离DER私钥的版本和算法PKCS#8字段,只获取纯净的密钥?

2个回答

1
你可以在这个网页上看到DER密钥在ASN1结构中的样子: https://lapo.it/asn1js/ 这是来自SwCrypt库的代码,用于从私钥中删除PKCS8头。这是Swift语言,但你可以轻松地将其改写为其他语言。
    static private func stripHeaderIfAny(keyData: NSData) throws -> NSData {
        var bytes = keyData.arrayOfBytes()

        var offset = 0
        guard bytes[offset] == 0x30 else {
            throw SwError.ASN1Parse
        }
        offset += 1

        if bytes[offset] > 0x80 {
            offset += Int(bytes[offset]) - 0x80
        }
        offset += 1

        guard bytes[offset] == 0x02 else {
            throw SwError.ASN1Parse
        }
        offset += 3

        //without PKCS8 header
        if bytes[offset] == 0x02 {
            return keyData
        }

        let OID: [UInt8] = [0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86,
                            0xf7, 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00]
        let slice: [UInt8] = Array(bytes[offset..<(offset + OID.count)])

        guard slice == OID else {
            throw SwError.ASN1Parse
        }

        offset += OID.count
        guard bytes[offset] == 0x04 else {
            throw SwError.ASN1Parse
        }

        offset += 1
        if bytes[offset] > 0x80 {
            offset += Int(bytes[offset]) - 0x80
        }
        offset += 1

        guard bytes[offset] == 0x30 else {
            throw SwError.ASN1Parse
        }

        return keyData.subdataWithRange(NSRange(location: offset, length: keyData.length - offset))
    }

1
我没有使用OpenSSL无法解决这个问题。所以这里提供一个使用OpenSSL的解决方案。
假设您有一个名为privateKey的NSData包含密钥,另一个名为signableData包含要签名的数据。
#import <openssl/x509.h>
#import <openssl/pem.h>

NSURL *cacheDir = [[[NSFileManager defaultManager] URLsForDirectory:NSCachesDirectory inDomains:NSUserDomainMask] lastObject];
NSString *infile = [[cacheDir URLByAppendingPathComponent:@"privkey.der"] path];

NSError *error;
[privateKey writeToFile:infile options:NSDataWritingFileProtectionComplete error:&error];
if (error) {
    NSLog(@"%@", error);
} else {
    BIO *in = BIO_new_file([infile cStringUsingEncoding:NSUTF8StringEncoding], "rb");
    PKCS8_PRIV_KEY_INFO *p8inf = d2i_PKCS8_PRIV_KEY_INFO_bio(in, NULL);
    NSLog(@"%i", p8inf->broken);
    EVP_PKEY *pkey = EVP_PKCS82PKEY(p8inf);
    PKCS8_PRIV_KEY_INFO_free(p8inf);
    BIO_free(in);

    uint8_t * cipherBuffer = NULL;

    // Calculate the buffer sizes.
    unsigned int cipherBufferSize = RSA_size(pkey->pkey.rsa);
    unsigned int signatureLength;

    // Allocate some buffer space. I don't trust calloc.
    cipherBuffer = malloc(cipherBufferSize);
    memset((void *)cipherBuffer, 0x0, cipherBufferSize);

    unsigned char *openSSLHash = SHA1(signableData.bytes, signableData.length, NULL);
    int success = RSA_sign(NID_sha1, openSSLHash, 20, cipherBuffer, &signatureLength, pkey->pkey.rsa);
    if (success) NSLog(@"WIN");


    NSData *signed = [NSData dataWithBytes:(const void*)cipherBuffer length:signatureLength];    

    EVP_PKEY_free(pkey);
}

对我而言它没有起作用。我已经替换了前两行为 NSString *path = [[NSBundle mainBundle] pathForResource:@"keyNew" ofType:@"pem"]; NSString *infile = [NSString stringWithContentsOfFile:path encoding:NSUTF8StringEncoding error:NULL]; 但总是出现错误,程序不能进入else部分。如果我删除if和else这两个条件语句,那么会在“BIO *in”处报错。 - Nitesh Meshram
@NiteshMeshram 你是在试图将密钥写入bundle吗?你不能在那里写文件。infile应该是指向密钥文件的路径(作为NSString),而不是文件的内容。在你的版本中,将BIO *in开始的行上的infile替换为path,并删除它上面的所有代码(除了导入),保留你的NSString *path = [[NSBundle mainBundle] pathForResource:@"keyNew" ofType:@"pem"]; - Cathy
我有一个.pem文件,我只想读取它。有什么建议吗?我已经尝试了读/写两种方式,但都不起作用。基本上我只想读取... - Nitesh Meshram
如果您的密钥是.pem格式,它将无法正常工作。您需要对其进行base-64编码以使其成为.der格式。OpenSSL(命令行)可以执行此操作 openssl x509 -in cert.crt -outform der -out cert.der。然后只需按照我上面说的做:将以“BIO *in…”开头的行中的“infile”替换为包含指向密钥文件路径(而不是内容!)的NSString。例如:NSString *path = [[NSBundle mainBundle] pathForResource:@"keyNew" ofType:@"der"]; - Cathy
好的,我已经修改了.pem文件并使用了.der文件,但是在“BIO *in”这个变量中仍然得到了<nil>。 代码 - NSString *path = [[NSBundle mainBundle] pathForResource:@"newpvt" ofType:@"der"]; NSString *infile = [NSString stringWithContentsOfFile:path encoding:NSUTF8StringEncoding error:NULL]; BIO *in = BIO_new_file([infile cStringUsingEncoding:NSUTF8StringEncoding], "rb"); PKCS8_PRIV_KEY_INFO *p8inf = d2i_PKCS8_PRIV_KEY_INFO_bio(in, NULL); - Nitesh Meshram
@NiteshMeshram 你正在读取文件,然后尝试打开名为密钥随机字节的文件。在我的答案中,我将其作为NSData写入文件。你只需要执行 BIO *in = BIO_new_file([[[NSBundle mainBundle] pathForResource:@"keyNew" ofType:@"der"] cStringUsingEncoding:NSUTF8StringEncoding], "rb"); 并且删除 NSString *infile = [NSString stringWithContentsOfFile:path encoding:NSUTF8StringEncoding error:NULL]; 这一行代码,它是用来打开和读取文件的。 - Cathy

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接