First, Keychain Foundation
According to Apple, keychain in iOS devices is a secure storage container that can be used to store sensitive information such as user name, password, network password, and authentication token for different applications. Apple itself uses keychain to save Wi-Fi network passwords, VPN credentials, and so on. It is a SQLite database, located in/private/var/keychains/keychain-2.db, where all of the data saved is encrypted.
Developers often want to be able to use the functionality provided by the operating system to save credentials (credentials) instead of saving them (vouchers) to places like nsuserdefaults,plist files. This data is saved because the developer does not want the user to log in every time, so the authentication information is saved to a place on the device and automatically logged in when the user opens the application again. Keychain information is present in the sandbox of every application (APP).
The data in keychain can be shared between applications by keychain access groups. Requires the group to be specified when data is saved to keychain. The best way to save data to keychain is to use the keychainitemwrapper provided by Apple. can go to this download example project. The first step is to create an instance of this class.
Copy Code code as follows:
Keychainitemwrapper *wrapper = [[Keychainitemwrapper alloc] initwithidentifier:@ "Password" accessgroup:nil];
The identifier (Identifier) is used when we want to take data from the keychain in the back. If you want to share information between applications, you need to specify Access groups (Access group). An application with the same access group can access the same keychain information.
Copy Code code as follows:
Keychainitemwrapper *wrapper = [[Keychainitemwrapper alloc] initwithidentifier:@ "account number" accessgroup:@ "YOUR_ APP_ID_HERE.com.yourcompany.GenericKeychainSuite "];
To save the information in keychain, use the Setobject:forkey: method. Here, (ID) ksecattraccount is a predefined key (key) that we can use to save the account name. KSECCLASS Specifies some kind of information we want to save, here is a generic password. Ksecvaluedata can be used to hold arbitrary data, here is a password.
Copy Code code as follows:
[Wrapper Setobject:ksecclassgenericpassword Forkey: (ID) ksecclass];
[Wrapper setobject:@ "username" Forkey: (ID) ksecattraccount];
[Wrapper setobject:@ "Password" Forkey: (ID) ksecvaluedata];
[Wrapper setobject: (ID) ksecattraccessiblealwaysthisdeviceonly Forkey: (ID) ksecattraccessible];
The Ksecattraccessiblein variable is used to specify that this application is appropriate to access this data. We need to pay special attention to this option and use the most stringent options. This key can be set to 6 kinds of values.
Of course, we should never use Ksecattraccessiblealways. The option for a safe point is ksecattraccessiblewhenunlocked. Some options end with thisdeviceonly, and if this option is selected, the data is encrypted with a hardware-related key (key) and cannot be transmitted to or seen by other devices. Even if they provide further security, using them may not be a good idea unless you have a better reason not to allow data to migrate between backups.
To get the data from the keychain, you can use the nsstring *accountname = [wrapper objectforkey: (ID) ksecattraccount];
The entry in the keychain is called Secitem, but it is stored in cfdictionary. The Secitemref type does not exist. Secitem has five categories: universal passwords, Internet passwords, certificates, keys, and identities. In most cases, we use a generic password. Many of the problems are caused by developers trying to use internet passwords. Internet passwords are much more complex and few advantages, and they are not necessary unless you develop a Web browser. Keychainitemwrapper only uses a generic password, which is one of the reasons I like it. iOS apps rarely store keys and identities, so we don't discuss that in this book. Only public key certificates should usually be stored in a file, not in a keychain.
Finally, we need to search the keychain for what we need. There are many parts of a key that can be used to search, but the best way is to assignments your identity to it and then search for it. Common password entries contain attribute ksecattrgeneric that you can use to store identifiers. This is also the way keychainitemwrapper is handled.
The items in the keychain have several searchable * * attributes * and an encrypted * * value * *. For common password entries, the more important attributes are accounts (Ksecattraccount), Services (Ksecattrservice), and Identifiers (Ksecattrgeneric). And the value is usually a password.
Description
Each keychain is composed of a graph, and the whole is a dictionary structure.
The 1.kSecClass key definition belongs to that type of keychain
2. Different types contain different attributes, and these attributes define the specific information for this item
3. Each item can contain a password entry to store the corresponding password
Second, keychain operation
The Security.framework framework in iOS provides four main ways to operate keychain:
Copy Code code as follows:
Inquire
Osstatus secitemcopymatching (cfdictionaryref query, Cftyperef *result);
Add to
Osstatus secitemadd (cfdictionaryref attributes, Cftyperef *result);
Update
Itemosstatus Secitemupdate in Keychain (cfdictionaryref query, Cfdictionaryref attributestoupdate);
Delete
Itemosstatus Secitemdelete in Keychain (cfdictionaryref query)
iii. use of keychain
Introduction of security packs, introduction of File #import <Security/Security.h>
Add to
Copy Code code as follows:
-(Ibaction) Add: () sender {
(NameField.text.length > && passwordField.text.length >) {
nsmutabledictionary* dic = [Nsmutabledictionary dictionary];
[dic setobject: () Ksecclassgenericpassword Forkey: () Ksecclass];
[DiC setObject:nameField.text Forkey: () Ksecattraccount];
[dic Setobject:[passwordfield.text datausingencoding:nsutf8stringencoding] Forkey: () ksecvaluedata];
Osstatus s = Secitemadd ((cfdictionaryref) dic, NULL);
NSLog (, s);
}
}
Find
Copy Code code as follows:
-(Ibaction) sel: () Sender {
nsdictionary* query = [Nsdictionary dictionarywithobjectsandkeys:ksecclassgenericpassword,ksecclass,
Ksecmatchlimitall,ksecmatchlimit,
Kcfbooleantrue,ksecreturnattributes,nil];
Cftyperef result = nil;
Osstatus s = secitemcopymatching ((cfdictionaryref) query, &result);
NSLog (, s);
NSLog (, result);
}
-(Ibaction) sname: () Sender {
(NameField.text.length >) {
nsdictionary* query = [Nsdictionary dictionarywithobjectsandkeys:ksecclassgenericpassword,ksecclass,
Namefield.text,ksecattraccount,
Kcfbooleantrue,ksecreturnattributes,nil];
Cftyperef result = nil;
Osstatus s = secitemcopymatching ((cfdictionaryref) query, &result);
NSLog (, s); NSLog (, result);
(s = = NOERR) {
nsmutabledictionary* dic = [Nsmutabledictionary Dictionarywithdictionary:result];
[dic setobject: () kcfbooleantrue forkey:ksecreturndata];
[dic Setobject:[query Objectforkey:ksecclass] forkey:ksecclass];
nsdata* data = nil;
(Secitemcopymatching (Cfdictionaryref) dic, (cftyperef*) &data) = = NOERR) {
(data.length)
NSLog (, [[[NSString Alloc] initwithdata:data encoding:nsutf8stringencoding] autorelease]);
}
}
}
}
Modify
Copy Code code as follows:
-(ibaction) Update: () sender {
(NameField.text.length > && passwordField.text.length >) {
nsdictionary* query = [Nsdictionary dictionarywithobjectsandkeys:ksecclassgenericpassword,ksecclass,
Namefield.text,ksecattraccount,
Kcfbooleantrue,ksecreturnattributes,nil];
Cftyperef result = nil;
(secitemcopymatching (cfdictionaryref) query, &result) = = NOERR)
{
nsmutabledictionary* update = [nsmutabledictionary dictionarywithdictionary: (nsdictionary*) result];
[Update Setobject:[query Objectforkey:ksecclass] Forkey:ksecclass];
[Update Setobject:[passwordfield.text datausingencoding:nsutf8stringencoding] forkey:ksecvaluedata];
[Update Removeobjectforkey:ksecclass];
Target_iphone_simulator
[Update Removeobjectforkey: () Ksecattraccessgroup];
nsmutabledictionary* UpdateItem = [Nsmutabledictionary Dictionarywithdictionary:result];
[UpdateItem setobject:[query Objectforkey: () Ksecclass] Forkey: () Ksecclass];
Osstatus status = Secitemupdate ((cfdictionaryref) UpdateItem, (cfdictionaryref) update);
NSLog (, status);
Remove
Copy Code code as follows:
-(Ibaction) del: () sender {
(NameField.text.length >) {
nsdictionary* query = [Nsdictionary dictionarywithobjectsandkeys:ksecclassgenericpassword,ksecclass,
Namefield.text,ksecattraccount,nil];
Osstatus status = Secitemdelete ((cfdictionaryref) query);
NSLog (, status); }
}
Four, save the password instance
take a look at the example of saving passwords using keychain:
Copy Code code as follows:
@implementation Wqkeychain
+ (Nsmutabledictionary *) Getkeychainquery: (NSString *) Service {
return [nsmutabledictionary Dictionarywithobjectsandkeys:
(__bridge_transfer ID) Ksecclassgenericpassword, (__bridge_transfer ID) ksecclass,
Service, (__bridge_transfer ID) ksecattrservice,
Service, (__bridge_transfer ID) ksecattraccount,
(__bridge_transfer ID) ksecattraccessibleafterfirstunlock, (__bridge_transfer ID) ksecattraccessible,
NIL];
}
+ (void) Save: (NSString *) service data: (ID) Data {
Get Search Dictionary
Nsmutabledictionary *keychainquery = [self getkeychainquery:service];
Delete old item before add New item
Secitemdelete ((__bridge_retained cfdictionaryref) keychainquery);
Add new object to search dictionary (attention:the data format)
[Keychainquery setobject:[nskeyedarchiver Archiveddatawithrootobject:data] Forkey: (__bridge_transfer ID) Ksecvaluedata];
ADD item to keychain with the search dictionary
Secitemadd ((__bridge_retained cfdictionaryref) keychainquery, NULL);
}
+ (ID) Load: (NSString *) Service {
ID ret = NIL;
Nsmutabledictionary *keychainquery = [self getkeychainquery:service];
Configure the search setting
[Keychainquery setobject: (ID) kcfbooleantrue Forkey: (__bridge_transfer ID) ksecreturndata];
[Keychainquery setobject: (__bridge_transfer ID) ksecmatchlimitone forkey: (__bridge_transfer ID) kSecMatchLimit];
Cfdataref keyData = NULL;
if (secitemcopymatching (__bridge_retained cfdictionaryref) keychainquery, (Cftyperef *) &keydata) = = NOERR) {
@try {
ret = [Nskeyedunarchiver unarchiveobjectwithdata: (__bridge_transfer NSData *) KeyData];
} @catch (NSException *e) {
NSLog (@ "Unarchive of%@ failed:%@", service, E);
} @finally {
}
}
return ret;
}
+ (void) Delete: (NSString *) Service {
Nsmutabledictionary *keychainquery = [self getkeychainquery:service];
Secitemdelete ((__bridge_retained cfdictionaryref) keychainquery);
}
@end
Copy Code code as follows:
@interface Wquserdatamanager:nsobject
/**
* @brief Store password
*
* @param password Password content
*/
+ (void) Savepassword: (NSString *) password;
/**
* @brief Read the password
*
* @return Password content
*/
+ (ID) Readpassword;
/**
* @brief Delete password data
*/
+ (void) Deletepassword;
@end
Copy Code code as follows:
#import "WQUserDataManager.h"
@implementation Wquserdatamanager
Static NSString * Const Key_in_keychain = @ "Com.wuqian.app.allinfo";
Static NSString * Const KEY_PASSWORD = @ "Com.wuqian.app.password";
+ (void) Savepassword: (NSString *) password
{
Nsmutabledictionary *usernamepasswordkvpairs = [Nsmutabledictionary dictionary];
[Usernamepasswordkvpairs Setobject:password Forkey:key_password];
[Wqkeychain Save:key_in_keychain Data:usernamepasswordkvpairs];
}
+ (ID) readpassword
{
Nsmutabledictionary *usernamepasswordkvpair = (nsmutabledictionary *) [Wqkeychain Load:key_in_keychain];
return [Usernamepasswordkvpair Objectforkey:key_password];
}
+ (void) Deletepassword
{
[Wqkeychain Delete:key_in_keychain];
}
@end
Implement a simple interface, save the set password, and immediately read the display to see the effect
Copy Code code as follows:
-(Ibaction) Btnaciton: (ID) sender
{
[Wquserdatamanager SavePassWord:self.textfield.text];
Self.label.text = [Wquserdatamanager Readpassword];
}