RSA: cryptage en iOS, décryptage en Java

j'ai une clé publique envoyée depuis un serveur Java. Les chaînes encodées de base64 correspondent avant que je ne décode et n'efface l'ASN.1 les en-têtes. Je stocker la clé publique dans le trousseau de clés avec SecItemAdd.

J'essaie donc de chiffrer les données en utilisant la clé publique et de les déchiffrer avec la clé privée en Java. Je suis à l'aide de SecKeyEncrypt du côté de iOS et Cipher sur le Java côté.

ce que je crypte est la clé AES symétrique qui crypte mes données réelles, donc la longueur de la clé est 16 octets. Quand base64 encodant simplement la clé, tout fonctionne, donc je sais que quelque chose ne va pas avec ce cryptage RSA.

voici un exemple de mon appel iOS:

OSStatus sanityCheck = SecKeyEncrypt(publicKey,
        kSecPaddingPKCS1,
        (const uint8_t *) [incomingData bytes],
        keyBufferSize,
        cipherBuffer,
        &cipherBufferSize
);

voici un exemple de mon appel 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;
}

j'ai essayé de nombreuses combinaisons et rien n'a fonctionné.

  • iOS: PKCS1, Java: RSA / ECB/PKCS1Padding
  • iOS: PKCS1, Java: RSA
  • iOS: PKCS1, Java: RSA / None/PKCS1Padding (lancers org.bouncycastle.crypto.DataLengthException: input too large for RSA cipher.)
  • iOS: OAEP, Java: RSA / ECB/OAEPWithSHA-1AndMGF1Padding
  • iOS: OAEP, Java: RSA / ECB/OAEPWithSHA-256AndMGF1Padding

j'ai également essayé D'utiliser le fournisseur Java interne ainsi que le fournisseur BouncyCastle. javax.crypto.BadPaddingException lance à chaque fois, mais le message est différent pour chaque combinaison. Certains montrent Data must start with zero, tandis que d'autres sont Message is larger than modulus.

iOS: PKCS1, Java: RSA ne jette pas une exception, mais le résultat déchiffré byte[] array devrait être de longueur 16, mais c'est de longueur 256, ce qui signifie que le rembourrage n'est pas correctement retiré.

quelqu'un Peut-il aider?

*** EDIT ***

Comme je suis en train de faire plus de tests, je suis tombé sur cette page (http://javadoc.iaik.tugraz.at/iaik_jce/current/iaik/pkcs/pkcs1/RSACipher.html), qui me dit que RSA == RSA/None/PKCS1Padding. Le décryptage fonctionne dans le sens qu'il n'y a pas d'exceptions, mais je reçois toujours un clé déchiffrée dont le octet [] est de longueur 256 au lieu de longueur 16.

un Autre point d'intérêt. Il semble que si le serveur Java a la clé publique générée par le périphérique iOS et cryptée en utilisant Cipher.getInstance("RSA"), le téléphone est capable de décoder le message correctement avec RSA / PKCS1.

*** EDIT 2 ***

j'ai regardé ces tutoriels et j'ai regardé à nouveau mon code sur l'iOS côté:

autant que je sache, mon code fait tout correctement. Une différence significative était dans la façon dont je sauvais la clé, donc j'ai essayé de la sauver de l'autre façon:

    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);

Que l'enregistrement a réussi, mais le résultat final était le même: la clé AES déchiffrée était toujours de 256 octets au lieu de 16 octets.

17
demandé sur mikeho 2014-09-30 00:51:56

2 réponses

j'ai eu le même problème. Fonctionne avec kSecPaddingNone, mais ne pas travail kSecPaddingPKCS1 avec PKCS1 Combinaison En code Java.

Mais, c'est pas une bonne idée de l'utiliser sans rembourrage.

Donc, sur iOS, remplacez kSecPaddingNonekSecPaddingOAEP et utiliser RSA/NONE/OAEPWithSHA1AndMGF1Padding dans votre code Java. Cela fonctionne pour moi.

12
répondu robertvojta 2014-12-12 14:45:06

Solution avec RSA/None/NoPadding

Ok, donc j'ai eu de travail, mais SANS REMBOURRAGE. Cette partie est vraiment frustrant moi et je laisse à d'autres pour essayer de les aider. Je vais peut-être publier ce que j'ai comme bibliothèque sur github, une pour Obj-C, une pour Java. Voici ce que j'ai trouvé jusqu'à présent.

TL;DR: sauvegardez la clé du porte-clés avec les attributs minimaux pour simplifier la récupération. Chiffrez avec SecKeyEncrypt mais utiliser kSecPaddingNone. Décrypter sur Java côté avec BouncyCastle et de l'algorithme RSA/None/NoPadding.

Envoi de Clé Publique RSA pour iOS à partir de Java

Utilisation Du Certificat X. 509

je voulais vérifier si l'envoi de la clé publique directement, en enlevant L'ASN.1 header and saving faisait en fait ce qu'il était censé faire. J'ai donc regardé l'envoi de la clé publique sur un certificat. Je veux donner du crédit à David Benko pour la fourniture d'une bibliothèque de cryptage (https://github.com/DavidBenko/DBTransitEncryption) qui m'a aidé avec la conversion de certificat. Je n'ai pas vraiment utilisé sa bibliothèque parce que 1. Je suis déjà à l'aide de RNCryptor/JNCryptor pour mon cryptage AES et 2. il n'a pas de composant Java, donc j'aurais besoin d'écrire mon propre décryptage AES là-bas et je ne voulais pas faire ça. Pour ceux qui sont intéressés et qui veulent adopter cette approche, voici mon code pour créer un certificat du côté de Java et ensuite convertir que certificat de clé publique sur iOS:

* Remarque Importante: Veuillez remplacer e.printStackTrace() avec de vraies déclarations de journalisation. Je ne l'ai utilisé que pour les tests et en production.

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;
}

utilisation de la clé publique RSA

Il est important de noter que vous n'avez pas besoin d'envoyer la clé publique sur un certificat. En effet, après avoir découvert que la clé publique était sauvegardé incorrectement (voir ci-dessous), j'ai inversé ce code et ai sauvegardé la clé publique de mon appareil. Vous aurez besoin de bande de l' ASN.1 en-tête comme mentionné dans l'un des messages de blog. Ce code est reproduit ici (formaté pour plus de clarté).

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

Donc, je voudrais simplement enregistrer la clé de la sorte:

- (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"];
}

Enregistrement de Clé Publique RSA, au Trousseau

c'était exaspérant. Il s'est avéré que même si j'ai gardé ma clé pour le porte-clés, ce que j'ai récupéré n'était pas ce que je mis en! Je l'ai découvert par accident en comparant la clé base64 que j'enregistrais à la clé base64 que j'utilisais pour crypter ma clé AES. J'ai donc découvert qu'il est préférable de simplifier le NSDictionary utilisé lors de la sauvegarde de la clé. Voici ce que j'ai:

- (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];
}

Pour récupérer ma clé, j'utilise les méthodes suivantes:

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

À la fin de la journée, j'ai pu seulement faire fonctionner sans rembourrage. Je ne sais pas pourquoi BouncyCastle ne pouvait pas enlever le rembourrage, donc si quelqu'un a son point de vue, laissez-moi savoir.

Voici mon code pour le cryptage (modifié de 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;
}

Voici comment j'ai décrypter à la Java côté:

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;
    }
}
5
répondu mikeho 2014-10-02 16:17:04