3

在运行时,我的 iOS 应用程序接收到一个带有公私 RSA 密钥对的文件,该文件由其他人的 Java 生成:

KeyPairGenerator keygenerator;
keygenerator = KeyPairGenerator.getInstance("RSA");
keygenerator.initialize(4096);

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

我已经使用这种方法成功读取并使用了公钥,该方法从密钥中删除了一些前导码。

我现在想使用私钥。同样的方法不起作用,我认为序言有所不同。该博客建议它正在导入 PKCS#1 PEM 密钥,但随后说它们是二进制的,所以我认为它们只是指 Base64 编码的 DER 密钥。我还发现我拥有的密钥可能是PKCS#8编码的。

当然我可以使用

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

在示例私钥上,openssl 不会抱怨。

公钥是 PKCS#1 和私有 PKCS#8 是否有意义?

但如果可能的话,我真的很想使用 CommonCrypto 和安全框架,而不是链接到 OpenSSL 。在 Mac OS 上,libsecurity中有读取 PKCS#8 的函数,但这还没有进入 iOS。老实说,我确实尝试阅读源代码,但我无法弄清楚他们实际剥离密钥的位置。

[TL;DR] 如何使用 CommonCrypto 或某些 C/C++/ObjC 从 DER 私钥中去除版本和算法PKCS#8 字段,并获取明文密钥?

4

2 回答 2

1

没有 OpenSSL,我无法解决问题。所以这是一个使用 OpenSSL 的解决方案

假设你有一个名为 privateKey 的 NSData 和另一个你想要签名的名为 signableData 的 NSData。

#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);
}
于 2012-09-14T06:49:15.877 回答
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))
    }
于 2016-04-14T13:28:47.943 回答