iOS မှာ data တွေကို သိမ်းတော့မယ်ဆိုရင် ကျွန်တော်တို့တွေ အနေနဲ့ ပုံမှန်အားဖြင့်
iCloud Stroage
စတာတွေ မှာ ကို သိမ်းပါတယ်။ ပုံမှန်အားဖြင့် Setting တွေကို NSUserDefault အနေနဲ့ သိမ်းပါတယ်။
TouchID ကို အသုံးပြုတဲ့ အခါမှာတော့ auto login ဝင်ဖို့အတွက် အသုံးပြုသူရဲ့ သက်ဆိုင်ရာ information အချို့ကို သိမ်းဖို့လိုအပ်လာပါပြီ။ NSUserDefault က စိတ်ချရတဲ့ နေရာ မဟုတ်ပါဘူး။ plist အနေနဲ့ သိမ်းဆည်းထားတာကြောင့် လွယ်လင့် တကူ ရယူပြင်ဆင်နိုင်ပါတယ်။ အြခား storage နည်းလမ်း တွေလည်း အခက်အခဲလေးတွေ ရှိပြီးတော့ လွယ်လင့် တကူ သိမ်းဆည်းမရသာတွေ ရှိသလို security ကြောင့် မသိမ်းသင့်တာ တွေ ရှိပါတယ်။ ဥပမာ ။။ password ကို document storage မှာ သွားသိမ်းမယ်ဆိုရင် လွယ်လင့်တကူ ဖတ်လို့ရသွားနိုင်ပါတယ်။ Encrypt လုပ်ပြီး သိမ်းမယ်ဆိုရင် အဆင်ပြေပေမယ့် သိမ်းထားတဲ့ file ကို ပြင်ဆင်လို့ရသွားနိုင်ပါတယ်။ ဒါကြောင့် အကောင်းဆုံးကတော့ KeyChain မှာ သိမ်းဆည်းဖို့ပါပဲ။
KeyChain ဆိုတာကတော့ OS မှာ အထူးပြုလုပ်ထားတဲ့ database တစ်မျိုးပါပဲ။ data တွေ ကို လုံခြုံစိတ်ချစွာ သိမ်းထားနိုင်ပြီးတော့ သိမ်းထားတဲ့ attributes ပေါ်မှာ မူတည်ပြီးတော့ ပြန်ရှာနိုင်ပါတယ်။ user secrets တွေ အတွက် သိမ်းဖို့ သင့်လျော်ပါတယ်။
ဘာလို့ keychain ကို အသုံးပြုရတာလဲ ဆိုရင်တော့
Keychain မှာ data တွေကို user passcode နဲ့ ကာကွယ်ထားပေးပါတယ်။
iOS device ရဲ့ secret key နဲ့ data တွေကို ကာကွယ်ထားပေးပါတယ်။
တကယ်လို့ ဖုန်းပျောက်သွားပြီဆိုရင် find my iphone မှာ device lost လို့ ဆိုလိုက်တာ keychain ကို access လုပ်လို့ မရအောင် ကာကွယ်ထားပေးပါတယ်။
iCloud Backup လုပ်ထားတာ ဒါမှမဟုတ် iCloud Keychain Sync တွေ အတွက် encrypted လုပ်ထားပေးပါတယ်။ ဒါကြောင့် ဖုန်းအသစ်လဲပြီး restore ပြန်လုပ်တာနဲ့ Keychain data တွေ ပြန်ရနိုင်ပါတယ်။
Keychain မှာ ဘယ်လို အချိန်မှသာ ဖတ်နိုင်မယ်ဆိုတဲ့ Access Control တွေ ထည့်သွင်းလို့ ရပါတယ်။
iCloud Keychain Sync လုပ်လို့ရတဲ့အတွက် iPhone , iPad အြခင်းခြင်း sync လုပ်နိုင်ပါတယ်။
Keychain သုံးဖို့အတွက် Application ထဲမှာ security.framework လိုအပ်ပါတယ်။ ပြီးတော့ SecItem API ကို သုံးပြီးတော့ keychain database ရဲ့ CURD တွေကို လုပ်ဖို့ လိုအပ်ပါတယ်။
Application ကနေ Attributes နဲ့ query လုပ်လိုက်တဲ့ အခါမှာ Keychain ဆီကို ရောက်သွားမယ်။ Keychain ကနေ Secure enclave ကို ecnrypted data ပို့မယ်။ Secure enclave ကနေ decrypt လုပ်ပြီးတော့ keychain ဆီပြန်ပို့ပေးတယ်။ ရလာတဲ့ decyprted data က နေ user ရဲ့ secret key ကို keychain ကနေ Application ကို ပြန်ပို့ပေးပါတယ်။
ကျွန်တော်တို့တွေ Sample Code ကို ကြည့်ရအောင်။
NSDictionary *query = @{
(__bridge id)kSecClass: (__bridge id)kSecClassGenericPassword,
(__bridge id)kSecAttrService: key,
(__bridge id)kSecAttrAccount: name,
(__bridge id)kSecValueData: secret,
};
OSStatus status = SecItemAdd((__bridge CFDictionaryRef)query, nil);
if (status == noErr) {
//there is no Error. Successfully add
}
secret
ကို kSecValueData
ထဲမှာထည့်ပြီးတော့ Attribute Service နဲ့ Attribute Account ထည့်ပေးရပါတယ်။ Attribute Service နဲ့ Attribute Account ဟာ duplicate ဖြစ်လို့ မရပါဘူး။ secret
က NSData ဖြစ်နိုင်သလို NSDictionary , NSArray, NSString စတာတွေလည်း ဖြစ်နိုင်ပါတယ်။ NSDictionary , NSArray စတာတွေကို သိမ်းတဲ့ အခါမှာ NSKeyedArchiver လုပ်နိုင်ဖို့ လိုအပ်ပါတယ်။ သို့မဟုတ်ရင်တော့ သိမ်းလို့ ရမှာ မဟုတ်ပါဘူး။
ဒါကြောင့် အကောင်းဆုံးက NSData ကို သိမ်းတာ အသင့်လျော်ဆုံးပါပဲ။
SecItemAdd
နဲ့ ထည့်တဲ့ အခါမှာတော့ success ဖြစ်မဖြစ်ကို OSStatus နဲ့ ပြန်စစ်ဖို့ လိုအပ်ပါတယ်။
အခု ကျွန်တော်တို့တွေ့ data ကို ပြန်ထုတ်ဖို့ အောက်က code ကို ကြည့်ကြည့်ပါ။
NSDictionary *query = @{
(__bridge id)kSecClass: (__bridge id)kSecClassGenericPassword,
(__bridge id)kSecAttrService: key,
(__bridge id)kSecAttrAccount: name,
(__bridge id)kSecReturnData: @YES
};
CFTypeRef dataTypeRef = NULL;
OSStatus status = SecItemCopyMatching((__bridge CFDictionaryRef)query,
&dataTypeRef);
if(status != noErr) {
//error
}
else {
NSData *resultNSData = (__bridge NSData *)(CFDataRef)dataTypeRef;
}
SecItemCopyMatching
ကို အသုံးပြုပြီးတော့ query လုပ်တဲ့ အခါမှာ ထည့်ထားခဲ့တဲ့ kSecAttrService
, kSecAttrAccount
၂ ခုနဲ့ data ကို ပြန်ဆွဲထုတ်လို့ရပါတယ်။ kSecReturnData
က YES ဖြစ်ဖို့ လိုအပ်ပါတယ်။
အခု ထပ်ပြီးတော့ data ကို update လုပ်ကြည့်ရအောင်။
NSDictionary *query = @{
(__bridge id)kSecClass: (__bridge id)kSecClassGenericPassword,
(__bridge id)kSecAttrService: key,
(__bridge id)kSecAttrAccount: name
};
NSDictionary *changes = @{
(__bridge id)kSecValueData: secret
};
OSStatus status = SecItemUpdate((__bridge CFDictionaryRef)(query), (__bridge CFDictionaryRef)(changes));
ထုံးစံအတိုင်း kSecAttrService
နှင့် kSecAttrAccount
ကို အသုံးပြုပြီးတော့ query လုပ်ဖို့ လိုအပ်ပါတယ်။ ပြီးရင်တော့ kSecValueData ကို အြခား NSDictionary မှာ ထည့်ပြီးတော့ SecItemUpdate
နဲ့ update ရပါတယ်။
နောက်ဆုံး တစ်ခု အနေနဲ့ ပြန်ဖျက်ဖို့ပါ။
NSDictionary *query = @{
(__bridge id)kSecClass: (__bridge id)kSecClassGenericPassword,
(__bridge id)kSecAttrService: key,
(__bridge id)kSecAttrAccount: name
};
OSStatus status = SecItemDelete((__bridge CFDictionaryRef)(query));
ဖျက်ဖို့အတွက် query ကို kSecAttrService
နှင့် kSecAttrAccount
အသုံးပြုပါတယ်။ ပြီးရင်တော့ SecItemDelete ကို အသုံးပြုပြီးတော့ ဖျက်ပါတယ်။
data ကို ပြင်ချင်တဲ့ အခါမှာ ဖျက်ပြီး အသစ်ပြန်ထည့်တာထက် update လုပ်တာ က ပိုသင့်လျော်တယ်ဆိုတာကို သတိပြုစေချင်ပါတယ်။
အထက်မှာ ပြောသွားတဲ့ Code တွေဟာ KeyChain မှာပဲ သိမ်းသွားတာပါ။ တကယ်လို့ TouchID ကို အသုံးပြုပြီးတော့ သိမ်းမယ်ဆိုရင်တော့ kSecAttrAccessControl
ကို ထပ်ဖြည့်ပေးဖို့လိုပါတယ်။
SecAccessControlRef sacObject =
SecAccessControlCreateWithFlags(kCFAllocatorDefault,
kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly,
kSecAccessControlUserPresence, &error); !
NSData* secret = [@"top secret" dataWithEncoding:NSUTF8StringEncoding];
NSDictionary *query = @{
(id)kSecClass: (id)kSecClassGenericPassword,
(id)kSecAttrService: @"myservice",
(id)kSecAttrAccount: @"account name here",
(id)kSecValueData: secret,
(id)kSecAttrAccessControl: (id)sacObject};
!
OSStatus status = SecItemAdd((CFDictionaryRef)query, nil);
အထက်ပါ code မှာ
SecAccessControlRef sacObject =
SecAccessControlCreateWithFlags(kCFAllocatorDefault,
kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly,
kSecAccessControlUserPresence, &error); !
ကို ရေးထားတာ တွေ့နိုင်ပါတယ်။
ပြန်ဖတ်တဲ့ အခါမှာတော့
NSDictionary *query = @{
(id)kSecClass: (id)kSecClassGenericPassword,
(id)kSecAttrService: @"myservice",
(id)kSecAttrAccount: @"account name here",
(id)kSecReturnData: @YES,
(id)kSecUseOperationPrompt: @"Authenticate to login to server"
};
!
CFTypeRef dataTypeRef = NULL;
OSStatus status = SecItemCopyMatching((CFDictionaryRef)query,
&dataTypeRef);
kSecUseOperationPrompt
မှာ TouchID မှာ ဖော်ပြချင်တဲ့ စာကို ရေးသားထားဖို့ လိုအပ်ပါတယ်။
SecItemCopyMatching
ကို ခေါ်လိုက်တဲ့ အခါမှာတော့ အောက်ကလို UI မြင်ရပါမယ်။
သတိပြုသင့်တာကတော့ kSecAttrAccessControl
ကို အသုံးပြုတဲ့ အခါမှာ User က Device ရဲ့ Passcode ထည့်ပြီးတော့ Unlock လုပ်နိုင်ပါတယ်။ နောက်ပြီးတော့ Touch ID ကို disable လုပ်လိုက်တဲ့ အခါ သို့မဟုတ် Passcode ကို remove လုပ်လိုက်တဲ့ အခါမှာတော့ data ကို ပြန်ဖတ်လို့ ရမှာ မဟုတ်ပါဘူး။
TouchID ကို တောင်းတဲ့ အခါမှာ Thread ပြောင်းသွားတဲ့ အတွက် ပြန်ဖတ်တဲ့ အခါမှာ အောက်ကလို ရေးထားသင့်ပါတယ်။
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
CFTypeRef dataTypeRef = NULL;
OSStatus status = SecItemCopyMatching((CFDictionaryRef)query,
&dataTypeRef);
}
ကျွန်တော်တို့ တွေ App မှာ TouchID ကို အသုံးပြုပြီးတော့ kSecAttrAccessControl
ကို သုံးပြီးတော့ data သိမ်းမလား LAContext
ကို အသုံးပြုရမလား ဆိုပြီး မေးစရာ ဖြစ်လာပါတယ်။
တကယ်လို့ server side ပါတယ်။ login က server ဘက်က ဝင်ရမယ်ဆိုရင်တော့ LAContext
နဲ့ ပုံမှန် keychain ကို တွဲသုံးတာ က ပိုသင့်လျော်ပါတယ်။ Server side မပါဘူး။ App ထဲမှာပဲ security အနေနဲ့ ထည့်ချင်တယ်ဆိုရင်တော့ kSecAttrAccessControl
က သင့်လျော်ပါတယ်။
Keychain အကြောင်းကို ပိုပြီးနားလည် စေချင်တယ်ဆိုရင်တော့ WWDC 2014 က Keychain and Authentication with Touch ID ကို ကြည့်သင့်ပါတယ်။