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

RSA:在iOS中加密,在Java中解密,java,ios7,rsa,Java,Ios7,Rsa,我有一个从Java服务器发送的公钥。base64编码的字符串在我解码并去掉ASN.1头之前匹配。我使用SecItemAdd将公钥存储在钥匙链中 因此,我尝试使用公钥对数据进行加密,并使用Java中的私钥对其进行解密。我在iOS端使用SecKeyEncrypt,在Java端使用Cipher 我加密的是对称AES密钥,它加密我的实际数据,所以密钥长度是16字节。当简单地对密钥进行base64编码时,一切正常,因此我知道这种RSA加密有问题 以下是我的iOS呼叫示例: OSStatus sanityC

我有一个从Java服务器发送的公钥。base64编码的字符串在我解码并去掉ASN.1头之前匹配。我使用
SecItemAdd
将公钥存储在钥匙链中

因此,我尝试使用公钥对数据进行加密,并使用Java中的私钥对其进行解密。我在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/pkcs1p
  • iOS:PKCS1,Java:RSA
  • iOS:PKCS1,Java:RSA/None/PKCS1Padding(抛出
    org.bounchycastle.crypto.datalength异常:输入太大,不适合RSA密码。
  • iOS:OAEP,Java:RSA/ECB/OAEPWITHHA-1和MGF1
  • iOS:OAEP,Java:RSA/ECB/OAEPHA-256和MGF1
我还尝试使用内部Java提供程序和BouncyCastle提供程序。每次都会抛出
javax.crypto.BadPaddingException
,但每个组合的消息都不同。一些显示<代码>数据必须以零开始,而另一些显示<代码>消息大于模数

iOS:PKCS1,Java:RSA
不会抛出异常,但是得到的解密的
byte[]
数组的长度应该是16,但长度是256,这意味着填充没有正确地去除

有人能帮忙吗

***编辑***

在进行更多测试时,我看到了这个页面(),它本质上告诉我
RSA==RSA/None/PKCS1Padding
。解密工作的意义是没有例外,但我仍然得到一个解密密钥,其字节[]长度为256,而不是16

另一个有趣的问题。如果Java服务器具有从iOS设备生成的公钥,并使用
Cipher.getInstance(“RSA”)
进行加密,则手机可以使用RSA/PKCS1正确解码消息

***编辑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字节。

解决方案使用
RSA/None/NoPadding
好的,我让它工作了,但是没有填充。这一部分真的让我很沮丧,我把它留给其他人来帮助我。也许我最终会在github上发布我的库,一个用于Obj-C,一个用于Java。这是我到目前为止发现的

TL;DR:使用最少的属性将密钥保存到密钥链,以简化检索。使用
SecKeyEncrypt
加密,但使用
kSecPaddingNone
。使用BouncyCastle和算法在Java端解密
RSA/None/NoPadding

从Java向iOS发送RSA公钥 使用X.509证书 我想验证是否直接发送公钥,去掉ASN.1头并保存实际上是在做它应该做的事情。因此,我考虑将公钥作为证书发送。我想感谢David Benko提供了一个加密库()来帮助我进行证书转换。实际上我没有使用他的图书馆,因为1。我已经在使用
RNCryptor
/
jncyptor
进行AES加密和2。他没有Java端组件,所以我需要在那里编写自己的AES解密,我不想这样做。对于那些感兴趣并希望采用这种方法的人,下面是我的代码,用于在Java端创建证书,然后在iOS上将该证书转换为公钥:

*重要提示:请将
e.printStackTrace()
替换为真实的日志记录语句。我只在测试时使用它,而不是在生产中使用它

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

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;
}
- (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公钥 需要注意的是,不需要将公钥作为证书发送。事实上,在发现公钥保存不正确(见下文)后,我还原了此代码并将公钥保存到我的设备中。你需要去掉其中一篇博文中提到的
ASN.1
标题。该代码在此处重新发布(格式清晰)

因此,我只需像这样保存密钥:

- (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修改):


我也有同样的问题。可以使用
kSecPaddingNone
,但是不能使用
kSecPaddingPKCS1
和Java代码中的任何
PKCS1
组合

但是,不使用填充物是不好的

因此,在iOS上,将
kSecPaddingNone
替换为
kSecPaddingOAEP
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;
    }
}