Импортировать ключи RSA в связку ключей iPhone? - PullRequest
7 голосов
/ 27 апреля 2009

У меня есть пара объектов NSString, которые представляют пару открытых и закрытых ключей RSA (не сгенерированную SecKeyCreatePair, а внешней библиотекой шифрования). Как я могу создать объекты SecKeyRef (которые необходимы для методов SecKeyDecrypt / Encrypt) из этих объектов NSString?

Нужно ли мне сначала импортировать их в связку ключей? Если да, то как?

Спасибо!

Ответы [ 5 ]

4 голосов
/ 20 мая 2014

Итак, в iOS связка ключей находится в песочнице, AFAIK. Это означает, что все, что вы помещаете в цепочку для ключей, доступно только вашему приложению и только вашему приложению, если не указано иное. Вы должны включить Общий доступ к связке ключей в Возможности в настройках проекта.

Теперь, когда это не так, вы, безусловно, можете импортировать данные. Поскольку они NSString объекты, вам сначала нужно преобразовать их в NSData объекты, чтобы импортировать их правильно. Скорее всего, они закодированы в Base64, так что вам придется изменить это:

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

Теперь, когда это сделано, вы можете использовать этот метод, чтобы сохранить ключ в цепочке для ключей и получить 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;
}

Позже, если вы хотите получить SecKeyRef из цепочки для ключей, вы можете использовать это:

- (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 голосов
/ 02 октября 2014

РЕДАКТИРОВАТЬ: Используя приведенный ниже метод, мы смогли импортировать ключи размером до 4096. Любой размер ключа RSA, больший, чем этот, кажется, отклоняется связкой ключей. Мы возвращаем статус успеха, но не получаем ссылку на ключ.

Просто краткое замечание относительно импорта закрытых / открытых ключей RSA. В моем случае мне нужно было импортировать закрытый ключ, сгенерированный OpenSSL.

Этот проект делает большую часть того, что я хотел, вплоть до помещения его в связку ключей. Как вы можете видеть, у него есть только ключевые данные, в которые вы вставляете данные ключа, и цепочка для ключей вычисляет размер блока и т. Д. Из ключа. Брелок поддерживает ключ в кодировке ASN.1.

Когда вы экспортируете ключ в файл, это, скорее всего, файл PEM. Файл PEM - это просто структура DER в кодировке base64. Структура DER является обобщенной структурой, но в случае OpenSSL это обычно закрытый или открытый ключ в кодировке ASN.1.

Структура ASN.1 отображается довольно хорошо здесь . ПОЖАЛУЙСТА, прочитайте и поймите, как читать структуру ASN.1, прежде чем пытаться поиграть с этим, иначе импорт ключа другого размера завершится неудачей.

Мне явно не хватает «репутации», чтобы публиковать более 2 ссылок. Поэтому для следующего примера вставьте информацию base64 (все, кроме --- BEGIN * KEY --- и --- END * KEY --- at: lapo.it/asn1js.

Если вы посмотрите на проект iOS, который я связал, то увидите, что они содержат примеры ключей. Вставьте закрытый ключ в декодер ASN.1. Вы заметите, что у вас есть тег SEQUENCE, за которым следуют несколько значений INTEGER.

Теперь вставьте открытый ключ. Вы заметите, что открытый ключ имеет две части информации, общие с закрытым ключом. Модуль и показатель степени. В закрытом ключе это второе и третье значения INTEGER. У этого также есть некоторая информация наверху. Он имеет 2 дополнительные последовательности, теги OBJECT ID, NULL и BIT STRING.

В проекте вы также заметите, что он вызывает специальную функцию для обработки этого открытого ключа. Он удаляет всю информацию заголовка, пока не доберется до самого внутреннего тега SEQUENCE. В этот момент он обращается с ним точно так же, как с закрытым ключом, и может поместить его в связку ключей.

Почему это для одного, а не для другого? Посмотрите на верхний и нижний колонтитулы. Закрытый ключ говорит: «НАЧАЛО RSA ЧАСТНЫЙ КЛЮЧ» ---, открытый ключ говорит: «НАЧАТЬ ПУБЛИЧНЫЙ КЛЮЧ». Идентификатор объекта, который вы увидите в открытом ключе: 1.2.840.113549.1.1.1. Это идентификатор, представляющий собой статический тег, идентифицирующий содержащийся ключ как ключ типа RSA.

Поскольку в преамбуле закрытый ключ имеет RSA, предполагается, что это ключ RSA, и эта информация заголовка ASN.1 не требуется для идентификации ключа. Открытый ключ - это просто общий ключ, поэтому для определения его типа требуется заголовок.

Брелок НЕ будет импортировать ключ RSA с этим заголовком ASN.1. Вы должны раздеть это до последней ПОСЛЕДОВАТЕЛЬНОСТИ. В этот момент вы можете поместить его в связку ключей, и связка ключей смогла получить размер блока и другие ключевые атрибуты.

Так что, если BEGIN RSA PRIVATE KEY там, вам не нужно делать зачистку. Если это - НАЧАТЬ ЧАСТНЫЙ КЛЮЧ ---, вам нужно будет удалить эти начальные заголовки, прежде чем поместить их в цепочку для ключей.

В моем случае мне также был нужен открытый ключ. Мы не могли придумать способ получить его из цепочки для ключей, как только мы успешно вставили закрытый ключ (возможно, мы просто что-то пропустили), поэтому мы фактически создали открытый ключ ASN.1 из закрытого ключа и импортировали его в keycahin.

В закрытом ключе (после удаления заголовка ASN.1) у вас будет тег SEQUENCE, за которым следуют 3 тега INTEGER (после этого есть еще INTEGERS, но первые 3 - все, что нам нужно).

Первый - это тег VERSION. Второй - это Модуль, а третий - открытый показатель.

Просмотр открытого ключа (после удаления заголовка ASN.1). Вы видите SEQUENCE, за которой следуют 2 INTEGERS. Как вы уже догадались, это модуль и открытый показатель из закрытого ключа.

Так что все, что вам нужно сделать, это:

  1. Получите модуль и открытый показатель от закрытого ключа
  2. Создайте тег SEQUENCE в буфере и установите его длину [длина модуля] + [длина экспоненты]. (Когда вы записываете эти байты в буфер, вам, скорее всего, нужно будет изменить обратный порядок байтов. Я сделал по крайней мере.)
  3. Добавить данные модуля, которые вы взяли с закрытого ключа
  4. Добавьте данные экспоненты, которые вы взяли из закрытого ключа

Это все, что вам нужно сделать, чтобы создать открытый ключ из вашего закрытого ключа, который вы импортировали. Кажется, не так много информации для импорта ключей RSA, которые вы не генерируете на устройстве, и я слышал, что ключи, сгенерированные на устройстве, НЕ содержат эти заголовки ASN.1, но я никогда не пробовал , Наши ключи довольно большие и требуют слишком много времени для генерации. Единственный вариант, который я когда-либо нашел, - это использовать OpenSSL, где вы должны скомпилировать свой собственный для iOS. Я бы предпочел использовать систему безопасности, где это возможно.

Я все еще довольно новичок в разработке для iOS, и я уверен, что кто-то знает простую функцию, которая делает все это, что я не смог найти, и я СМОТРЕЛ. Кажется, это работает нормально, пока не станет доступен более простой API для обработки ключей.

Последнее замечание: закрытый ключ, включенный в проект, содержал тег BIT STRING, но тот, который я импортировал из закрытого ключа, сгенерированного OpenSSL, имел тег OCTET STRING.

1 голос
/ 11 января 2014

Ответом было позвонить SecItemAdd с правильным набором флагов. Смотри: http://hg.mozilla.org/services/fx-home/file/tip/Sources/NetworkAndStorage/CryptoUtils.m#l931

1 голос
/ 29 апреля 2009

Я выкопал этот код (лицензия BSD) из библиотеки MYcrypto . Кажется, делать то, что вы хотите.

    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 голосов
/ 24 июня 2009

Я не уверен, работает ли код в этой ветке форума разработчиков Apple , но это похоже на прямой ответ на ваш вопрос.

...