* * * * * * * * * * * * * * * * * * * * * * * * *


Preface:

When sensitive information (passwords, keys, etc.) needs to be stored in projects, Apple provides a storage mechanism called keychain. A keychain is an encrypted database stored on a hard disk. This may be the reason why the keychain information remains after the App is uninstalled. A keychain is suitable for storing a small amount of data (no more than 1000 bytes or up to megabytes). QiKeychain is used to add, delete, modify, and query a keychain.

The following figure provides an intuitive understanding of keychain.

Demo (QiKeychain)

I did four things with Demo (QiKeychain).

  • Added: Stores the user name and password to a keychain.
  • Query: Queries the password from the keychain based on the user name.
  • Delete: Deletes the user name and password from the keychain.
  • Modify: Changes the password of the user name in the keychain.

The operations performed by Demo (QiKeychain) on keychain are as follows:

  • Storage user name QiShare, password 1234;
  • Query the password of user QiShare. The password is 1234.
  • Change the password of user QiShare to 123456.
  • Query the password of QiShare, and 123456 is displayed.
  • Example Delete QiShare from the keychain.

Keychain basically uses apis

A keychain has four apis for adding, deleting, modifying, and querying data in a keychain.

Data items in a keychain exist as items. For example, in the case of storing user names and passwords, each item contains stored user names and passwords and other attributes. A keychain contains multiple user names and passwords items.

The following diagram (storing data and properties into the keychain) helps us understand the stored procedure

SecItemAdd: Adds one item or more items to a keychain
OSStatus SecItemAdd(CFDictionaryRef attributes, CFTypeRef * __nullable CF_RETURNS_RETAINED result)
    API_AVAILABLE(macos(10.6), ios(2.0));
Copy the code

Store key code:

    NSDictionary *saveSecItems = @{(id)kSecClass: (id)kSecClassGenericPassword,
                               (id)kSecAttrService: service,
                               (id)kSecAttrAccount: account,
                               (id)kSecValueData: passwordData
                               };
    OSStatus saveStatus = SecItemAdd((CFDictionaryRef)saveSecItems, NULL);
Copy the code
SecItemCopyMatching: Returns an item or items matching a search query
OSStatus SecItemCopyMatching(CFDictionaryRef query, CFTypeRef * __nullable CF_RETURNS_RETAINED result)
    API_AVAILABLE(macos(10.6), ios(2.0));
Copy the code

Query key code:

  NSDictionary *matchSecItems = @{
                                    (id)kSecClass: (id)kSecClassGenericPassword,
                                    (id)kSecAttrService: service,
                                    (id)kSecAttrAccount: account,
                                    (id)kSecMatchLimit: (id)kSecMatchLimitOne,
                                    (id)kSecReturnData: @(YES)
                                    };
  CFTypeRef dataRef = nil;
  OSStatus errorCode = SecItemCopyMatching((CFDictionaryRef)matchSecItems, (CFTypeRef *)&dataRef);
Copy the code
SecItemUpdate: Modifies an item or items matching a search query
OSStatus SecItemUpdate(CFDictionaryRef query, CFDictionaryRef attributesToUpdate)
    API_AVAILABLE(macos(10.6), ios(2.0));
Copy the code

Note: in updating the code, I had some problems at the beginning, thanks to the team members, especially Kuno.

SecItemUpdate takes two parameters, Query and attributesToUpdate. The first parameter query is used to query the corresponding item, and the second parameter attributesToUpdate is used to pass in the information to be updated. The author mistakenly passed (ID)kSecClass: (ID)kSecClassGenericPassword to change for the second attributesToUpdate parameter. Error message errSecNoSuchAttr = -25303, /* The specified attribute does not exist. */

Update key code:

    NSDictionary *queryItems = @{(id)kSecClass: (id)kSecClassGenericPassword,
                               (id)kSecAttrService: service,
                               (id)kSecAttrAccount: account
                               };
    NSData *passwordData = [password dataUsingEncoding:NSUTF8StringEncoding];
    NSDictionary *updatedItems = @{
                                   (id)kSecValueData: passwordData,
      };
    OSStatus updateStatus = SecItemUpdate((CFDictionaryRef)queryItems, (CFDictionaryRef)updatedItems);
Copy the code
SecItemDelete: Deletes an item or items that match the search query
OSStatus SecItemDelete(CFDictionaryRef query)
    API_AVAILABLE(macos(10.6), ios(2.0));
Copy the code

Delete key code:

    NSDictionary *deleteSecItems = @{
                                    (id)kSecClass: (id)kSecClassGenericPassword,
                                    (id)kSecAttrService: service,
                                    (id)kSecAttrAccount: account
                                    };
    OSStatus errorCode = SecItemDelete((CFDictionaryRef)deleteSecItems);
Copy the code
Obviously the keychainAdd and deleteAll related apis need to be set accordinglyAttribute dictionary(Refer to saveSecItems, matchSecItems, queryItems, updatedItems, deleteSecItems respectively)
  • The key and value of the attribute dictionary are used as follows:
  • (id)kSecClass: (id)kSecClassGenericPassword

KSecClass represents the class (ID) of item. The value of kSecClass indicates a general password for item. I usually pass kSecClassGenericPassword

  • (id)kSecAttrService: service
The value of kSecAttrService is used to indicate the service of the itemCopy the code
  • (id)kSecAttrAccount: account

(id) The value of kSecAttrAccoun indicates the account name of item

  • (id)kSecValueData: passwordData

(id)kSecValueData Indicates the item data

  • (id)kSecMatchLimit: (id)kSecMatchLimitOne,

(id)kSecMatchLimit has two values (id)kSecMatchLimitOne, and (id)kSecMatchLimitAll kSecMatchLimitOne: KSecMatchLimitAll: Matches an unlimited number of items

  • (id)kSecReturnData: @(YES)

(id) The value of kSecReturnData is a Boolean value used to determine whether to return item data

  • The value of kSecClass represents the class of item

The value of kSecClass indicates a generic password item that the author generally passes in as kSecClassGenericPassword

  • The value of kSecClass represents the class of item

The value of kSecClass indicates a generic password item that the author generally passes in as kSecClassGenericPassword

Codes related to Demo (QiKeychain)

In Demo (QiKeychain), the author encapsulates apis related to keychain. Obtain the GitHub address of Demo (QiKeychain) : QiKeychain.

Note: The code I later encapsulated changed the logic of the save operation.

The modification is as follows: When saving the user name and password -> Check whether the user name exists in the keychain -> Update the user name if it exists. -> If no, save the file.

The corresponding schematic diagram (using keys to store network passwords) is as follows:

Reference learning address

  • Keychain Services
  • SAMKeychain

Recommended articles:

IOS custom drag-and-drop control: QiDragView iOS custom card control: QiCardView iOS Wireshark Capture iOS Charles capture TCP IP address and UDP QiCardView