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

我有一个从Java服务器发送的公钥。 在解码和去除ASN.1标头之前,base64编码的字符串匹配。 我使用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 / 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不会抛出exception,但生成的解密byte[]数组应该是长度为16,但它的长度为256,这意味着填充没有被正确剥离。

有人可以帮忙吗?

*** 编辑 ***

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

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

*** 编辑2 ***

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

  • http://blog.flirble.org/2011/01/05/rsa-public-key-openssl-ios/
  • http://blog.wingsofhermes.org/?p=42
  • http://blog.wingsofhermes.org/?p=75

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

  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字节。

我有同样的问题。 适用于kSecPaddingNone ,但不适用于Java代码中任何PKCS1组合的kSecPaddingPKCS1

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

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

使用RSA/None/NoPadding解决方案

好的,所以我让它工作但没有PADDING 。 这部分让我非常沮丧,我将其留给其他人试图帮助。 也许我最终会释放我在github上的库,一个用于Obj-C,一个用于Java。 这是我到目前为止发现的。

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

从Java发送RSA公钥到iOS

使用X.509证书

我想validation是否直接发送公钥,剥离ASN.1标头并保存实际上是在做它应该做的事情。 所以我考虑将公钥作为证书发送。 我想赞扬David Benko提供了一个加密库( https://github.com/DavidBenko/DBTransitEncryption ),帮助我进行证书转换。 我实际上并没有使用他的库,因为1.我已经使用RNCryptor / JNCryptor进行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; } 

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公钥

请务必注意,您不需要将公钥作为证书发送。 实际上,在发现公钥被正确保存后(见下文),我还原了这段代码并将公钥保存到我的设备中。 您需要删除其中一篇博文中提到的ASN.1标头。 该代码在此处重新发布(为清晰起见而格式化)。

 + (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公钥保存到Keychain

这令人抓狂。 事实certificate,即使我将钥匙保存在钥匙链上,我检索的也不是我放入的钥匙! 当我将保存的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; } }