RSAパブリックプライベートキーペアを表すNSStringオブジェクトがいくつかあります(SecKeyCreatePairではなく、外部暗号ライブラリによって生成されます)。これらのNSStringオブジェクトからSecKeyRefオブジェクト(SecKeyDecrypt / Encryptメソッドに必要)を作成するにはどうすればよいですか?
最初にそれらをキーチェーンにインポートする必要がありますか?もしそうなら、どのように?
ありがとう!
RSAパブリックプライベートキーペアを表すNSStringオブジェクトがいくつかあります(SecKeyCreatePairではなく、外部暗号ライブラリによって生成されます)。これらのNSStringオブジェクトからSecKeyRefオブジェクト(SecKeyDecrypt / Encryptメソッドに必要)を作成するにはどうすればよいですか?
最初にそれらをキーチェーンにインポートする必要がありますか?もしそうなら、どのように?
ありがとう!
そのため、iOS では、キーチェーンはサンドボックス化されています。これは、特に指定しない限り、キーチェーンに入れたものはすべて、アプリとアプリのみからのみアクセスできることを意味します。プロジェクト設定のCapabilitiesでKeychain Sharingを有効にする必要があります。
それが邪魔にならないようになったので、データを確実にインポートできます。それらはオブジェクトであるため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;
}
編集: 以下の方法を使用して、サイズ 4096 までのキーをインポートできました。これよりも大きい RSA キーサイズは、キーチェーンによって拒否されたようです。成功ステータスが返されますが、キーへの参照は取得されません。
RSA 秘密鍵/公開鍵のインポートに関する簡単なメモです。私の場合、OpenSSL によって生成された秘密鍵をインポートする必要がありました。
このプロジェクトは、キーチェーンに入れる限り、私が望んでいたことのほとんどを行います。ご覧のとおり、キーデータを押し込むキーデータがあり、キーチェーンはキーからブロックサイズなどを計算します。キーチェーンは、ASN.1 でエンコードされたキーをサポートしています。
キーをファイルにエクスポートする場合、ほとんどの場合、それは PEM ファイルです。PEM ファイルは、base64 でエンコードされた DER 構造です。DER 構造は一般化された構造ですが、OpenSSL の場合、通常は ASN.1 でエンコードされた秘密鍵または公開鍵です。
ここでは、ASN.1 構造がよく表されています。これをいじる前に、ASN.1構造の読み方を読んで理解してください。そうしないと、別のサイズのキーをインポートすると失敗します。
2つ以上のリンクを投稿するのに十分な「評判」がないようです。したがって、次の例では、base64 情報 (--- BEGIN * KEY --- および ---END * KEY --- を除くすべてを lapo.it/asn1js.
リンクした iOS プロジェクトを見ると、サンプル キーが含まれていることがわかります。秘密鍵を ASN.1 デコーダーに貼り付けます。SEQUENCE タグの後に複数の INTEGER 値が続くことに気付くでしょう。
ここで公開鍵を貼り付けます。公開鍵には、秘密鍵と共通する 2 つの情報があることがわかります。モジュラスと指数。秘密鍵では、これは 2 番目と 3 番目の INTEGER 値です。上部にもいくつかの情報があります。2 つの追加の SEQUENCE、OBJECT ID、NULL、および BIT STRING タグがあります。
また、このプロジェクトでは、彼が特別な関数を呼び出してその公開鍵を処理していることにも気付くでしょう。最も内側の SEQUENCE タグに到達するまで、すべてのヘッダー情報を取り除きます。その時点で、彼はそれを秘密鍵とまったく同じように扱い、キーチェーンに入れることができます。
なぜこれを一方に対して行い、他方に対しては行わないのですか? ヘッダーとフッターのテキストを見てください。秘密鍵は --- BEGIN RSA PRIVATE KEY --- を示し、公開鍵は --- BEGIN PUBLIC KEY --- を示します。公開鍵に表示されるオブジェクト ID は 1.2.840.113549.1.1.1 です。これは、含まれているキーを RSA タイプのキーとして識別する静的タグである ID です。
秘密鍵のプリアンブルには RSA が含まれているため、これは RSA 鍵であると想定され、そのヘッダー ASN.1 情報は鍵を識別するために必要ありません。公開鍵は単なる一般的な鍵であるため、鍵の種類を識別するためにヘッダーが必要です。
キーチェーンは、この ASN.1 ヘッダーを含む RSA キーをインポートしません。最後の SEQUENCE まですべて削除する必要があります。その時点で、それをキーチェーンに入れることができ、キーチェーンはブロック サイズとその他のキー属性を取得できました。
したがって、BEGIN RSA PRIVATE KEY が存在する場合は、ストリッピングを行う必要はありません。それが -- BEGIN PRIVATE KEY --- である場合、キーチェーンに入れる前にこれらの初期ヘッダーを削除する必要があります。
私の場合、公開鍵も必要でした。秘密鍵を正常に配置した後、キーチェーンからそれを取得する方法を理解できなかったので (何かを見逃した可能性があります)、実際に秘密鍵から ASN.1 公開鍵を作成し、それを keycahin にインポートしました。
秘密鍵 (ASN.1 ヘッダーの除去後) には、SEQUENCE タグの後に 3 つの INTEGER タグが続きます (この後にさらに INTEGERS がありますが、最初の 3 つだけが重要です)。
1 つ目は VERSION タグです。2 番目はモジュラスで、3 番目は公開指数です。
公開鍵を見ると (ASN.1 ヘッダーの除去後)、SEQUENCE の後に 2 つの整数が続きます。ご想像のとおり、これは秘密鍵のモジュラスと公開指数です。
したがって、必要なことは次のとおりです。
インポートした秘密鍵から公開鍵を作成するために必要なことはこれだけです。デバイスで生成しない RSA キーをインポートするための情報はあまりないようです。また、デバイスで生成されたキーにはこれらの ASN.1 ヘッダーが含まれていないと聞きましたが、試したことはありません。 . 私たちのキーは非常に大きく、デバイスでの生成に時間がかかりすぎます。私が見つけた唯一のオプションは、iOS 用に独自にコンパイルする必要がある OpenSSL を使用することでした。可能であれば、セキュリティ フレームワークを使用したいと思います。
私はまだiOS開発にかなり慣れていないので、私が見つけることができなかったこれらすべてを実行する単純な関数を誰かが知っていると確信しています。これは、キーを処理するためのより簡単な API が利用可能になるまでは問題なく機能するようです。
最後に 1 つ: プロジェクトに含まれている秘密鍵には BIT STRING のタグがありましたが、OpenSSL で生成された秘密鍵からインポートしたものには OCTET STRING というタグがありました。
答えはSecItemAdd
、正しいフラグのセットで呼び出すことでした。参照: http://hg.mozilla.org/services/fx-home/file/tip/Sources/NetworkAndStorage/CryptoUtils.m#l931
このAppleデベロッパフォーラムスレッドのコードが機能するかどうかはわかりませんが、あなたの質問に対する直接の答えのようです。
MYcrypto ライブラリからこのコード (BSD ライセンス) を掘り出しました。それはあなたが望むことをするようです。
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
}