[英]RSA: encrypt in iOS, decrypt in 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;
}
我嘗試了很多組合,但沒有任何效果。
org.bouncycastle.crypto.DataLengthException: input too large for RSA cipher.
) 我也嘗試使用內部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
。 解密工作的意義是沒有異常,但我仍然得到一個解密密鑰,其byte []長度為256而不是長度為16。
另一個興趣點。 似乎如果Java服務器具有從iOS設備生成並使用Cipher.getInstance("RSA")
加密的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字節。
我有同樣的問題。 適用於kSecPaddingNone
,但不適用於Java代碼中任何PKCS1
組合的kSecPaddingPKCS1
。
但是,沒有填充使用它並不是一個好主意。
因此,在iOS,更換kSecPaddingNone
與kSecPaddingOAEP
和使用RSA/NONE/OAEPWithSHA1AndMGF1Padding
在Java代碼中。 這對我有用。
RSA/None/NoPadding
解決方案 好的,所以我讓它工作但沒有PADDING 。 這部分讓我非常沮喪,我將其留給其他人試圖幫助。 也許我最終將在github上發布我的庫,一個用於Obj-C,一個用於Java。 這是我到目前為止發現的。
TL; DR :使用最少的屬性將密鑰保存到鑰匙串,以使檢索更簡單。 加密與SecKeyEncrypt
但使用kSecPaddingNone
。 使用BouncyCastle和算法RSA/None/NoPadding
解密Java端。
我想驗證是否直接發送公鑰,剝離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;
}
請務必注意,您不需要將公鑰作為證書發送。 實際上,在發現公鑰被錯誤保存后(見下文),我還原了這段代碼並將公鑰保存到我的設備中。 您需要刪除其中一篇博文中提到的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"];
}
這令人抓狂。 事實證明,即使我將鑰匙保存在鑰匙鏈上,我檢索的也不是我放入的鑰匙! 當我將保存的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;
}
}
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.