2014-09-29 204 views
16

我有一個從Java服務器發送的公鑰。在解碼和去除ASN.1頭之前,base64編碼的字符串相匹配。我將鑰匙鏈中的公鑰存儲在SecItemAdd中。RSA:在iOS中進行加密,在Java中進行解密

所以我試圖使用公鑰加密數據並使用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,爪哇:RSA/ECB/PKCS1Padding
  • 的iOS:PKCS1,爪哇:RSA
  • 的iOS:PKCS1,爪哇:RSA /無/ PKCS1Padding(拋出org.bouncycastle.crypto.DataLengthException: input too large for RSA cipher.
  • iOS的: OAEP,爪哇:RSA/ECB/OAEPWithSHA-1AndMGF1Padding
  • 的iOS:OAEP,爪哇: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,但長度是256,這意味着填充未被正確地去除。

有人可以幫忙嗎?

*** 編輯 ***

正如我在做更多的測試,我遇到了這個頁面(http://javadoc.iaik.tugraz.at/iaik_jce/current/iaik/pkcs/pkcs1/RSACipher.html),它實際上告訴我,RSA == RSA/None/PKCS1Padding。解密的工作原理是沒有例外,但我仍然得到一個解密密鑰,其字節[]長度爲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個字節。

回答

12

我有同樣的問題。可以使用kSecPaddingNone,但不可以使用kSecPaddingPKCS1與任何PKCS1組合在Java代碼中。

但是,不使用填充來使用它不是個好主意。

因此,在iOS上,將kSecPaddingNone替換爲kSecPaddingOAEP,並在您的Java代碼中使用RSA/NONE/OAEPWithSHA1AndMGF1Padding。這對我有用。

+0

謝謝@ robert-vojta!這對我也很有用。對於那些有同樣問題的人,你可以在我的答案上進行替換。我不想讚揚他的解決方案! – mikeho 2014-12-17 16:18:06

+0

謝謝羅伯特!這真的解決了我的問題。 – 2015-04-15 19:11:14

+0

其實工作。無論如何,OAEP更好。 – 2017-10-21 14:18:04

5

解決方案與RSA/None/NoPadding

好了,我懂了工作,但沒有填充。這部分令我非常沮喪,我將其留給其他人嘗試幫忙。也許我最終會發布我作爲github上的庫,一個用於Obj-C,一個用於Java。這是我迄今發現的。

TL; DR:用最小的屬性保存鑰匙鏈,使檢索更簡單。使用SecKeyEncrypt加密,但使用kSecPaddingNone。用BouncyCastle和算法RSA/None/NoPadding解密Java端。

從Java

發送RSA公鑰的iOS使用X.509證書

我想覈實是否直接發送公鑰,剝出的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; 
} 

對象 -

- (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公鑰鑰匙扣

很鬱悶。事實證明,即使我將鑰匙保存在鑰匙鏈中,我所檢索的並不是我放入的!當我將我保存的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無法刪除填充,所以如果有人有任何見解,請告訴我。

這裏是我的加密代碼(從大衛本克修改):

- (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; 
    } 
} 
+0

嗨mikeho,我遇到了和你一樣的問題。 我看到你將發佈一個lib和iOS和Android之間的加密/解密數據與RSA算法。 你做到了嗎? – HiepNguyen 2016-03-21 09:19:32

相關問題