web-dev-qa-db-ja.com

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_は例外をスローしませんが、結果の復号化された_byte[]_配列は長さ16である必要がありますが、長さ256であるため、パディングが正しく削除されません。

誰かが助けることができますか?

***[〜#〜]編集[〜#〜]***

さらにテストを行っていると、このページ( http://javadoc.iaik.tugraz.at/iaik_jce/current/iaik/pkcs/pkcs1/RSACipher.html )に出くわしました。 _RSA == RSA/None/PKCS1Padding_と教えてくれます。復号化は例外がないという意味で機能しますが、byte []が長さ16ではなく長さ256の復号化されたキーを取得しています。

もう1つの興味深い点。 JavaサーバーにiOSデバイスから生成され、Cipher.getInstance("RSA")を使用して暗号化された公開鍵がある場合、電話はRSA/PKCS1を使用してメッセージを正しくデコードできるようです。

***編集2***

私はこれらのチュートリアルを見て、iOS側でコードをもう一度調べました。

私の知る限り、私のコードはすべてを正しく実行しています。重要な違いの1つは、キーの保存方法にあったため、別の方法で保存してみました。

_    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キーの長さは16バイトではなく256バイトのままでした。

18
mikeho

私も同じ問題を抱えていました。 kSecPaddingNoneで機能しますが、機能しません _kSecPaddingPKCS1_で、Javaコードの_PKCS1_の組み合わせで機能します。

ただし、パディングなしで使用することはお勧めできません。

したがって、iOSでは、kSecPaddingNonekSecPaddingOAEPに置き換え、Javaコードで_RSA/NONE/OAEPWithSHA1AndMGF1Padding_を使用します。これは私にとってはうまくいきます。

12
zrzka

_RSA/None/NoPadding_による解決策

さて、私はそれを動作させましたが、パディングなし。この部分は本当にイライラしているので、他の人に手伝ってもらおうと思います。たぶん、私が持っているものをgithubのライブラリとしてリリースするかもしれません。1つはObj-C用、もう1つはJava用です。これが私がこれまでに見つけたものです。

TL; DR:検索を簡単にするために、最小限の属性でキーをキーチェーンに保存します。 SecKeyEncryptで暗号化しますが、kSecPaddingNoneを使用します。 BouncyCastleとアルゴリズム_RSA/None/NoPadding_を使用してJava側で復号化します。

JavaからiOSへのRSA公開鍵の送信

X.509証明書の使用

公開鍵を直接送信し、ASN.1ヘッダーを削除して保存することが、実際に本来の機能を果たしているかどうかを確認したかったのです。そこで、代わりに公開鍵を証明書として送信することを検討しました。 David Benkoが暗号化ライブラリ( https://github.com/DavidBenko/DBTransitEncryption )を提供してくれたことを称賛したいと思います。証明書の変換。 1.AES暗号化にすでにRNCryptor/JNCryptorを使用しており、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公開鍵の使用

公開鍵を証明書として送信する必要がないことに注意することが重要です。実際、公開鍵が正しく保存されていないことを発見した後(以下を参照)、このコードを元に戻し、公開鍵をデバイスに保存しました。ブログ投稿の1つに記載されているように、_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がパディングを削除できなかった理由がわからないので、誰か洞察があれば教えてください。

これが暗号化のための私のコードです(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;
    }
}
_
6
mikeho