Импортировать ключи RSA в iPhone keychain?

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

нужно ли сначала импортировать их в брелок? Если да, то как?

спасибо!

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

EDIT: используя приведенный ниже метод, мы смогли импортировать ключи до размера 4096. Любой размер ключа RSA больше, чем это, кажется, отклоняется keychain. Мы получаем статус успеха, но мы не получаем ссылку на ключ.

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

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

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

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

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

Если вы посмотрите на проект iOS, который я связал, вы увидите, что они включают образцы ключей. Вставьте закрытый ключ в АСН.1 дешифратора. Вы заметите, что у вас есть тег последовательности, за которым следует несколько целочисленных значений.

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

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

Почему это так, а не иначе? Посмотреть текст колонтитула. Закрытый ключ говорит - - - начните закрытый ключ RSA - - -, открытый ключ говорит - - - начните открытый ключ - - -. Идентификатор объекта, который вы увидите в открытом ключе: 1.2.840.113549.1.1.1. Это ID, который является статическим тегом, идентифицирующим содержащийся ключ как ключ типа RSA.

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

Keychain не будет импортировать ключ RSA с этим ASN.Заголовок 1. Вам нужно раздеть его до последней последовательности. В этот момент Вы можете поставить его в keychain, а keychain смог получить размер блока и другие ключевые атрибуты.

поэтому, если начать закрытый ключ RSA есть, вам не нужно делать зачистку. Если это -- begin PRIVATE KEY - - -, вам нужно будет удалить эти начальные заголовки, прежде чем поместить его в keychain.

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

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

первый-это тег версии. Второй-модуль, а третий - публичный показатель.

просмотр открытого ключа (после ASN.1 зачистка заголовка) вы видите последовательность затем следуют 2 целых числа. Вы догадались, это модуль и публичный показатель от закрытого ключа.

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

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

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

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

последнее примечание: закрытый ключ, включенный в проект, имел тег битовой строки, но тот, который я импортирую из закрытого ключа OpenSSL, имел строку октета тега.


я выкопал этот код (лицензия 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
}

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


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