[iOS] 簡単にシステムのキーチェーンを操作できるライブラリ、SSKeychainについて

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

1 はじめに

(1) SSKeychainとは

iOSでは、パスワードを安全に保存できる仕組みとしてKeychain Serviceを提供しています。

https://developer.apple.com/library/ios/documentation/Security/Reference/keychainservices/index.html

しかし、これを利用するには、結構たくさんのコーディングが必要になります。

これに対して、SSKeychainは、このような処理をラッピングして簡単に記述できるフレームワークとなっています。

SSKeychainは、MITライセンスで公開されており、CocoaPodで簡単にインストールが可能です。

pod 'SSKeychain', '~> 1.3'

CocoaPodsによる、外部ライブラリの利用と作成

なお、2016年2月現在の最新バージョンは1.3.1です。

(2) システムデータ

キーチェーンは、アプリごとのデータではなく、端末単位で管理されるものです。 従って、NSUserDefaultsのように、アプリを削除しても消えることはありません。 削除が必要な場合は、明示的に作業する必要があります。

SSKeychainでは、NSUserDefaults並みに、簡単にデータ保存ができますので、永久的なデータ保存が必要な場合は応用できるかも知れません。

(3) 2種類の使用方法

SSKeychainは、以下の4つのファイルで構成されており、使い方としてSSKeychainSSKeychainQueryの2種類があります。

  • SSKeychain.h
  • SSKeychain.m
  • SSKeychainQuery.h
  • SSKeychainQuery.m

001

2 SSKeychain

1つ目の使い方としてのSSKeychainでは、クラスメソッドを使用してキーチェンを操作します。

(1) クラスメソッド

主要なクラスメソッドは、次の通りです。

// 全てのキーチェンの列挙
+ (NSArray *)allAccounts;

// サービス名を指定しての列挙
+ (NSArray *)accountsForService:(NSString *)serviceName;

// サービス名とアカウント名を指定してパスワードを取得する
+ (NSString *)passwordForService:(NSString *)serviceName account:(NSString *)account;

// サービス名とアカウント名を指定してキーチェーン自体を削除する(全キーチェーン数自体が減る)
+ (BOOL)deletePasswordForService:(NSString *)serviceName account:(NSString *)account;

// サービス名とアカウント名を指定してパスワードを変更する
+ (BOOL)setPassword:(NSString *)password forService:(NSString *)serviceName account:(NSString *)account;

(2) 動作確認

簡単なサンプルを作成して、動作を確認してみました。

先ずは、端末に現在格納されているキーチェーンを列挙してUITextViewに表示するメソッドです。 全キーチェーンを列挙した後、それぞれのサービス名とアカウント名を取得して、パスワードを読みだしています。

// 列挙
- (void)refreshTextView
{
    NSString *text = @"";
    int n=1;

    NSArray *data = [SSKeychain allAccounts];
    for( NSDictionary *d in data){
        NSString *service = d[kSSKeychainWhereKey];
        NSString *account = d[kSSKeychainAccountKey];

        NSString *password = [SSKeychain passwordForService:service account:account];
        text = S=%@ A=%@ P=%@\n",n++,service,account,password]];
    }
    self.textView.text = text;
}

続いて、Saveボタンを押した時のコードです。 UITextFieldに入力された、サービス名、アカウント名、パスワードを>setPassword:で保存しています。

すでに、同じサービス名とアカウントのキーがある場合は、上書きになります。

// 保存
- (IBAction)tapSaveButton:(id)sender {
    NSString *service = self.serviceTextField.text;
    NSString *account = self.accountTextField.text;
    NSString *password = self.passwordTextField.text;

    if ( service.length != 0
        && account.length != 0
        && password.length != 0){

        NSError *error;
        [SSKeychain setPassword:password
                     forService:service
                        account:account
                          error:&error];
    }
    [self refreshTextView]; // 再表示
}

最後にDeleteボタンを押した時のコードです。 設定された、サービス名とアカウント名を使用して、deletePasswordForService:を呼び出しています。 サービス名及び、アカウント名が一致したデータは削除されます。

パスワードは、分からなくても削除可能です。

// 削除
- (IBAction)tapDeleteButton:(id)sender {
    NSString *service = self.serviceTextField.text;
    NSString *account = self.accountTextField.text;

    if ( service.length != 0
        && account.length != 0){

        NSError *error;
        [SSKeychain deletePasswordForService:service
                                     account:account error:&error];
    }
    [self refreshTextView]; // 再表示
}

動作確認中の画面です。

002

3 SSKeychainQuery

2つ目の使用方法であるSSKeychainQueryでは、インスタンスを生成し、プロパティ及び、インスタンスメソッドからキーチェーンを操作します。

(1) プロパティとインスタンスメソッド

主要なプロパティは、次の通りです。


// サービス名
@property (nonatomic, copy) NSString *service;

// アカウント名
@property (nonatomic, copy) NSString *account;

// ラベル
@property (nonatomic, copy) NSString *label;

// パスワード
@property (nonatomic, copy) NSData *passwordData;
@property (nonatomic, copy) id<NSCoding> passwordObject;
@property (nonatomic, copy) NSString *password;

続いてインスタンスメソッドです。全体的に、DBへのアクセス風になってます。


// サービス名・アカウント名・パスワードを指定して保存する
// サービス名・アカウント名の一致するキーチェンが存在する場合は、パスワードの変更になる
- (BOOL)save:(NSError **)error;

// サービス名とアカウント名を指定してキーチェーン自体を削除する(全キーチェン数が減る)
- (BOOL)deleteItem:(NSError **)error;

// サービス名とアカウント名を指定してパスワードを取得する
- (NSArray *)fetchAll:(NSError **)error;

// 全てのキーチェンの列挙
- (BOOL)fetch:(NSError **)error;

(2) 動作確認

先のSSKeychainの動作確認と同様の機能を実装してみます。

先ずは、全キーチェーンを列挙してUITextViewに表示するメソッドです。

fetchAll:で全てのキーチェーンを列挙した後、サービス名、アカウント名及び、パスワードの取得要領は、SSKeychainと同じです。


// 列挙
- (void)refreshTextView
{
	NSString *text = @"";

   NSError *error = nil;
   SSKeychainQuery *query = [[SSKeychainQuery alloc] init];
   NSArray *data = [query fetchAll:&error];

   int n=1;
   for( NSDictionary *d in data){
       NSString *service = d[kSSKeychainWhereKey];
       NSString *account = d[kSSKeychainAccountKey];

       NSString *password = [SSKeychain passwordForService:service account:account];
       text = S=%@ A=%@ P=%@\n",n++,service,account,password]];
   }

}

続いて、Saveボタンを押した時のコードです。 UITextFieldに入力された、サービス名、アカウント及び、パスワードをプロパティにセットしてsave:で保存しています。

既に、同じサービス名とアカウント名のキーチェーンがある場合は、上書きになります。


// 保存
- (IBAction)tapSaveButton:(id)sender {
- NSError *error = nil;

    SSKeychainQuery *query = [[SSKeychainQuery alloc] init];
    query.service = self.serviceTextField.text;
    query.account = self.accountTextField.text;
    query.password = self.passwordTextField.text;

    [query save:&error];
    
    [self refreshTextView]; // 再表示
}

最後にDeleteボタンを押した時のコードです。

サービス名とアカウント名をプロパティにセットして、deleteItem:を呼び出しています。 サービス名とアカウント名が一致したデータは削除されます。

パスワードは、分からなくても削除可能です。


// 削除
- (IBAction)tapDeleteButton:(id)sender {
    NSError *error = nil;
    SSKeychainQuery *query = [[SSKeychainQuery alloc] init];
    query.service = self.serviceTextField.text;
    query.account = self.accountTextField.text;
    [query deleteItem:&error];

    [self refreshTextView]; // 再表示
}

実行画面については、先と同じなので、割愛します。

(3) NSString以外

プロパティを見てお気付きの方もおられると思いますが、SSKeychainQueryでは、パスワードのプロパティにNSString以外のものがあります。

passwordData及びpasswordObjectが、それです。


// パスワード
@property (nonatomic, copy) NSData *passwordData;
@property (nonatomic, copy) id<NSCoding> passwordObject;
@property (nonatomic, copy) NSString *password;

こちらを利用すると、NSString以外のものが保存可能です。

下記のコードは、GitHubにあるテストコードの一部です。

NSDictionaryのデータを保存しているのが分かります。

https://github.com/soffes/sskeychain/blob/master/Tests/SSKeychainTests.m


- (void)testPasswordObject {
	SSKeychainQuery *query = [[SSKeychainQuery alloc] init];
	query.service = kSSKeychainServiceName;
	query.account = kSSKeychainAccountName;

	NSDictionary *dictionary = @{@"number": @42, @"string": @"Hello World"};
	query.passwordObject = dictionary;

	NSError *error;
	XCTAssertTrue([query save:&error], @"Unable to save item: %@", error);

	query = [[SSKeychainQuery alloc] init];
	query.service = kSSKeychainServiceName;
	query.account = kSSKeychainAccountName;
	query.passwordObject = nil;
	XCTAssertTrue([query fetch:&error], @"Unable to fetch keychain item: %@", error);
	XCTAssertEqualObjects(query.passwordObject, dictionary, @"Passwords were not equal");
}

4 最後に

SSKeychainQueryのおかげで、システムのキーチェーンが、非常に簡単に扱えます。 先人の功績に感謝です。

5 参考資料


GitHub sskeychain
http://cocoadocs.org/docsets/SSKeychain/1.3.1/