この記事は公開されてから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'
なお、2016年2月現在の最新バージョンは1.3.1です。
(2) システムデータ
キーチェーンは、アプリごとのデータではなく、端末単位で管理されるものです。 従って、NSUserDefaultsのように、アプリを削除しても消えることはありません。 削除が必要な場合は、明示的に作業する必要があります。
SSKeychainでは、NSUserDefaults並みに、簡単にデータ保存ができますので、永久的なデータ保存が必要な場合は応用できるかも知れません。
(3) 2種類の使用方法
SSKeychainは、以下の4つのファイルで構成されており、使い方としてSSKeychainとSSKeychainQueryの2種類があります。
- SSKeychain.h
- SSKeychain.m
- SSKeychainQuery.h
- SSKeychainQuery.m
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]; // 再表示
}
動作確認中の画面です。
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/