首页 文章

RSA:在iOS中加密,用Java解密

提问于
浏览
17

我有一个从Java服务器发送的公钥 . 在解码和删除ASN.1标头之前,base64编码的字符串匹配 . 我使用 SecItemAdd 将公钥存储在钥匙串中 .

所以我在iOS端使用 SecKeyEncrypt ,在Java端使用 Cipher .

我正在加密的是加密我的实际数据的对称AES密钥,因此密钥长度为16个字节 . 当简单地对base64进行编码时,一切正常,所以我知道这个RSA加密有问题 .

这是我的iOS调用示例:

OSStatus sanityCheck = SecKeyEncrypt(publicKey,
        kSecPaddingPKCS1,
        (const uint8_t *) [incomingData bytes],
        keyBufferSize,
        cipherBuffer,
        &cipherBufferSize
);

这是我的Java调用的一个例子:

public static byte[] decryptMessage (byte[] message, PrivateKey privateKey, String algorithm) {
    if (message == null || privateKey == null) {
        return null;
    }
    Cipher cipher = createCipher(Cipher.DECRYPT_MODE, privateKey, algorithm, false);
    if (cipher == null) {
        return null;
    }

    try {
        return cipher.doFinal(message);
    }
    catch (IllegalBlockSizeException e) {
        e.printStackTrace();  //To change body of catch statement use File | Settings | File Templates.
        return null;
    }
    catch (BadPaddingException e) {
        e.printStackTrace();  //To change body of catch statement use File | Settings | File Templates.
        return null;
    }
}

private static Cipher createCipher (int mode, Key encryptionKey, String algorithm, boolean useBouncyCastle) {
    Cipher cipher;

    try {
        if (useBouncyCastle) {
            Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider());
            cipher = Cipher.getInstance(algorithm, "BC");
        }
        else {
            cipher = Cipher.getInstance(algorithm);
        }
    }
    catch (NoSuchAlgorithmException e) {
        e.printStackTrace();  //To change body of catch statement use File | Settings | File Templates.
        return null;
    }
    catch (NoSuchPaddingException e) {
        e.printStackTrace();  //To change body of catch statement use File | Settings | File Templates.
        return null;
    }
    catch (NoSuchProviderException e) {
        e.printStackTrace();
        return null;
    }

    try {
        cipher.init(mode, encryptionKey);
    }
    catch (InvalidKeyException e) {
        e.printStackTrace();  //To change body of catch statement use File | Settings | File Templates.
        return null;
    }

    return cipher;
}

我尝试了很多组合,但没有任何效果 .

  • iOS:PKCS1,Java:RSA / ECB / PKCS1Padding

  • iOS:PKCS1,Java:RSA

  • iOS:PKCS1,Java:RSA / None / PKCS1Padding(抛出 org.bouncycastle.crypto.DataLengthException: input too large for RSA cipher.

  • iOS:OAEP,Java:RSA / ECB / OAEPWithSHA-1AndMGF1Padding

  • iOS:OAEP,Java:RSA / ECB / OAEPWithSHA-256AndMGF1Padding

我也尝试使用内部Java提供程序以及BouncyCastle提供程序 . 每次都会抛出 javax.crypto.BadPaddingException ,但每个组合的消息都不同 . 有些显示 Data must start with zero ,有些显示 Message is larger than modulus .

iOS: PKCS1, Java: RSA 不会抛出异常,但生成的解密 byte[] 数组应该是长度为16,但它正确地被删除了.1563378_t .

有人可以帮忙吗?

*** EDIT ***

当我进行更多测试时,我遇到了这个页面(http://javadoc.iaik.tugraz.at/iaik_jce/current/iaik/pkcs/pkcs1/RSACipher.html),它基本上告诉我 RSA == RSA/None/PKCS1Padding . 解密工作的意义是没有异常,但我仍然得到一个解密密钥,其byte []长度为256而不是长度为16 .

另一个兴趣点 . 似乎如果Java服务器具有从iOS设备生成并使用 Cipher.getInstance("RSA") 加密的公钥,则电话能够使用RSA / PKCS1正确解码消息 .

*** EDIT 2 ***

我查看了这些教程,并在iOS端再次查看了我的代码:

据我所知,我的代码正在做正确的事情 . 一个显着的区别在于我如何保存密钥,所以我尝试以另一种方式保存它:

OSStatus error = noErr;
    CFTypeRef persistPeer = NULL;

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

    keyAttr[(__bridge id) kSecClass] = (__bridge id) kSecClassKey;
    keyAttr[(__bridge id) kSecAttrKeyType] = (__bridge id) kSecAttrKeyTypeRSA;
    keyAttr[(__bridge id) kSecAttrApplicationTag] = [secKeyWrapper getKeyTag:serverPublicKeyTag];
    keyAttr[(__bridge id) kSecValueData] = strippedServerPublicKey;
    keyAttr[(__bridge id) kSecReturnPersistentRef] = @YES;

    error = SecItemAdd((__bridge CFDictionaryRef) keyAttr, (CFTypeRef *)&persistPeer);

    if (persistPeer == nil || ( error != noErr && error != errSecDuplicateItem)) {
        NSLog(@"Problem adding public key to keychain");
        return;
    }

    CFRelease(persistPeer);

保存成功,但最终结果是相同的:解密的AES密钥仍然是256字节长而不是16字节 .

2 回答

  • 5

    我有同样的问题 . 使用 kSecPaddingNone ,但 doesn'tkSecPaddingPKCS1 一起使用,Java代码中的任何 PKCS1 组合 .

    但是,没有填充使用它并不是一个好主意 .

    因此,在iOS上,将 kSecPaddingNone 替换为 kSecPaddingOAEP 并在Java代码中使用 RSA/NONE/OAEPWithSHA1AndMGF1Padding . 这对我有用 .

  • 12

    使用RSA / None / NoPadding的解决方案

    好的,所以我让它工作但 WITHOUT PADDING . 这部分让我非常沮丧,我将其留给其他人试图帮助 . 也许我到目前为止发现了什么 .

    TL;DR :使用最少的属性将密钥保存到钥匙串,以使检索更简单 . 使用 SecKeyEncrypt 进行加密,但使用 kSecPaddingNone . 用BouncyCastle和算法 RSA/None/NoPadding 解密Java端 .

    从Java向iOS发送RSA公钥

    使用X.509证书

    我想验证是否直接发送公钥,剥离ASN.1标头并保存实际上正在做它应该做的事情 . 所以我考虑将公钥作为证书发送 . 我想赞扬David Benko提供的加密库(https://github.com/DavidBenko/DBTransitEncryption)帮助我进行了证书转换 . 我没有't actually use his library because 1. I'已经使用 RNCryptor / JNCryptor 进行我的AES加密和2.他没有想要这样做 . 对于那些感兴趣并希望采用这种方法的人,这是我在Java端创建证书然后将该证书转换为iOS上的公钥的代码:

    *重要说明:请将 e.printStackTrace() 替换为真实的日志记录 . 我只用它来测试和 生产环境 中的 NOT .

    Java

    public static X509Certificate generateCertificate (KeyPair newKeys) {
        Security.addProvider(new BouncyCastleProvider());
        Date startDate = new Date();
        Date expiryDate = new DateTime().plusYears(100).toDate();
    
        BigInteger serialNumber = new BigInteger(10, new Random());
        try {
            ContentSigner sigGen = new JcaContentSignerBuilder("SHA1withRSA").setProvider("BC").build(newKeys
                                                                                                              .getPrivate());
            SubjectPublicKeyInfo subjectPublicKeyInfo = new SubjectPublicKeyInfo(ASN1Sequence.getInstance(newKeys
                                                                                                                  .getPublic().getEncoded()
                                                                                                                  ));
            X500Name dnName = new X500Name("CN=FoodJudge API Certificate");
            X509v1CertificateBuilder builder = new X509v1CertificateBuilder(dnName,
                                                                            serialNumber,
                                                                            startDate, expiryDate,
                                                                            dnName,
                                                                            subjectPublicKeyInfo);
            X509CertificateHolder holder = builder.build(sigGen);
            return new JcaX509CertificateConverter().setProvider("BC").getCertificate(holder);
        }
        catch (OperatorCreationException e) {
            e.printStackTrace();
        }
        catch (CertificateException e) {
            e.printStackTrace();
        }
        return null;
    }
    

    Obj-C

    - (SecKeyRef)extractPublicKeyFromCertificate:(NSData *)certificateBytes {
        if (certificateBytes == nil) {
            return nil;
        }
    
        SecCertificateRef certificate = SecCertificateCreateWithData(kCFAllocatorDefault, ( __bridge CFDataRef) certificateBytes);
        if (certificate == nil) {
            NSLog(@"Can not read certificate from data");
            return false;
        }
    
        SecTrustRef trust;
        SecPolicyRef policy = SecPolicyCreateBasicX509();
        OSStatus returnCode = SecTrustCreateWithCertificates(certificate, policy, &trust);
    
        // release the certificate as we're done using it
        CFRelease(certificate);
        // release the policy
        CFRelease(policy);
    
        if (returnCode != errSecSuccess) {
            NSLog(@"SecTrustCreateWithCertificates fail. Error Code: %d", (int)returnCode);
            return nil;
        }
    
        SecTrustResultType trustResultType;
        returnCode = SecTrustEvaluate(trust, &trustResultType);
        if (returnCode != errSecSuccess) {
            // TODO log
            CFRelease(trust);
            return nil;
        }
    
        SecKeyRef publicKey = SecTrustCopyPublicKey(trust);
        CFRelease(trust);
    
        if (publicKey == nil) {
            NSLog(@"SecTrustCopyPublicKey fail");
            return nil;
        }
    
        return publicKey;
    }
    

    使用RSA公钥

    它_1163412_ t需要将公钥作为证书发送 . 实际上,在发现公钥被错误保存后(见下文),我还原了这段代码并将公钥保存到我的设备中 . 您需要删除其中一篇博文中提到的 ASN.1 Headers . 该代码在此处重新发布(为清晰起见而格式化) .

    + (NSData *)stripPublicKeyHeader:(NSData *)keyBits {
        // Skip ASN.1 public key header
        if (keyBits == nil) {
            return nil;
        }
    
        unsigned int len = [keyBits length];
        if (!len) {
            return nil;
        }
    
        unsigned char *c_key = (unsigned char *)[keyBits bytes];
        unsigned int  idx    = 0;
    
        if (c_key[idx++] != 0x30) {
            return nil;
        }
    
        if (c_key[idx] > 0x80) {
            idx += c_key[idx] - 0x80 + 1;
        }
        else {
            idx++;
        }
    
        if (idx >= len) {
            return nil;
        }
    
        if (c_key[idx] != 0x30) {
            return nil;
        }
    
        idx += 15;
    
        if (idx >= len - 2) {
            return nil;
        }
    
        if (c_key[idx++] != 0x03) {
            return nil;
        }
    
        if (c_key[idx] > 0x80) {
            idx += c_key[idx] - 0x80 + 1;
        }
        else {
            idx++;
        }
    
        if (idx >= len) {
            return nil;
        }
    
        if (c_key[idx++] != 0x00) {
            return nil;
        }
    
        if (idx >= len) {
            return nil;
        }
    
        // Now make a new NSData from this buffer
        return([NSData dataWithBytes:&c_key[idx] length:len - idx]);
    }
    

    所以我会像这样保存密钥:

    - (void)storeServerPublicKey:(NSString *)serverPublicKey {
        if (!serverPublicKey) {
            return;
        }
        SecKeyWrapper *secKeyWrapper = [SecKeyWrapper sharedWrapper];
        NSData *decryptedServerPublicKey = [[NSData alloc] initWithBase64EncodedString:serverPublicKey options:0];
    
        NSData *strippedServerPublicKey = [SecKeyWrapper stripPublicKeyHeader:decryptedServerPublicKey];
        if (!strippedServerPublicKey) {
            return;
        }
        [secKeyWrapper savePublicKeyToKeychain:strippedServerPublicKey tag:@"com.sampleapp.publickey"];
    }
    

    将RSA公钥保存到钥匙串

    这令人抓狂 . 原来,即使我将钥匙保存到钥匙串中,我检索到的不是我放入的钥匙!当我将保存的base64密钥与我用来加密AES密钥的base64密钥进行比较时,我意外地发现了这一点 . 所以我发现最好简化保存密钥时使用的NSDictionary . 这是我最终得到的:

    - (void)savePublicKeyToKeychain:(NSData *)key tag:(NSString *)tagString {
        NSData *tag = [self getKeyTag:tagString];
    
        NSDictionary *saveDict = @{
                (__bridge id) kSecClass : (__bridge id) kSecClassKey,
                (__bridge id) kSecAttrKeyType : (__bridge id) kSecAttrKeyTypeRSA,
                (__bridge id) kSecAttrApplicationTag : tag,
                (__bridge id) kSecAttrKeyClass : (__bridge id) kSecAttrKeyClassPublic,
                (__bridge id) kSecValueData : key
        };
        [self saveKeyToKeychain:saveDict tag:tagString];
    }
    
    - (void)saveKeyToKeychain:(NSDictionary *)saveDict tag:(NSString *)tagString {
        OSStatus sanityCheck = SecItemAdd((__bridge CFDictionaryRef) saveDict, NULL);
        if (sanityCheck != errSecSuccess) {
            if (sanityCheck == errSecDuplicateItem) {
                // delete the duplicate and save again
                sanityCheck = SecItemDelete((__bridge CFDictionaryRef) saveDict);
                sanityCheck = SecItemAdd((__bridge CFDictionaryRef) saveDict, NULL);
            }
            if (sanityCheck != errSecSuccess) {
                NSLog(@"Problem saving the key to keychain, OSStatus == %d.", (int) sanityCheck);
            }
        }
        // remove from cache
        [keyCache removeObjectForKey:tagString];
    }
    

    要检索我的密钥,我使用以下方法:

    - (SecKeyRef)getKeyRef:(NSString *)tagString isPrivate:(BOOL)isPrivate {
         NSData *tag = [self getKeyTag:tagString];
    
         id keyClass = (__bridge id) kSecAttrKeyClassPublic;
         if (isPrivate) {
             keyClass = (__bridge id) kSecAttrKeyClassPrivate;
         }
    
         NSDictionary *queryDict = @{
                 (__bridge id) kSecClass : (__bridge id) kSecClassKey,
                 (__bridge id) kSecAttrKeyType : (__bridge id) kSecAttrKeyTypeRSA,
                 (__bridge id) kSecAttrApplicationTag : tag,
                 (__bridge id) kSecAttrKeyClass : keyClass,
                 (__bridge id) kSecReturnRef : (__bridge id) kCFBooleanTrue
         };
         return [self getKeyRef:queryDict tag:tagString];
     }
    
    - (SecKeyRef)getKeyRef:(NSDictionary *)query tag:(NSString *)tagString {
        SecKeyRef keyReference = NULL;
        OSStatus sanityCheck = SecItemCopyMatching((__bridge CFDictionaryRef) query, (CFTypeRef *) &keyReference);
        if (sanityCheck != errSecSuccess) {
            NSLog(@"Error trying to retrieve key from keychain. tag: %@. sanityCheck: %li", tagString, sanityCheck);
            return nil;
        }
        return keyReference;
    }
    

    在一天结束时,我只能在没有填充的情况下使其工作 . 我不确定为什么 BouncyCastle 无法删除填充,所以如果有人有任何见解,请告诉我 .

    这是我的加密代码(由David Benko修改):

    - (NSData *)encryptData:(NSData *)content usingPublicKey:(NSString *)publicKeyTag {
        SecKeyRef publicKey = [self getKeyRef:publicKeyTag isPrivate:NO];
        NSData *keyBits = [self getKeyBitsFromKey:publicKey];
        NSString *keyString = [keyBits base64EncodedStringWithOptions:0];
        NSAssert(publicKey != nil,@"Public key can not be nil");
    
        size_t cipherLen = SecKeyGetBlockSize(publicKey); // convert to byte
        void *cipher = malloc(cipherLen);
        size_t maxPlainLen = cipherLen - 12;
    
        size_t plainLen = [content length];
        if (plainLen > maxPlainLen) {
            NSLog(@"content(%ld) is too long, must < %ld", plainLen, maxPlainLen);
            return nil;
        }
    
        void *plain = malloc(plainLen);
        [content getBytes:plain
                   length:plainLen];
    
        OSStatus returnCode = SecKeyEncrypt(publicKey, kSecPaddingNone, plain,
                plainLen, cipher, &cipherLen);
    
        NSData *result = nil;
        if (returnCode != errSecSuccess) {
            NSLog(@"SecKeyEncrypt fail. Error Code: %d", (int)returnCode);
        }
        else {
            result = [NSData dataWithBytes:cipher
                                    length:cipherLen];
        }
    
        free(plain);
        free(cipher);
    
        return result;
    }
    

    这是我在Java方面解密的方式:

    private Response authenticate (String encryptedSymmetricString) {
        byte[] encryptedSymmetricKey = Base64.decodeBase64(encryptedSymmetricKeyString);
        String privateKey = Server.getServerPrivateKey();
        byte[] decryptedSymmetricKey = KeyHandler.decryptMessage(encryptedSymmetricKey, privateKey,
                                                                 KeyHandler.ASYMMETRIC_CIPHER_ALGORITHM);
    }
    
    public static byte[] decryptMessage (byte[] message, String privateKeyString, String algorithm) {
        if (message == null || privateKeyString == null) {
            return null;
        }
        PrivateKey privateKey = getPrivateKey(privateKeyString);
        return decryptMessage(message, privateKey, algorithm);
    }
    
    public static byte[] decryptMessage (byte[] message, PrivateKey privateKey, String algorithm) {
        if (message == null || privateKey == null) {
            return null;
        }
        Cipher cipher = createCipher(Cipher.DECRYPT_MODE, privateKey, algorithm, true);
        if (cipher == null) {
            return null;
        }
    
        try {
            return cipher.doFinal(message);
        }
        catch (IllegalBlockSizeException e) {
            e.printStackTrace();  //To change body of catch statement use File | Settings | File Templates.
            return null;
        }
        catch (BadPaddingException e) {
            e.printStackTrace();  //To change body of catch statement use File | Settings | File Templates.
            return null;
        }
    }
    

相关问题