Ferramenta de armazenamento de dados criptografados: introdução e uso do Keychain

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 kSecAttrAccessible

    kSecAttrAccessibleWhenUnlocked (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;

Acho que você gosta

Origin blog.csdn.net/ID314846818/article/details/83037117
Recomendado
Clasificación