encriptar ejemplo desencriptar descifrar con cifrar java ios7 rsa

ejemplo - encriptar y desencriptar rsa en java



RSA: cifrar en iOS, descifrar en Java (2)

Tengo una clave pública que se envía desde un servidor Java. Las cadenas codificadas en base64 coinciden antes de que decodifique y elimine los encabezados ASN.1. SecItemAdd la clave pública en el llavero con SecItemAdd .

Así que estoy tratando de cifrar los datos usando la clave pública y descifrarlos con la clave privada en Java. Estoy usando SecKeyEncrypt en el lado de iOS y Cipher en el lado de Java.

Lo que estoy cifrando es la clave AES simétrica que cifra mis datos reales, por lo que la longitud de la clave es de 16 bytes. Cuando simplemente se codifica la clave en base64, todo funciona, así que sé que algo está mal con este cifrado RSA.

Aquí hay un ejemplo de mi llamada a iOS:

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

Aquí hay un ejemplo de mi llamada a 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; }

He probado tantas combinaciones y nada ha funcionado.

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

También he intentado usar el proveedor interno de Java así como el proveedor de BouncyCastle. La javax.crypto.BadPaddingException se lanza cada vez, pero el mensaje es diferente para cada combinación. Algunos muestran que los Data must start with zero , mientras que otros son Message is larger than modulus .

El iOS: PKCS1, Java: RSA no lanza una excepción, pero la matriz de byte[] desencriptada resultante debe tener una longitud de 16, pero su longitud es de 256, lo que significa que el relleno no se elimina correctamente.

¿Alguien puede ayudar?

*** EDITAR ***

A medida que realizo más pruebas, encontré esta página ( http://javadoc.iaik.tugraz.at/iaik_jce/current/iaik/pkcs/pkcs1/RSACipher.html ), que esencialmente me dice que RSA == RSA/None/PKCS1Padding . El descifrado funciona en el sentido de que no hay excepciones, pero sigo recibiendo una clave descifrada cuyo byte [] tiene una longitud de 256 en lugar de una de 16.

Otro punto de interés. Parece que si el servidor Java tiene la clave pública generada desde el dispositivo iOS y cifrada con Cipher.getInstance("RSA") , el teléfono puede decodificar el mensaje correctamente con RSA / PKCS1.

*** EDITAR 2 ***

He mirado estos tutoriales y he revisado mi código nuevamente en el lado de iOS:

Por lo que puedo decir, mi código está haciendo todo correctamente. Una diferencia significativa estaba en cómo guardaba la clave, así que intenté guardarla de la otra manera:

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

La operación de guardar se realizó correctamente, pero el resultado final fue el mismo: la clave AES descifrada tenía todavía una longitud de 256 bytes en lugar de 16 bytes.


Solución con RSA/None/NoPadding

De acuerdo, lo tengo funcionando pero SIN PADDING . Esta parte realmente me frustra y lo dejo a otros para tratar de ayudar. Tal vez finalmente libere lo que tengo como biblioteca en github, uno para Obj-C, uno para Java. Esto es lo que descubrí hasta ahora.

TL; DR : guarde la clave en el llavero con atributos mínimos para simplificar la recuperación. Cifre con SecKeyEncrypt pero use kSecPaddingNone . Descifre en el lado de Java con BouncyCastle y el algoritmo RSA/None/NoPadding .

Enviando RSA Public Key a iOS desde Java

Usando el certificado X.509

Quería verificar si enviar la clave pública directamente, eliminar el encabezado ASN.1 y guardar estaba haciendo lo que se suponía que debía hacer. Así que miré a enviar la clave pública como un certificado. Quiero darle crédito a David Benko por proporcionar una biblioteca de cifrado ( https://github.com/DavidBenko/DBTransitEncryption ) que me ayudó con la conversión del certificado. Realmente no RNCryptor su biblioteca porque 1. Ya estoy usando RNCryptor / JNCryptor para mi cifrado AES y 2. él no tiene un componente lateral de Java, así que necesitaría escribir mi propio descifrado AES allí y no lo hice. No quiero hacer eso. Para aquellos interesados ​​y que quieran adoptar este enfoque, aquí está mi código para crear un certificado en el lado de Java y luego convertir ese certificado en una clave pública en iOS:

* Nota importante: reemplace e.printStackTrace() con declaraciones de registro reales. Solo utilicé esto para pruebas y NO en producción.

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

Usando la clave pública RSA

Es importante tener en cuenta que no es necesario enviar la clave pública como un certificado. De hecho, después de descubrir que la clave pública se estaba guardando incorrectamente (ver más abajo), revertí este código y guardé la clave pública en mi dispositivo. Deberá eliminar el encabezado ASN.1 como se menciona en una de las publicaciones del blog. Ese código se vuelve a publicar aquí (formateado para mayor claridad).

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

Así que simplemente guardaría la clave así:

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

Guardar la clave pública RSA para llavero

Fue enloquecedor. Resultó que aunque guardé mi llave para el llavero, ¡lo que recuperé no fue lo que puse! Descubrí esto por accidente cuando comparaba la clave base64 que estaba guardando en la clave base64 que estaba usando para cifrar mi clave AES. Así que descubrí que es mejor simplificar el NSDictionary utilizado al guardar la clave. Esto es lo que terminé con:

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

Para recuperar mi clave, utilizo los siguientes métodos:

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

Al final del día, solo pude hacer que funcionara sin relleno. No estoy seguro de por qué BouncyCastle no pudo eliminar el relleno, así que si alguien tiene alguna idea, hágamelo saber.

Aquí está mi código para cifrar (modificado 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; }

Así es como descifro en el lado de 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; } }


Tuve el mismo problema kSecPaddingNone con kSecPaddingNone , pero no funciona con kSecPaddingPKCS1 con ninguna combinación PKCS1 en el código Java.

Pero, no es buena idea usarlo sin relleno.

Entonces, en iOS, reemplace kSecPaddingNone con kSecPaddingOAEP y use RSA/NONE/OAEPWithSHA1AndMGF1Padding en su código Java. Esto funciona para mí.