解決方案與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;
}
}
謝謝@ robert-vojta!這對我也很有用。對於那些有同樣問題的人,你可以在我的答案上進行替換。我不想讚揚他的解決方案! – mikeho 2014-12-17 16:18:06
謝謝羅伯特!這真的解決了我的問題。 – 2015-04-15 19:11:14
其實工作。無論如何,OAEP更好。 – 2017-10-21 14:18:04