Импортировать ключи RSA в связку ключей iPhone?

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

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

Спасибо!


person Anant    schedule 27.04.2009    source источник
comment
Мы поняли это некоторое время назад - SecItemAdd () будет работать, если переданы правильные атрибуты словаря. См. hg.mozilla.org/services / fx-home / file / tip / Sources /   -  person Anant    schedule 14.03.2011
comment
вся магия заключается в аргументе keyData, который вы откуда-то получаете (downloadPrivateKeyBundle) и расшифровываете. Каков формат этого большого двоичного объекта NSData?   -  person Uri    schedule 20.08.2012


Ответы (5)


Итак, в 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;
}
person mikeho    schedule 20.05.2014

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

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

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

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

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

У меня явно недостаточно «репутации», чтобы размещать более двух ссылок. Итак, для следующего примера вставьте информацию base64 (все, кроме --- BEGIN * KEY --- и --- END * KEY --- в: 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, вам не нужно выполнять удаление. Если это - BEGIN 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.

person Mrbass21    schedule 02.10.2014

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

person Anant    schedule 10.01.2014

Я откопал этот код (лицензия 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
}
person anisoptera    schedule 28.04.2009
comment
Код на самом деле не работает с iPhone SDK, хотя он подходит для общей разработки Mac OS X на реальном компьютере. Спасибо, в любом случае! - person Anant; 11.06.2009
comment
Добавьте несколько директив компилятора #if TARGET_IPHONE_SIMULATOR и #else, и вы сможете получить что-то, что работает в симуляторе и устройстве. (У вас есть версия для Mac выше, просто найдите аналоги iPhone. Я занимаюсь аналогичной задачей, или я бы просто ответил сам.) - person bbrown; 24.06.2009
comment
@bbrown: вообще бесполезно, чувак - person Stefan Arentz; 25.08.2010
comment
Я проголосовал против, потому что на самом деле он не выполняет то, что хочет @Anant. Если бы вы отредактировали его, чтобы включить код iOS, это могло бы быть хорошим ответом. - person ; 06.01.2011
comment
@anisoptera: Мне удалось импортировать закрытый ключ в связку ключей в приложении Mac. Теперь я хочу экспортировать данные закрытого ключа обратно из цепочки для ключей, не заставляя цепочку для ключей запрашивать пароль. Могу ли я что-нибудь сделать для этого? - person Subhash; 04.07.2011
comment
Я почти уверен, что вы не сможете этого сделать, поскольку это было бы огромной дырой в безопасности, если бы случайные приложения могли извлекать личные данные из связки ключей, не спрашивая пользователя. - person anisoptera; 08.07.2011

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

person bbrown    schedule 24.06.2009