Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- 得た知識と出来るようになったこと(再掲)
- iOSにおけるKeyChain操作への理解
- RSAキーの生成(Private Key/ Public Key)
- キー・リファレンスの取得
- キー・データの取得
- キー・データからのmodulus、exponentの取り出し(DERフォーマットの理解→バイナリ・データの操作)
- Public Keyによるデータの暗号化とPrivate Keyによる復号
- 1. iOSにおけるKeyChain操作への理解
- この理解を得るために是非とも参照頂きたいのは、次のブログ記事です。
- [iOS] Keychain Services とは
- KeyChainに関してゼロ・スタートで読むには少し骨が折れるかもしれませんが、私は自分が必要なところ、自分が分かるところだけ読むという感じで都合3周して、毎回非常に有益な知見をもたらしてもらいました。このことを説明しているサイトを他に見かけませんでした。分かる範囲で読んで実際のコードで試してみて、他の資料をみて、またこの記事に戻ってくるというそのサイクルで良いと思います。私は周回を重ねるごとに、この記事のありがたみが増してきました。
- 次に、実際に動くコードに一番近いのが、結局お上が書き下した以下です。
- Dummy
- 首をかしげるようなコードもあります。あと基本的に古いので、ARCをお使いのよい子?の皆さんは、コピペするとXcodeにいっぱい怒られること必至ですが、めげず頑張りましょう。
- また、Stack Overflowとかで「回答」という形で掲載されているコードも鵜呑みにしないでください。有効な回答であっても、間違いとかゴミとかいっぱい入っていました。
- ということで、私のゴミもココ(あとで置きます)においておきますので、鵜呑みにせず、可能な範囲でご活用頂ければと。
- 2. RSAキーの生成(Private Key/ Public Key)
- 先に紹介したブログの記事をひとまず読まれた方なら、
- どうやらiOSの中には鍵とかパスワードを保管してくれる機構があるらしい。
- データベースに対するCRUD(SQLならinsert/update/select/delete)に相当する操作が用意されていて、保管されている鍵を取り出せそう (それがSecItemCopyMatching)
- くらいは読み取れたと思います。
- じゃぁ、肝心の鍵はどうやって作るのよ?というのが次の説明です。RSAによる鍵を生成するにはというと
- -(void)generateKeyPair {
- SecKeyRef publicKey;
- SecKeyRef privateKey;
- NSMutableDictionary *privateKeyAttr = [[NSMutableDictionary alloc] init];
- NSMutableDictionary *publicKeyAttr = [[NSMutableDictionary alloc] init];
- NSMutableDictionary *keyPairAttr = [[NSMutableDictionary alloc] init];
- [privateKeyAttr setObject:[NSNumber numberWithBool:YES] forKey:(__bridge id)kSecAttrIsPermanent];
- [privateKeyAttr setObject:privateTag forKey:(__bridge id)kSecAttrApplicationTag];
- [publicKeyAttr setObject:[NSNumber numberWithBool:YES] forKey:(__bridge id)kSecAttrIsPermanent];
- [publicKeyAttr setObject:publicTag forKey:(__bridge id)kSecAttrApplicationTag];
- [keyPairAttr setObject:(__bridge id)kSecAttrKeyTypeRSA forKey:(__bridge id)kSecAttrKeyType];
- [keyPairAttr setObject:[NSNumber numberWithInt:keySize] forKey:(__bridge id)kSecAttrKeySizeInBits];
- [keyPairAttr setObject:privateKeyAttr forKey:(__bridge id)kSecPrivateKeyAttrs];
- [keyPairAttr setObject:publicKeyAttr forKey:(__bridge id)kSecPublicKeyAttrs];
- OSStatus err = SecKeyGeneratePair((__bridge CFDictionaryRef)keyPairAttr, &publicKey, &privateKey);
- if(publicKey) CFRelease(publicKey);
- if(privateKey) CFRelease(privateKey);
- if(err == noErr && publicKey != NULL && privateKey != NULL) {
- DLog(@"generateKeyPair: Success");
- }
- }
- こんな感じです。細かい説明が必要な人は、それぞれのモチベーションに合わせて調べて頂くとして、Private Key, Public Key, KeyPairの大きく3つのAttributeを設定しています。最終的に欲しいのはKeyPairのAttributeで、Private KeyとPublic KeyのAttributeは、KeyPairのAttributeとして格納しています。各KeyのAttributeとしては、Tagを設定しているのが見て取れると思いますが、後にKeyを取り出すために使う識別子といった感じです。
- KeyPairとしてのAttributeには、RSA Keyであること、それからKeyのサイズが指定されていて、実際のKeyの生成はSecKeyGeneratePair関数で行われています。
- なお、このメソッドの外側で以下に相当する操作を事前に行っています。ご覧の通り少しものの定義通りと申しますか、律儀にやっていますが、たぶん普通に文字列からNSData作ってしまって問題ないと思います。
- static const UInt8 publicKeyIdentifier[] = "hiraoka.kzt.publickey";
- static const UInt8 privateKeyIdentifier[] = "hiraoka.kzt.privatekey";
- NSData *privateTag = [[NSData alloc] initWithBytes:privateKeyIdentifier length:sizeof(privateKeyIdentifier)];
- NSData *publicTag = [[NSData alloc] initWithBytes:publicKeyIdentifier length:sizeof(publicKeyIdentifier)];
- int keySize = 2048;
- ところで、生成したKeyは、SecKeyRef変数に格納されているわけで、名前からして鍵そのもののバイナリデータではないことが推測できます。鍵に対するリファレンスになってはいますが、実際に暗号化したり復号する場合に渡す鍵もこのリファレンスの変数を渡すことで処理がなされますので、後述のバイナリデータとしてキー・データを操作してみたいといった欲求がない限りは、この変数で鍵とつきあっていくことになります。
- このセクションの最後に... お上の書いたコードとこのコードに見た目上大きく違う点があります。__bridge xxxというやつです。それがARCとの関係です。ここはRSAのお話をする場なので、別のところでお調べ頂ければと思います。Objective-Cを書いていると、モダンなんだかクラシックなんだかよく分からない、心情的にはノスタルジックな心持ちにされることが多々ありますが、過去と現在を行き来するには、やはり突きつけられる現実というのがあり、それがこれって感じですかね。
- 3. キー・リファレンスの取得
- 作った直後は、SecKeyRef変数に格納されているので困らないですが、もしかして毎回作るんでしょうか?KeyChainって鍵って保管できるんじゃなかったでしたっけ?
- そうでした。KeyChainさんのことほったらかしでした。
- でも、やってみてびっくりしたのですが、前述の方法で作った鍵は我々がKeyChainに格納しなくても勝手に入ってるんです。どうやらそういうものらしいです。となると、アプリケーションの中では、キー・リファレンスの取得を試みて空振りしたら鍵を作るという流れになるってことでしょうか。私はそうしています。
- ということで、キー・リファレンスの取得に話が移ります。
- /*
- * KeyChainからPublic Keyを取得して返却する
- *
- */
- -(SecKeyRef)getPublicKeyRefFromKeyChain {
- OSStatus resultCode = noErr;
- SecKeyRef publicKeyReference = NULL;
- NSMutableDictionary *queryPublicKey = [[NSMutableDictionary alloc] init];
- // Set the public key query dictionary.
- [queryPublicKey setObject:(__bridge id)kSecClassKey forKey:(__bridge id)kSecClass];
- [queryPublicKey setObject:publicTag forKey:(__bridge id)kSecAttrApplicationTag];
- [queryPublicKey setObject:(__bridge id)kSecAttrKeyTypeRSA forKey:(__bridge id)kSecAttrKeyType];
- [queryPublicKey setObject:[NSNumber numberWithBool:YES] forKey:(__bridge id)kSecReturnRef];
- // Get the key.
- resultCode = SecItemCopyMatching((__bridge CFDictionaryRef)queryPublicKey, (CFTypeRef *)&publicKeyReference);
- if (resultCode != noErr) {
- publicKeyReference = NULL;
- }
- return publicKeyReference;
- }
- /*
- * KeyChainからPrivate Keyを取得して返却する
- *
- */
- - (SecKeyRef)getPrivateKeyRefFromKeyChain {
- OSStatus resultCode = noErr;
- SecKeyRef privateKeyReference = NULL;
- NSMutableDictionary *queryPrivateKey = [[NSMutableDictionary alloc] init];
- // Set the private key query dictionary.
- [queryPrivateKey setObject:(__bridge id)kSecClassKey forKey:(__bridge id)kSecClass];
- [queryPrivateKey setObject:privateTag forKey:(__bridge id)kSecAttrApplicationTag];
- [queryPrivateKey setObject:(__bridge id)kSecAttrKeyTypeRSA forKey:(__bridge id)kSecAttrKeyType];
- [queryPrivateKey setObject:[NSNumber numberWithBool:YES] forKey:(__bridge id)kSecReturnRef];
- // Get the key.
- resultCode = SecItemCopyMatching((__bridge CFDictionaryRef)queryPrivateKey, (CFTypeRef *)&privateKeyReference);
- if(resultCode != noErr) {
- privateKeyReference = NULL;
- }
- return privateKeyReference;
- }
- よく見なくても馬鹿っぽいですが、勉強のためです。馬鹿は体で覚えるということではなく、大事なことは二度言うということで、内容的には同じ二つのメソッドをさらしています。
- Public KeyとPrivate KeyのそれぞれをKeyChainから取り出しています。といっても取り出すのは前述の通りキー・リファレンスの形です。やっていることはキー生成と同じで、今回はクエリするということで、Attributeとは名付けていませんが同じことです。鍵をくれ、Public(Private)のタグがついた、RSAだったはず、リファレンスの形で、というDictionary値を用意しておいて、それをSecItemCopyMatchingを使って取得します。
- まぁ普通はこのメソッドはTagを引数にとって、どっちでも動くようにしますよね。
- 4. キー・データの取得
- このキー・データというものが何を指しているのかに気がつくまでに、私は少し時間かかりました。解説が理解できなかったという意味ではなくて、最初に出会っていたにも関わらず、あるとき自分が求めていたものがそれだということに、後になって気がついたということです。実際に気がついたのは、opensslコマンドで生成したPublic KeyをiOSのKeyChainに取り込むというサンプルコードを目にをした時で、[iOS] Keychain Services とはを読む2周目にしてようやくSecItemCopyMatching関数がとる引数のバリエーションを認識しました。
- /*
- * KetChainからPublic KeyのData部分を取得
- * DERフォーマットになっているはず
- */
- - (NSData *)getPublicKeyBits {
- OSStatus resultCode = noErr;
- NSData *publicKeyBits = nil;
- CFTypeRef pk;
- NSMutableDictionary *queryPublicKey = [[NSMutableDictionary alloc] init];
- // Set the public key query dictionary.
- [queryPublicKey setObject:(__bridge_transfer id)kSecClassKey forKey:(__bridge_transfer id)kSecClass];
- [queryPublicKey setObject:publicTag forKey:(__bridge_transfer id)kSecAttrApplicationTag];
- [queryPublicKey setObject:(__bridge_transfer id)kSecAttrKeyTypeRSA forKey:(__bridge_transfer id)kSecAttrKeyType];
- [queryPublicKey setObject:[NSNumber numberWithBool:YES] forKey:(__bridge_transfer id)kSecReturnData];
- // Get the key bits.
- resultCode = SecItemCopyMatching((__bridge_retained CFDictionaryRef)queryPublicKey, &pk);
- if (resultCode != noErr) {
- publicKeyBits = nil;
- }
- publicKeyBits = (__bridge_transfer NSData*)pk;
- return publicKeyBits;
- }
- 戻ってくる値は、kSecReturnDataとしているところがポイントです。後は__bridgeさんどう折り合いをつけるか位で、喉元過ぎればたいしたことないですね。
- 5. キー・データからのmodulus、exponentの取り出し(DERフォーマットの理解->バイナリ・データの操作)
- せっかくバイナリで鍵本体を抽出できるようになったわけですから、それを実際に読み下してみましょうということで、次です。鍵はDER形式になっていて、その辺は以下のブログ記事が詳しく、かつ分かりやすく説明してくれています。
- RSA 秘密鍵/公開鍵ファイルのフォーマット
- derEncodingGetSizeFromが分かりにくいかもしれないので、コメントをつけています。前述のサイトの解説と併せて読めば何をしているか分かると思います。
- // KeyChainに格納されているPublic KeyからExponentを切り出す
- - (NSData *)getPublicKeyExponent {
- NSData* pk = [self getPublicKeyBits];
- if (pk == NULL) return NULL;
- int iterator = 0;
- iterator++; // TYPE - bit stream - mod + exp
- [self derEncodingGetSizeFrom:pk at:&iterator];
- iterator++; // TYPE - bit stream mod
- int mod_size = [self derEncodingGetSizeFrom:pk at:&iterator];
- iterator += mod_size;
- iterator++; // TYPE - bit stream exp
- int exp_size = [self derEncodingGetSizeFrom:pk at:&iterator];
- return [pk subdataWithRange:NSMakeRange(iterator, exp_size)];
- }
- // KeyChainに格納されているPublic KeyからModulusを切り出す
- - (NSData *)getPublicKeyModulus {
- NSData* pk = [self getPublicKeyBits];
- if (pk == NULL) return NULL;
- int iterator = 0;
- iterator++; // TYPE - bit stream - mod + exp
- [self derEncodingGetSizeFrom:pk at:&iterator]; // Total size
- iterator++; // TYPE - bit stream mod
- int mod_size = [self derEncodingGetSizeFrom:pk at:&iterator];
- return [pk subdataWithRange:NSMakeRange(iterator, mod_size)];
- }
- // RSAKeyのフォーマットから、データのサイズを取り出すユーティリティ・メソッド
- // 終わったらiteratorはデータが入っていたバイト部分を前に進めてデータ部分の先頭へ持って行く
- - (int)derEncodingGetSizeFrom:(NSData *)buf at:(int*)iterator {
- const uint8_t *data = [buf bytes];
- int itr = *iterator;
- int num_bytes = 1;
- int ret = 0;
- //最初の1バイトの最上位ビットが立っていたら、データの長さフィールドは1バイトに収まっていることを示していて、
- //立っていなかったら、残りの7ビットの中でデータの長さを表現している。
- //最初のビットも立っていないから、8ビット全部=つまりその1バイト全体で長さを表してる
- if (data[itr] > 0x80) {
- num_bytes = data[itr] - 0x80;
- itr++;
- }
- //最初のBitが立っていなかったら、num_bytes:1なので1回だけループが回って、itrも加算されていないので
- //そのBitがそのまま整数として返却される。
- //最初のBitが立っていたら、続いて読むべきByte数がnum_bytesに入っているので、そこまで読み進めて整数化
- for (int i = 0 ; i < num_bytes; i++) ret = (ret * 0x100) + data[itr + i];
- //読んだ分だけ(データサイズが入っていたバイト分だけ)前に進める
- *iterator = itr + num_bytes;
- return ret;
- }
- 6. Public Keyによるデータの暗号化とPrivate Keyによる復号
- これは比較的簡単です。
- /* 公開鍵(Public Key)で暗号化
- * plainData -> plainBuffer -> (暗号化)-> cipherBuffer
- */
- - (NSData *)encryptWithPublicKey:(NSData *)plainData {
- OSStatus status = noErr;
- size_t plainBufferSize = [plainData length];
- uint8_t *plainBuffer = (uint8_t *)[plainData bytes];
- size_t cipherBufferSize = SecKeyGetBlockSize(self.publicKey);
- uint8_t *cipherBuffer = malloc(cipherBufferSize);
- //kSecPaddingPKCS1,kSecPaddingOAEP
- status = SecKeyEncrypt(self.publicKey,
- kSecPaddingPKCS1,
- plainBuffer,
- plainBufferSize,
- cipherBuffer,
- &cipherBufferSize
- );
- NSData *result = [NSData dataWithBytes:cipherBuffer length:cipherBufferSize];
- return result;
- }
- /*
- * 秘密鍵(Private Key)で復号
- * encrypedData -> cipherBuffer -> (復号)-> plainBuffer
- */
- - (NSData *)decryptWithPrivateKey:(NSData *)encrypedData {
- OSStatus status = noErr;
- size_t cipherBufferSize = [encrypedData length];
- uint8_t *cipherBuffer = (uint8_t *)[encrypedData bytes];
- size_t plainBufferSize = SecKeyGetBlockSize(self.privateKey);
- uint8_t *plainBuffer = malloc(plainBufferSize);
- //kSecPaddingPKCS1, kSecPaddingOAEP
- status = SecKeyDecrypt(self.privateKey,
- kSecPaddingPKCS1,
- cipherBuffer,
- cipherBufferSize,
- plainBuffer,
- &plainBufferSize
- );
- NSData *result = [NSData dataWithBytes:plainBuffer length:plainBufferSize];
- return result;
- }
- SecKeyEncryptとSecKeyDecryptが暗号化/復号を行っている関数です。「Private Keyによる暗号化とPublic Keyによる復号は、RSAだからできることであって、公開鍵認証方式をとるキーに関する一般的な話ではない」の部分でも触れますが、SecKeyEncryptにはPrivate Keyを、SecKeyDecryptにPublic Keyを与えてもエラーとなります。
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement