Unityで作成したIOS向けアプリで、平文で端末に保存しちゃよくなさそうなものがあったので暗号化して保存するために、ネイティブのKeyChainを使用したのでそのメモ。
細部まで仕様を把握できていないので何かあったらつっこんでください。
KeyChainって?
端末に自動で暗号化して値を保存してくれるやつ。
UserDefaultsに入れられないような情報(token,pwd)などを保存するときに便利。
Unityでいうと、PlayerPrefsを使うとIOSだとUserDefaultsが使用されるはずなので、平文保存になってしまう。
KeyChainの細かい仕組みをわかりやすくまとめてくださっている記事があるので、詳しく知りたい方はこちらの記事を参考にしていただければなと思います。
早速実装しよう
値の保存
int _Add(const char *dataType, const char *value)
{
NSMutableDictionary* attributes = nil;
NSMutableDictionary* query = [NSMutableDictionary dictionary];
NSData* sata = [[NSString stringWithCString:value encoding:NSUTF8StringEncoding] dataUsingEncoding:NSUTF8StringEncoding];
[query setObject:(id)kSecClassGenericPassword forKey:(id)kSecClass];
[query setObject:(id)[NSString stringWithCString:dataType encoding:NSUTF8StringEncoding] forKey:(id)kSecAttrAccount];
[query setObject:SERVICE_NAME forKey:(id)kSecAttrService];
OSStatus err = SecItemCopyMatching((CFDictionaryRef)query, NULL);
if (err == noErr) {
// update item
attributes = [NSMutableDictionary dictionary];
[attributes setObject:sata forKey:(id)kSecValueData];
[attributes setObject:[NSDate date] forKey:(id)kSecAttrModificationDate];
err = SecItemUpdate((CFDictionaryRef)query, (CFDictionaryRef)attributes);
return (int)err;
} else if (err == errSecItemNotFound) {
attributes = [NSMutableDictionary dictionary];
[attributes setObject:(id)kSecClassGenericPassword forKey:(id)kSecClass];
[attributes setObject:(id)[NSString stringWithCString:dataType encoding:NSUTF8StringEncoding] forKey:(id)kSecAttrAccount];
[attributes setObject:sata forKey:(id)kSecValueData];
[attributes setObject:SERVICE_NAME forKey:(id)kSecAttrService];
[attributes setObject:[NSDate date] forKey:(id)kSecAttrCreationDate];
[attributes setObject:[NSDate date] forKey:(id)kSecAttrModificationDate];
err = SecItemAdd((CFDictionaryRef)attributes, NULL);
return (int)err;
} else {
return (int)err;
}
}
- 引数は保存の名前と値。ここでは
dataType,value
で定義。 -
dataType
で端末内を検索する。 - すでに値がある場合は値の更新をかける
- 値がまだない場合は、定義して登録する。
[query setObject:SERVICE_NAME forKey:(id)kSecAttrService];
でSERVICE_NAMEをしようしてるので#define SERVICE_NAME
で上の方に定義して貰えば動くと思います。(これが必要なのかどうか実際わかってないので詳しい人いたら教えてください。)
あと、エラー処理とかもしてなくて成功で0が帰ってくるはずなのでそれ以外が帰ってきた時の処理はC#でよしなに記述してもらえればなーと思います。
値の取り出し
char* _Get(const char *dataType)
{
NSMutableDictionary* query = [NSMutableDictionary dictionary];
[query setObject:(id)kSecClassGenericPassword forKey:(id)kSecClass];
[query setObject:(id)[NSString stringWithCString:dataType encoding:NSUTF8StringEncoding] forKey:(id)kSecAttrAccount];
[query setObject:SERVICE_NAME forKey:(id)kSecAttrService];
[query setObject:(id)kCFBooleanTrue forKey:(id)kSecReturnData];
CFDataRef cfresult = NULL;
OSStatus err = SecItemCopyMatching((CFDictionaryRef)query, (CFTypeRef*)&cfresult);
if (err == noErr) {
NSData* passwordData = (__bridge_transfer NSData *)cfresult;
const char* value = [[[NSString alloc] initWithData:passwordData encoding:NSUTF8StringEncoding] UTF8String];
char *str = strdup(value);
return str;
NSLog(@"create uuid");
} else {
return NULL;
}
}
やってることは_Add
の前半と同じような感じで、SecItemCopyMatching
の第二引数が(CFTypeRef*)&cfresult
になることで、検索した値を取り出せる。
こちらもエラーを握りつぶして全部nullで返しているので、必要によってハンドリングして使ってもらえればなーと思います。
値の削除
int _Delete(const char *dataType)
{
NSMutableDictionary* query = [NSMutableDictionary dictionary];
[query setObject:(id)kSecClassGenericPassword forKey:(id)kSecClass];
[query setObject:(id)[NSString stringWithCString:dataType encoding:NSUTF8StringEncoding] forKey:(id)kSecAttrAccount];
OSStatus err = SecItemDelete((CFDictionaryRef)query);
if (err == noErr) {
return 0;
} else {
return (int)err
}
}
queryに値入れてSecItemDelete
に渡してあげるだけで削除できます。
調査不足でエラー処理がかなり適当ですが成功で0返しときました(追加のとこと同じにしといた)。
ここら辺は調査してまた変えてみるかもしれません。
終わりに
動くものが実装できてよかったなと思いつつも、まだまだ理解が足りずエラーハンドリング等もできていないに等しい感じなのでもっと勉強していかなければと思います。
コード自体はここに置いておいたので、全体が見たい方や使う方はあくまで自己責任でお願いします。
改善点等、知見のある方いたら勉強させてもらえるとありがたいです。