以前の記事で書いたように(→「設定の保存にまつわる問題」)、ユーザのパスワード、それもアプリとは異なる別のサービス用のアカウントのものを、アプリの設定(User Defaults)にそのまま保存するのは望ましくない。たとえ、アプリの設定(~/Library/Preferences に plist ファイルとして保存される)がバイナリ形式で、かつファイルモードが 0600 であったとしても。
パスワードを保存するなら、やはり「キーチェーン」を使うべきだ。
(「Keychain Services Programming Guide: Introduction」より)
Keychain Services provides secure storage of passwords, keys, certificates, and notes for one or more users. A user can unlock a keychain with a single password, and any Keychain Services–aware application can then use that keychain to store and retrieve passwords.
今回は、このキーチェーンサービスについて調べてみた。
アプリへの組み込み
アプリからキーチェーンサービスにアクセスするために必要な関数は以下の 2 つ。
- SecKeychainAddGenericPassword (キーチェーンにパスワードを追加)
- SecKeychainFindGenericPassword (キーチェーンからパスワードを取得)
これをアプリ内で使う場合の注意点は、(a) security/security.h を import すること、(b) Security.framework をリンクすること、の 2 点だ。(a) はソースに書くだけ。一方、(b) のためには Xcode のプロジェクトウィンドウで以下のような操作を行う。
- 「グループとファイル」の「Frameworks」グループ上でポップアップメニューを出し「追加」>「既存のフレームワーク...」を選ぶ。
- 現れたシートから Security.framework を選び「追加」ボタンを押す。
上記を実行すれば「Frameworks」グループ内に Security.framework が追加されるが、実際にはどこでポップアップメニューを開いても大差はない。それよりも重要なのは「ターゲット」のビルドフェーズ「バイナリをライブラリにリンク」に Security.framework が追加されていることだ。
サンプルアプリ
この 2 つの関数を使って、簡単なサンプルアプリを作ってみた。右のスクリーンショットがそのアプリウィンドウだ。Save Password でアカウントとパスワードを指定して「Save」ボタンを押すと、パスワードがキーチェーンに保存される。次に、Load Password でアカウントを入力し「Load」ボタンを押すと、下のパスワードフィールドに保存したパスワードが表示される。もちろん、アカウントが間違っていればパスワードの取得に失敗する。
デフォルトのキーチェーンがロックされていれば、保存時にロックを解除するかというダイアログが出てくるし、同様に取得時にはキーチェーンへのアクセスを許可するかというダイアログも出てくる。また、実際にパスワードが保存できていることは「キーチェーンアクセス.app」を起動すれば確認できる。
以下に、アプリのソースの一部(AppController.{h,m})を示す。
#import <Cocoa/Cocoa.h>
@interface AppController : NSObject {
NSTextField *accountFieldForSave;
NSTextField *passwordFieldForSave;
NSTextField *accountFieldForLoad;
NSTextField *passwordFieldForLoad;
}
@property (assign) IBOutlet NSTextField *accountFieldForSave;
@property (assign) IBOutlet NSTextField *passwordFieldForSave;
@property (assign) IBOutlet NSTextField *accountFieldForLoad;
@property (assign) IBOutlet NSTextField *passwordFieldForLoad;
- (IBAction)load:(id)sender;
- (IBAction)save:(id)sender;
@end
#import <Security/Security.h>
#import "AppController.h"
NSString * const KCTServiceName = @"KeyChainTest App";
@implementation AppController
@synthesize accountFieldForSave, passwordFieldForSave;
@synthesize accountFieldForLoad, passwordFieldForLoad;
- (IBAction)load:(id)sender {
NSString *account = [accountFieldForLoad stringValue];
const char *serviceName = [KCTServiceName UTF8String];
const char *accountName = [account UTF8String];
void *passwordData = nil;
SecKeychainItemRef itemRef = nil;
UInt32 passwordLength;
OSStatus status;
status = SecKeychainFindGenericPassword(NULL,
strlen(serviceName),
serviceName,
strlen(accountName),
accountName,
&passwordLength,
&passwordData,
&itemRef);
NSLog(@"Load password: status = %d", status);
if (status == noErr) {
NSString *password =
[[NSString alloc] initWithBytes:passwordData
length:passwordLength
encoding:NSUTF8StringEncoding];
[passwordFieldForLoad setStringValue:password];
status = SecKeychainItemFreeContent(NULL,
passwordData);
[password release];
} else {
[passwordFieldForLoad setStringValue:@""];
}
}
- (IBAction)save:(id)sender {
NSString *account = [accountFieldForSave stringValue];
NSString *password = [passwordFieldForSave stringValue];
// !!!:
const char *serviceName = [KCTServiceName UTF8String];
const char *accountName = [account UTF8String];
const char *passwordData = [password UTF8String];
OSStatus status;
status = SecKeychainAddGenericPassword(NULL,
strlen(serviceName),
serviceName,
strlen(accountName),
accountName,
strlen(passwordData),
passwordData,
NULL);
NSLog(@"Save password: status = %d", status);
}
@end
Cocoa のオブジェクトにくるまれていないサービスのため、使うのが少し面倒(NSString を直接わたせなかったり)だが、実質的に 2 つの関数を呼び出すだけでパスワードのような機密性の高い情報を安全に保管できる。やってみると思ったよりも簡単だった。
実は、この 2 つの関数だけではパスワードを変更することができない(同じアカウントで Add を呼ぶとエラーになる)。実際のアプリ(MacBloggerGlass)に組み込むときは、もちろん変更もできるようにしなければならない。
関連リンク
- Keychain Services Programming Guide (Mac OS X Reference Library)

0 件のコメント:
コメントを投稿