2009-04-27 18 views
7

Tengo un par de objetos NSString que representan un par de claves pública-privada RSA (no generada por SecKeyCreatePair, sino por una biblioteca de criptografía externa). ¿Cómo puedo crear objetos SecKeyRef (que son necesarios para los métodos SecKeyDecrypt/Encrypt) de estos objetos NSString?Importar claves RSA al llavero iPhone?

¿Es necesario importarlos en el llavero del primero? ¿Si es así, cómo?

Gracias!

+1

Nos dimos cuenta de esto hace un tiempo: SecItemAdd() funcionará si se pasan los atributos correctos del diccionario. Ver http://hg.mozilla.org/services/fx-home/file/tip/Sources/NetworkAndStorage/CryptoUtils.m#l931 – Anant

+0

toda la magia está en el argumento keyData, que se obtiene de alguna parte (downloadPrivateKeyBundle) y descifrar . ¿Cuál es el formato de este blob NSData? – Uri

Respuesta

1

Cavé este código (licencia BSD) desde the MYcrypto library. Parece hacer lo que quieras.

SecKeyRef importKey(NSData *data, 
        SecExternalItemType type, 
        SecKeychainRef keychain, 
        SecKeyImportExportParameters *params) { 
    SecExternalFormat inputFormat = (type==kSecItemTypeSessionKey) ?kSecFormatRawKey :kSecFormatUnknown; 
    CFArrayRef items = NULL; 

    params->version = SEC_KEY_IMPORT_EXPORT_PARAMS_VERSION; 
    params->flags |= kSecKeyImportOnlyOne; 
    params->keyAttributes |= CSSM_KEYATTR_EXTRACTABLE; 
    if (keychain) { 
     params->keyAttributes |= CSSM_KEYATTR_PERMANENT; 
     if (type==kSecItemTypeSessionKey) 
      params->keyUsage = CSSM_KEYUSE_ENCRYPT | CSSM_KEYUSE_DECRYPT; 
     else if (type==kSecItemTypePublicKey) 
      params->keyUsage = CSSM_KEYUSE_ENCRYPT | CSSM_KEYUSE_VERIFY | CSSM_KEYUSE_WRAP; 
     else if (type==kSecItemTypePrivateKey) 
      params->keyUsage = CSSM_KEYUSE_DECRYPT | CSSM_KEYUSE_SIGN; 
    } 
    if (!check(SecKeychainItemImport((CFDataRef)data, NULL, &inputFormat, &type, 
            0, params, keychain, &items), 
       @"SecKeychainItemImport")) 
     return nil; 
    if (!items || CFArrayGetCount(items) != 1) 
     return nil; 
    SecKeyRef key = (SecKeyRef)CFRetain(CFArrayGetValueAtIndex(items,0)); 
    CFRelease(items); 
    return key; // caller must CFRelease 
} 
+0

El código realmente no funciona en el iPhone SDK, aunque está bien para el desarrollo general de Mac OS X en una computadora real. ¡Gracias de cualquier manera! – Anant

+0

Agregue algunas directivas de compilador '#if TARGET_IPHONE_SIMULATOR' y' # else' y podrá obtener algo que funcione en el simulador y en el dispositivo. (Tienes la versión de Mac anterior, solo busca los análogos de iPhone. Estoy en medio de una tarea similar o simplemente me respondería a mí mismo). – bbrown

+0

@bbrown: nada útil en absoluto amigo –

3

Por lo tanto, en iOS, el llavero es un espacio aislado, AFAIK. Esto significa que cualquier cosa que coloque en el llavero solo es accesible por su aplicación y su aplicación a menos que especifique lo contrario. Usted tiene que permitir Llavero Compartiendo bajo Capacidades en la configuración del proyecto.

Ahora que está fuera del camino, que sin duda puede importar los datos. Ya que son NSString objetos, que le primero que tiene que convertir a NSData objetos para importar correctamente. Lo más probable es que están codificados en base 64, por lo que tendrá que invertir:

NSData *decodedData = [[NSData alloc] initWithBase64EncodedString:base64String options:0]; 

Ahora que se hace eso, puede utilizar este método para guardar su clave tanto para el llavero y obtener el SecKeyRef:

/** 
* key: the data you're importing 
* keySize: the length of the key (512, 1024, 2048) 
* isPrivate: is this a private key or public key? 
*/ 
- (SecKeyRef)saveKeyToKeychain:(NSData *)key keySize:(NSUInteger)keySize private:(BOOL)isPrivate { 
    OSStatus sanityCheck = noErr; 
    NSData *tag; 
    id keyClass; 

    if (isPrivate) { 
     tag = privateTag; 
     keyClass = (__bridge id) kSecAttrKeyClassPrivate; 
    } 
    else { 
     tag = publicTag; 
     keyClass = (__bridge id) kSecAttrKeyClassPublic; 
    } 

    NSDictionary *saveDict = @{ 
      (__bridge id) kSecClass : (__bridge id) kSecClassKey, 
      (__bridge id) kSecAttrKeyType : (__bridge id) kSecAttrKeyTypeRSA, 
      (__bridge id) kSecAttrApplicationTag : tag, 
      (__bridge id) kSecAttrKeyClass : keyClass, 
      (__bridge id) kSecValueData : key, 
      (__bridge id) kSecAttrKeySizeInBits : [NSNumber numberWithUnsignedInteger:keySize], 
      (__bridge id) kSecAttrEffectiveKeySize : [NSNumber numberWithUnsignedInteger:keySize], 
      (__bridge id) kSecAttrCanDerive : (__bridge id) kCFBooleanFalse, 
      (__bridge id) kSecAttrCanEncrypt : (__bridge id) kCFBooleanTrue, 
      (__bridge id) kSecAttrCanDecrypt : (__bridge id) kCFBooleanFalse, 
      (__bridge id) kSecAttrCanVerify : (__bridge id) kCFBooleanTrue, 
      (__bridge id) kSecAttrCanSign : (__bridge id) kCFBooleanFalse, 
      (__bridge id) kSecAttrCanWrap : (__bridge id) kCFBooleanTrue, 
      (__bridge id) kSecAttrCanUnwrap : (__bridge id) kCFBooleanFalse 
    }; 

    SecKeyRef savedKeyRef = NULL; 
    sanityCheck = SecItemAdd((__bridge CFDictionaryRef) saveDict, (CFTypeRef *)&savedKeyRef); 
    if (sanityCheck != errSecSuccess) { 
     LOGGING_FACILITY1(sanityCheck != noErr, @"Problem saving the key to keychain, OSStatus == %d.", sanityCheck); 
    } 

    return savedKeyRef; 
} 

Más tarde, si desea recuperar el SecKeyRef del llavero, puede utilizar esto:

- (SecKeyRef)getKeyRef:(BOOL)isPrivate { 
    OSStatus sanityCheck = noErr; 
    NSData *tag; 
    id keyClass; 
    if (isPrivate) { 
     if (privateKeyRef != NULL) { 
      // already exists in memory, return 
      return privateKeyRef; 
     } 
     tag = privateTag; 
     keyClass = (__bridge id) kSecAttrKeyClassPrivate; 
    } 
    else { 
     if (publicKeyRef != NULL) { 
      // already exists in memory, return 
      return publicKeyRef; 
     } 
     tag = publicTag; 
     keyClass = (__bridge id) kSecAttrKeyClassPublic; 
    } 

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

    SecKeyRef keyReference = NULL; 
    sanityCheck = SecItemCopyMatching((__bridge CFDictionaryRef) queryDict, (CFTypeRef *) &keyReference); 
    if (sanityCheck != errSecSuccess) { 
     NSLog(@"Error trying to retrieve key from server. isPrivate: %d. sanityCheck: %li", isPrivate, sanityCheck); 
    } 

    if (isPrivate) { 
     privateKeyRef = keyReference; 
    } 
    else { 
     publicKeyRef = keyReference; 
    } 
    return keyReference; 
} 
3

EDIT: Utilizando el método siguiente pudimos importar claves de hasta tamaño 4096. Cualquier RSA Keysize más grande th y esto parece ser rechazado por el llavero. Recuperamos un estado de éxito, pero no obtenemos una referencia a la clave.

Sólo una nota rápida con respecto a la importación de RSA claves privadas/públicas. En mi caso, necesitaba importar una clave privada generada por OpenSSL.

This project hace la mayor parte de lo que quería en cuanto a ponerlo en el llavero. Como puede ver, solo tiene una clave de datos en la que introduce los datos clave y el llavero calcula el tamaño del bloque, etc. de la clave. Keychain admite una clave codificada en ASN.1.

Cuando exporta una clave a un archivo, lo más probable es que sea un archivo PEM. Un archivo PEM es solo una estructura DER codificada en base64. La estructura DER es una estructura generalizada, pero en el caso de OpenSSL, generalmente es una clave privada o pública codificada en ASN.1.

La estructura ASN.1 se muestra bastante bien here. POR FAVOR, lea y comprenda cómo leer la estructura ASN.1 antes de probar y manipular esto, o la importación de una clave de otro tamaño fallará.

Aparentemente no tengo suficiente "reputación" para publicar más de 2 enlaces.Por lo tanto, para el siguiente ejemplo, pegue la información de base64 (todo excepto --- INICIAR * TECLA --- y --- TECLA END * en: lapo.it/asn1js.

Si mira el iOS proyecto que he vinculado, verá que incluyen claves de muestra. Pegue la clave privada en el decodificador ASN.1. Notará que tiene una etiqueta SEQUENCE seguida de varios valores INTEGER.

Ahora pegue la clave pública. Notarás que la clave pública tiene dos elementos de información en común con la clave privada: el módulo y el exponente. En la clave privada, este es el segundo y tercer valor INTEGER. También tiene información en la parte superior. 2 SECUENCIAS adicionales, una IDENTIFICACIÓN DE OBJETO, NULA y etiquetas BIT STRING

También notará en el proyecto que llama a una función especial para procesar esa clave pública. Lo que hace es quitar toda la información del encabezado hasta que llega a la etiqueta SEQUENCE más interna. En ese punto, lo trata exactamente igual que la clave privada y puede ponerlo en el llavero.

¿Por qué hacer esto para uno y no para el otro? Mire el texto del encabezado y pie de página. La clave privada dice --- BEGIN RSA PRIVATE KEY ---, la clave pública dice --- BEGIN PUBLIC KEY ---. El ID de objeto que verá en la clave pública es: 1.2.840.113549.1.1.1. Esta es una ID que es una etiqueta estática que identifica la clave contenida como una clave de tipo RSA.

Dado que la clave privada tiene RSA en el preámbulo, se supone que es una clave RSA, y que la información ASN.1 del encabezado no es necesaria para identificar la clave. La clave pública es solo una clave genérica, por lo que se requiere un encabezado para identificar qué tipo de clave es.

El llavero NO importará una clave RSA con este encabezado ASN.1. Necesitas desnudarlo hasta la última SECUENCIA. En ese punto, puede ponerlo en el llavero, y el llavero pudo derivar el tamaño del bloque y otros atributos clave.

Por lo tanto, si BEGIN RSA PRIVATE KEY está allí, no es necesario que realice la extracción. Si es - COMIENZA LA CLAVE PRIVADA ---, tendrá que quitar esos encabezados iniciales antes de ponerlo en el llavero.

En mi caso, también necesitaba la clave pública. No pudimos encontrar la forma de obtenerlo desde el llavero una vez que pusimos la clave privada con éxito (es posible que hayamos perdido algo), así que creamos una clave pública ASN.1 a partir de la clave privada y la importamos a keycahin.

En la clave privada (Después del stripping del encabezado ASN.1), tendrá una etiqueta SEQUENCE seguida de 3 etiquetas INTEGER (hay más INTEGERS después de esto, pero los 3 primeros son todo lo que nos importa).

La primera es una etiqueta VERSIÓN. El segundo es el Módulo y el tercero es el exponente público.

Observando la clave pública (después de eliminar el encabezado ASN.1) Ver SECUENCIA seguido de 2 INTEGRADORES. Lo adivinaste, este es el módulo y el exponente público de la clave privada.

Así que todo lo que necesita hacer es:

  1. Coge el módulo y exponente público de la clave privada
  2. Crear una etiqueta de secuencia en un búfer y establecer su longitud para [longitud módulo] + [exponente longitud]. (Al escribir estos bytes en el búfer, lo más probable es que necesite revertir el endian de los bytes. Lo hice al menos.)
  3. añadir los datos de módulo agarraste de la clave privada
  4. Añadir los datos exponente agarraste de la clave privada

Eso es todo lo que necesita hacer para crear una clave pública de su clave privada ha importado . No parece haber mucha información para importar claves RSA que no generas en el dispositivo, y he escuchado que las claves generadas en el dispositivo NO contienen estos encabezados ASN.1, pero nunca lo intenté . Nuestras claves son bastante grandes y llevan un dispositivo demasiado largo para generar. La única opción que encontré fue usar OpenSSL, donde tienes que compilar el tuyo para iOS. Prefiero usar el marco de seguridad siempre que sea posible.

Todavía soy bastante nuevo en el desarrollo de iOS, y estoy seguro de que alguien conoce una función simple que hace todo esto que no pude encontrar, y MIRÉ. Esto parece funcionar bien hasta que haya una API más fácil para procesar claves.

Una nota final: La clave privada incluida con el proyecto tenía una etiqueta de BIT STRING, pero la que importé de una clave privada generada de OpenSSL tenía la etiqueta OCTET STRING.

Cuestiones relacionadas