1. Introdução
As chaves em dispositivos iOS armazenam com segurança alguns dados confidenciais dos usuários, como senhas de usuários, tokens de autenticação, etc. A própria Apple usa chaveiro para armazenar senhas de Wi-Fi, certificados, etc. Os dados nas chaves podem ser compartilhados entre aplicativos com o mesmo TeamID no mesmo grupo de chaves, ativando a função de compartilhamento de chaves.
Os dados salvos pelo Keychain são criptografados e armazenados internamente usando um banco de dados sqlite localizado em /private/var/Keychains/keychain-x.db. Como o chaveiro é armazenado fora da sandbox do aplicativo, os dados armazenados no chaveiro não serão excluídos quando o usuário excluir o aplicativo. Como esta situação pode levar a alguns vazamentos de privacidade do usuário, a Apple afirmou que não garante que os dados das chaves existirão após a exclusão do aplicativo. Esta situação são apenas alguns detalhes de implementação dos dados de armazenamento das chaves, e a Apple também modificou algumas versões beta do iOS 10.3 Depois de passar esse recurso, pode-se considerar que o impacto real é relativamente grande, e não foi lançado para a versão oficial.
Um cenário de uso comum é armazenar tokens de comando de login do usuário na esperança de que os usuários possam fazer login diretamente ao desinstalar o aplicativo e instalá-lo novamente. Sempre que os dados do usuário são solicitados, um token de comando de login precisa ser trazido. Deve-se notar que o IO dos dados das chaves precisa ser criptografado e descriptografado, o que exige um desempenho relativamente intensivo, portanto, a leitura e gravação direta frequente dos dados das chaves devem ser evitado tanto quanto possível.
2. Noções básicas de chaveiro
Cada dado armazenado no Keychain é oficialmente chamado de SecItem. A operação do SecItem requer informações de consulta correspondentes. Essas informações de consulta precisam usar a chave fornecida pelo sistema. A seguir, apresentamos principalmente os seguintes tipos comuns:
-
A chave que representa o tipo de dados de armazenamento, incluindo um total de dois, tipo de conteúdo e tipo de armazenamento de dados
1) Tipo de conteúdo de armazenamento: kSecClass, os valores opcionais são:kSecClassGenericPassword (senha geral)
kSecClassInternetPassword (senha da Internet)
kSecClassCertificate (certificado)
kSecClassKey (chave de segurança)
kSecClassIdentity (identificador)Normalmente o valor que usamos é kSecClassGenericPassword
3) Quando o valor de kSecClass é kSecClassGenericPassword, o tipo de dados de armazenamento correspondente é NSData e a chave do tipo de dados de armazenamento é kSecValueData. Existem diferentes suportes de API para outros tipos de operações. Atualmente, apenas este tipo é introduzido.
-
Grupo de compartilhamento de dados de chaveiro: kSecAttrAccessGroup
kSecAttrAccessGroup (Grupo que permite acesso): Se não definido, o primeiro definido no plist será usado automaticamente. Normalmente o plist será configurado como TeamID+BundleID, que é o prefixo appID mais o BundleID da configuração de empacotamento do aplicativo.
-
Uma é a chave dos atributos detalhados do SecItem, incluindo principalmente kSecAttrGeneric, kSecAttrAccount, kSecAttrService, kSecAttrAccessGroup
kSecAttrGeneric (atributo geral): pode não ser definido ou definido, mas não afeta a exclusividade.
O valor kSecAttrAccount (conta)
é uma String que identifica uma conta exclusiva. (Se não for definido, será tratado como @"").
kSecAttrService (serviço):
O valor é uma String, marcando o único serviço. É equivalente a ser considerado uma chave primária conjunta com kSecAttrAccount, marcando o SecItem exclusivo. Diferentes serviços ou contas devem ser usados para salvar conteúdos diferentes. (Se não definido, será considerado @"") -
Defina o controle de acesso SecItem (usado ao adicionar): kSecAttrAccessible, se adicionar configurações de autorização do usuário, use kSecAttrAccessControl (novo no iOS 8)
1) valor opcional kSecAttrAccessiblekSecAttrAccessibleWhenUnlocked (padrão do sistema): quando o dispositivo está desbloqueado
kSecAttrAccessibleAfterFirstUnlock: quando o dispositivo é desbloqueado pela primeira vez
kSecAttrAccessibleAlways (obsoleto): a qualquer momento
kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly: dispositivo atual, ao definir a senha
kSecAttrAccessibleWhenUnlockedThisDeviceOnly: dispositivo atual, quando desbloqueado
kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly: dispositivo atual, quando desbloqueado pela primeira vez
kSecAttrAccessibleAlwaysThisDeviceOnly: dispositivo atual, a qualquer momento
2) O valor de kSecAttrAccessControl é um objeto SecAccessControlRef. SecAccessControlRef contém principalmente dois parâmetros principais, um é o kSecAttrAccessible mencionado acima e o outro é SecAccessControlCreateFlags, que é usado para definir permissões relacionadas à autenticação do usuário.
-
Backup para iCloud: kSecAttrSynchronizable
Se for necessário backup, ao adicionar SecItem, o valor correspondente é definido como @YES. Se você deseja sincronizar com outros dispositivos e usá-lo, evite usar as configurações DeviceOnly ou outras permissões de controle relacionadas ao dispositivo.
3. Uso
A API principal para adicionar, excluir, modificar e verificar dados do Keychain:
```
/** 添加 */
OSStatus SecItemAdd(CFDictionaryRef attributes, CFTypeRef * __nullable CF_RETURNS_RETAINED result);
/** 查询 */
OSStatus SecItemCopyMatching(CFDictionaryRef query, CFTypeRef * __nullable CF_RETURNS_RETAINED result);
/** 更新 */
OSStatus SecItemUpdate(CFDictionaryRef query, CFDictionaryRef attributesToUpdate);
/** 删除 */
OSStatus SecItemDelete(CFDictionaryRef query);
```
consultar informações
- (NSMutableDictionary *)queryWithAccount:(NSString *)account service:(NSString *)service {
NSMutableDictionary *dictionary = [NSMutableDictionary dictionaryWithCapacity:3];
// 设置储存的数据类型为「通用密码」
[dictionary setObject:(__bridge id)kSecClassGenericPassword forKey:(__bridge id)kSecClass];
// account 与 service 可以认为是联合主键
[dictionary setObject:service forKey:(__bridge id)kSecAttrService];
[dictionary setObject:account forKey:(__bridge id)kSecAttrAccount];
// kSecAttrAccessGroup 不设置则自动使用plist里设置的第一个
[dictionary setObject:@"com.xxxx.xx.accessgroup" forKey:(__bridge id)kSecAttrAccessGroup];
// 需要注意,如果设置为 YES, 怎在添加 SecItem 时,kSecAttrAccessible 就不要设置为 DeviceOnly
[dictionary setObject:@YES forKey:(__bridge id)(kSecAttrSynchronizable)];
return dictionary;
}
`
Adicionar SecItem
NSMutableDictionary* query = [self queryWithAccount: @"account" service:@"service"];
// 需要保存的内容
[query setObject:[@"password_xxx" dataUsingEncoding:NSUTF8StringEncoding] forKey:(__bridge id)kSecValueData];
// 设置访问权限(如果需要本地验证,则创建 SecAccessControlRef 对象,使用 kSecAttrAccessControl Key)
[query setObject:(__bridge id)kSecAttrAccessibleWhenUnlocked forKey:(__bridge id)kSecAttrAccessible];
// 设置与否不影响数据的唯一性
[query setObject:self.label forKey:(__bridge id)kSecAttrLabel];
// 插入数据
OSStatus s = SecItemAdd((__bridge CFDictionaryRef)query, NULL);
renovar
// 指定需要更新的条目
NSMutableDictionary * searchQuery = [self queryWithAccount: @"account" service:@"service"];
// 需要更新的属性
NSMutableDictionary *update = [[NSMutableDictionary alloc]init];
[update setObject:self.passwordData forKey:(__bridge id)kSecValueData];
[update setObject:(__bridge id)kSecAttrAccessibleWhenUnlocked forKey:(__bridge id)kSecAttrAccessible];
status = SecItemUpdate((__bridge CFDictionaryRef)(update), (__bridge CFDictionaryRef)(query));
Investigar
CFTypeRef result = NULL;
NSMutableDictionary* query = [self queryWithAccount: @"account" service:@"service"];
// 需要返回值,如果只判断条目是否存在则不需要设置
[query setObject:@YES forKey:(__bridge id)kSecReturnData];
// 设置只需要一条结果
[query setObject:(__bridge id)kSecMatchLimitOne forKey:(__bridge id)kSecMatchLimit];
// 查找
status = SecItemCopyMatching((__bridge CFDictionaryRef)query, &result);
if (statue == errSecSuccess) {
// 获取到的数据
NSData *passwordData = (__bridge_transfer NSData *)result;
}
excluir
NSMutableDictionary* query = [self queryWithAccount: @"account" service:@"service"];
status = SecItemDelete((__bridge CFDictionaryRef)query);
// 注:Mac 上 keychain 储存机制不同,需要做额外处理
Diferentes tipos de armazenamento de chaveiro e parâmetros correspondentes
iOS钥匙串KeyChain相关参数的说明
密匙类型:
键:
CFTypeRef kSecClass
值:
CFTypeRef kSecClassGenericPassword //一般密码
CFTypeRef kSecClassInternetPassword //网络密码
CFTypeRef kSecClassCertificate //证书
CFTypeRef kSecClassKey //密钥
CFTypeRef kSecClassIdentity //身份证书(带私钥的证书)
密钥串项属性
1.kSecClassGenericPassword //一般密码
属性:
kSecAttrAccessible //kSecAttrAccessiblein变量用来指定这条信息的保护程度
kSecAttrAccessGroup //密钥访问组
kSecAttrCreationDate //创建日期(read only)
kSecAttrModificationDate //最后一次修改日期
kSecAttrDescription //描述
kSecAttrComment //注释
kSecAttrCreator //创建者
kSecAttrType //类型
kSecAttrLabel //标签(给用户看)
kSecAttrIsInvisible //是否隐藏
kSecAttrIsNegative //是否具有密码
kSecAttrAccount //账户名
kSecAttrService //所具有服务
kSecAttrGeneric //用户自定义内容
2. kSecClassInternetPassword //网络密码
属性:
kSecAttrAccessible
kSecAttrAccessGroup
kSecAttrCreationDate
kSecAttrModificationDate
kSecAttrDescription
kSecAttrComment
kSecAttrCreator
kSecAttrType
kSecAttrLabel
kSecAttrIsInvisible
kSecAttrIsNegative
kSecAttrAccount
kSecAttrSecurityDomain
kSecAttrServer
kSecAttrProtocol //协议类型 CFNumberRef
kSecAttrAuthenticationType //认证类型 CFNumberRef
kSecAttrPort //网络端口
kSecAttrPath //访问路径
3.kSecClassCertificate //证书
属性:
kSecAttrAccessible
kSecAttrAccessGroup
kSecAttrLabel
kSecAttrCertificateType //证书类型
kSecAttrCertificateEncoding //证书编码类型
kSecAttrSubject //X.500主题名称
kSecAttrIssuer //X.500发行者名称
kSecAttrSerialNumber //序列号
kSecAttrSubjectKeyID //主题ID
kSecAttrPublicKeyHash //公钥Hash值
4.kSecClassKey//密钥
属性:
kSecAttrAccessible //变量用来指定这条信息的保护程度
kSecAttrAccessGroup //密钥存取群
kSecAttrKeyClass //加密密钥类
kSecAttrLabel //标签
kSecAttrApplicationLabel //标签(给程序使用) CFStringRef(通常是公钥的Hash值)
kSecAttrIsPermanent //是否永久保存加密密钥
kSecAttrApplicationTag //标签(私有标签数据)
kSecAttrKeyType //加密密钥类型(算法)
kSecAttrKeySizeInBits //密钥总位数 CFNumberRef
kSecAttrEffectiveKeySize //密钥有效位数 CFNumberRef
kSecAttrCanEncrypt //密钥是否可用于加密 CFBooleanRef
kSecAttrCanDecrypt //密钥是否可用于解密 CFBooleanRef
kSecAttrCanDerive //密钥是否可用于导出其他密钥 CFBooleanRef
kSecAttrCanSign //密钥是否可用于数字签名 CFBooleanRef
kSecAttrCanVerify //密钥是否可用于验证数字签名 CFBooleanRef
kSecAttrCanWrap //密钥是否可用于打包其他密钥 CFBooleanRef
kSecAttrCanUnwrap //密钥是否可用于解包其他密钥 CFBooleanRef
5.kSecClassIdentity //身份证书(带私钥的证书)
属性:
1.证书属性
2.私钥属性
密钥串项属性的值:
属性:
1.kSecAttrAccessible
CFTypeRef kSecAttrAccessibleWhenUnlocked; //解锁可访问,备份
CFTypeRef kSecAttrAccessibleAfterFirstUnlock; //第一次解锁后可访问,备份
CFTypeRef kSecAttrAccessibleAlways; //一直可访问,备份
CFTypeRef kSecAttrAccessibleWhenUnlockedThisDeviceOnly; //解锁可访问,不备份
CFTypeRef kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly; //第一次解锁后可访问,不备份
CFTypeRef kSecAttrAccessibleAlwaysThisDeviceOnly; //一直可访问,不备份
2.kSecAttrProtocol
略...
3.kSecAttrKeyClass //加密密钥类 CFTypeRef
CFTypeRef kSecAttrKeyClassPublic; //公钥
CFTypeRef kSecAttrKeyClassPrivate; //私钥
CFTypeRef kSecAttrKeyClassSymmetric; //对称密钥
返回值类型
可以同时指定多种返回值类型
CFTypeRef kSecReturnData; //返回数据(CFDataRef) CFBooleanRef
CFTypeRef kSecReturnAttributes; //返回属性字典(CFDictionaryRef) CFBooleanRef
CFTypeRef kSecReturnRef; //返回实例(SecKeychainItemRef, SecKeyRef, SecCertificateRef, SecIdentityRef, or CFDataRef) CFBooleanRef
CFTypeRef kSecReturnPersistentRef; //返回持久型实例(CFDataRef) CFBooleanRef
写入值类型
CFTypeRef kSecValueData;
CFTypeRef kSecValueRef;
CFTypeRef kSecValuePersistentRef;