Toute application qui enregistre les données de l'utilisateur doit veiller à la sécurité et à la confidentialité de ces données. Comme nous l'avons vu avec les récentes violations de données, le fait de ne pas protéger les données stockées de vos utilisateurs peut avoir des conséquences très graves. Dans ce tutoriel, vous apprendrez les meilleures pratiques pour protéger les données de vos utilisateurs..
Dans le post précédent, vous avez appris à protéger des fichiers à l'aide de l'API Data Protection. La protection basée sur les fichiers est une fonctionnalité puissante pour le stockage sécurisé de données en vrac. Mais il peut être exagéré de protéger une petite quantité d'informations, telle qu'une clé ou un mot de passe. Pour ces types d'articles, le trousseau est la solution recommandée.
Le trousseau est un endroit idéal pour stocker de petites quantités d'informations telles que des chaînes sensibles et des identifiants qui persistent même lorsque l'utilisateur supprime l'application. Un exemple pourrait être un jeton de périphérique ou de session que votre serveur renvoie à l'application lors de l'inscription. Que vous appeliez cela une chaîne secrète ou un jeton unique, le trousseau fait référence à tous ces éléments en tant que mots de passe.
Il existe quelques bibliothèques tierces populaires pour les services de trousseau, telles que Strongbox (Swift) et SSKeychain (Objective-C). Ou, si vous souhaitez contrôler totalement votre propre code, vous pouvez utiliser directement l'API Keychain Services, qui est une API C..
Je vais expliquer brièvement comment fonctionne le trousseau. Vous pouvez considérer le trousseau comme une base de données typique dans laquelle vous exécutez des requêtes sur une table. Les fonctions de l’API de trousseau nécessitent toutes une CFDictionary
objet qui contient les attributs de la requête.
Chaque entrée du trousseau a un nom de service. Le nom du service est un identifiant: a clé pour n'importe quoi valeur vous souhaitez stocker ou récupérer dans le trousseau. Pour autoriser le stockage d'un élément de trousseau pour un utilisateur spécifique uniquement, vous souhaiterez souvent spécifier un nom de compte..
Comme chaque fonction de trousseau utilise un dictionnaire similaire avec plusieurs des mêmes paramètres pour créer une requête, vous pouvez éviter le code en double en créant une fonction d'assistance qui renvoie ce dictionnaire de requête..
import Security //… class func passwordQuery (service: String, compte: String) -> Dictionnairelet dictionary = [kSecClass as String: kSecClassGenericPassword, kSecAttrAccount as String: compte, kSecAttrService as: service, kSecAttrAccessible as String: kSecAttrService as String
Ce code configure la requête dictionnaire
avec vos noms de compte et de service et dit au trousseau que nous allons stocker un mot de passe.
De la même manière que vous pouvez définir le niveau de protection pour des fichiers individuels (comme discuté dans l'article précédent), vous pouvez également définir les niveaux de protection pour votre élément de trousseau à l'aide de la commande kSecAttrAccessible
clé.
le SecItemAdd ()
fonction ajoute des données au trousseau. Cette fonction prend un Les données
objet, ce qui le rend polyvalent pour stocker de nombreux types d'objets. En utilisant la fonction de requête de mot de passe que nous avons créée ci-dessus, stockons une chaîne dans le trousseau. Pour ce faire, il suffit de convertir le Chaîne
à Les données
.
@discardableResult class func setPassword (_ mot_de_passe: Chaîne, service: Chaîne, compte: Chaîne) -> Bool statut var: OSStatus = -1 if! (service.isEmpty) &&! (compte.isEmpty) deletePassword (service: service , compte: compte) // supprime le mot de passe si une chaîne vide est transmise. Peut changer pour passer nil à supprimer mot de passe, etc. kSecValueData as String] = statut de dataFromString = SecItemAdd (dictionnaire comme CFDictionary, nil) return status == errSecSuccess
Pour éviter les insertions en double, le code ci-dessus supprime d'abord l'entrée précédente, le cas échéant. Écrivons cette fonction maintenant. Ceci est accompli en utilisant le SecItemDelete ()
une fonction.
@discardableResult class func deletePassword (service: String, compte: String) -> Bool statut var: OSStatus = -1 if! (service.isEmpty) &&! (compte.isEmpty) let dictionary = passwordQuery (service: service, compte : compte) status = SecItemDelete (dictionnaire en tant que CFDictionary); retourne le statut == errSecSuccess
Ensuite, pour récupérer une entrée du trousseau, utilisez le bouton SecItemCopyMatching ()
une fonction. Il va retourner un AnyObject
qui correspond à votre requête.
class func password (service: String, compte: String) -> String // renvoie une chaîne vide si non trouvé, peut renvoyer un statut optionnel: OSStatus = -1 var resultString = "" if! (service.isEmpty) &&! (account.isEmpty) var passwordData: AnyObject? var dictionary = passwordQuery (service: service, compte: compte) dictionnaire [kSecReturnData en tant que chaîne] = kCFBooleanTrue dictionnaire [kSecMatchLimit en tant que chaîne] = kSecMatchLimitOne status = SecItemCopyMatching (dictionnaire en tant que CFDictionary, & mot de passe) si status == errSecSalite comme? Data resultString = String (data: retrievedData, encoding: String.Encoding.utf8)! return resultString
Dans ce code, nous définissons la kSecReturnData
paramètre à kCFBooleanTrue
. kSecReturnData
signifie que les données réelles de l'article seront retournées. Une autre option pourrait être de retourner les attributs (kSecReturnAttributes
) de l'article. La clé prend un CFBooléen
type qui contient les constantes kCFBooleanTrue
ou kCFBooleanFalse
. Nous mettons kSecMatchLimit
à kSecMatchLimitOne
de sorte que seul le premier élément trouvé dans le trousseau sera renvoyé, par opposition à un nombre illimité de résultats.
Le trousseau est également l'emplacement recommandé pour stocker des objets de clé publique et privée, par exemple, si votre application fonctionne avec et doit stocker EC ou RSA. SecKey
objets.
La principale différence est qu'au lieu de demander au trousseau de stocker un mot de passe, nous pouvons lui demander de stocker une clé. En fait, nous pouvons être spécifiques en définissant les types de clés stockées, telles que celles-ci, qu'elles soient publiques ou privées. Tout ce qui reste à faire est d’adapter la fonction d’aide à la requête au type de clé souhaité..
Les clés sont généralement identifiées à l'aide d'une balise de domaine inverse telle que com.mydomain.mykey au lieu des noms de service et de compte (étant donné que les clés publiques sont partagées ouvertement entre différentes sociétés ou entités). Nous allons prendre les chaînes de service et de compte et les convertir en balises Les données
objet. Par exemple, le code ci-dessus s’adapte pour stocker un fichier RSA Private SecKey
ressemblerait à ceci:
class func keyQuery (service: String, compte: String) -> Dictionnairelet tagString = "com.mydomain." + service + "." + compte laisser tag = tagString.data (en utilisant: .utf8)! // stockez-le sous forme de chaîne. ] return dictionary @discardableResult class func setKey (_ clé: SecKey, service: String, compte: String) -> Bool statut var: OSStatus = -1 if! (service.isEmpty) &&! (account.isEmpty) deleteKey (service: service, compte: compte) var dictionary = keyQuery dictionnaire [kSecValueRef as String] = clé status = SecItemAdd (dictionnaire comme CFDictionary, nil); return status == errSecSuccess @discardableResult class func deleteKey (service: String, compte: String) -> Bool statut var: OSStatus = -1 if! (service.isEmpty) &&! (account.isEmpty) let dictionary = keyQuery (service: service, compte: compte) status = SecItemDelete (dictionnaire en tant que CFDictionary); return status == errSecSuccess clé de fonction de classe (service: Chaîne, compte: Chaîne) -> SecKey? var item: CFTypeRef? if! (service.isEmpty) &&! (account.isEmpty) dictionnaire de clé = dictionnaire de clés (service: service, compte: compte) dictionnaire [kSecReturnRef as String] = kCFBooleanTrue dictionnaire [kSecMatchLimit en tant que String] = kSecMatchLimitOnSite &article); retourner l'article comme! SecKey?
Articles sécurisés avec le kSecAttrAccessibleWhen déverrouillé
les drapeaux ne sont déverrouillés que lorsque le périphérique est déverrouillé, mais cela dépend du fait que l'utilisateur ait un code d'authentification ou un Touch ID configuré en premier lieu.
le mot de passe d'application
Les informations d'identification permettent de sécuriser les éléments du trousseau à l'aide d'un mot de passe supplémentaire. De cette façon, si l'utilisateur n'a pas de code de passe ou de Touch ID configuré, les éléments seront toujours sécurisés et une couche de sécurité supplémentaire sera ajoutée s'ils disposent d'un code d'authentification..
À titre d'exemple, après l'authentification de votre application auprès de votre serveur par votre application, celui-ci peut renvoyer le mot de passe via HTTPS requis pour déverrouiller l'élément de trousseau. C'est le moyen préféré de fournir ce mot de passe supplémentaire. Le codage en dur d'un mot de passe dans le binaire n'est pas recommandé.
Un autre scénario pourrait consister à récupérer le mot de passe supplémentaire à partir d'un mot de passe fourni par l'utilisateur dans votre application. Cependant, cela nécessite plus de travail pour sécuriser correctement (avec PBKDF2). Nous verrons comment sécuriser les mots de passe fournis par les utilisateurs dans le prochain tutoriel..
Le mot de passe de l'application peut également être utilisé pour stocker une clé confidentielle, par exemple une clé que vous ne souhaitez pas voir exposée uniquement parce que l'utilisateur n'a pas encore défini de code d'accès..
mot de passe d'application
est disponible uniquement sur iOS 9 et supérieur, vous aurez donc besoin d’une solution de secours qui n’utilise pas mot de passe d'application
si vous ciblez des versions iOS inférieures. Pour utiliser le code, vous devez ajouter les éléments suivants dans votre en-tête de pontage:
#importation#importation
Le code suivant définit un mot de passe pour la requête dictionnaire
.
if #available (iOS 9.0, *) // Utilisez ceci à la place de kSecAttrAccessible pour l'erreur de requête var: Unmanaged? laisserSupporterAvant .Encoding.utf8)! localAuthenticationContext.setCredential (theApplicationPassword, type: LACredentialType.applicationPassword) dictionnaire [kSecUseAuthenticationContext as String] = localAuthenticationContext
Notez que nous avons mis kSecAttrAccessControl
sur le dictionnaire
. Ceci est utilisé à la place de kSecAttrAccessible
, qui était précédemment défini dans notre passwordQuery
méthode. Si vous essayez d'utiliser les deux, vous obtiendrez un OSStatus
-50
Erreur.
À partir de iOS 8, vous pouvez stocker des données dans le trousseau, qui ne sont accessibles que lorsque l'utilisateur s'est authentifié avec succès sur l'appareil avec Touch ID ou un code d'accès. Lorsqu'il est temps pour l'utilisateur de s'authentifier, Touch ID aura la priorité s'il est configuré, sinon l'écran de mot de passe est présenté. L’enregistrement dans le trousseau ne nécessite pas l’authentification de l’utilisateur, mais la récupération des données.
Vous pouvez définir un élément de trousseau de manière à exiger l’authentification de l’utilisateur en fournissant un objet de contrôle d’accès défini sur .userPresence
. Si aucun mot de passe n’est défini, toute demande de trousseau avec .userPresence
va échouer.
si #available (iOS 8.0, *) let accessControl = SecAccessControlCreateWithFlags (kCFAllocatorDefault, kSecAttrAccessibleWhenUnlockedThisDeviceOnly, .userPresence, nil) si accessControl! = nil dictionnaire
Cette fonctionnalité est utile lorsque vous souhaitez vous assurer que votre application est utilisée par la bonne personne. Par exemple, il serait important que l'utilisateur s'authentifie avant de pouvoir se connecter à une application bancaire. Cela protégera les utilisateurs qui ont laissé leur appareil déverrouillé et empêchera l'accès à la banque..
De plus, si vous n'avez pas de composant côté serveur dans votre application, vous pouvez utiliser cette fonctionnalité pour effectuer une authentification côté périphérique..
Pour la requête de chargement, vous pouvez indiquer pourquoi l'utilisateur doit s'authentifier..
dictionary [kSecUseOperationPrompt as String] = "Authentifiez-vous pour récupérer x"
Lors de la récupération des données avec SecItemCopyMatching ()
, la fonction affiche l'interface utilisateur d'authentification et attend que l'utilisateur utilise Touch ID ou saisisse le code d'authentification. Puisque SecItemCopyMatching ()
bloquera jusqu'à ce que l'utilisateur ait fini de s'authentifier; vous devrez appeler la fonction depuis un thread en arrière-plan afin de permettre au thread principal de l'interface utilisateur de rester réactif..
DispatchQueue.global (). Async status = SecItemCopyMatching (dictionnaire comme CFDictionary, & passwordData) si status == errSecSuccess si laissez retrievedData = passwordData comme? Data DispatchQueue.main.async //… faire le reste du travail sur le fil principal
Encore une fois, nous mettons kSecAttrAccessControl
sur la requête dictionnaire
. Vous devrez enlever kSecAttrAccessible
, qui était précédemment défini dans notre passwordQuery
méthode. L’utilisation simultanée des deux entraînera une OSStatus
-50 erreur.
Dans cet article, vous avez visité l’API des services de trousseau. Avec l’API de protection des données que nous avons vue dans le post précédent, l’utilisation de cette bibliothèque fait partie des meilleures pratiques pour la sécurisation des données..
Cependant, si l'utilisateur n'a pas de code d'authentification ou de Touch ID sur le périphérique, il n'y a pas de cryptage pour l'une ou l'autre structure. Les API Key Protection et Data Protection étant généralement utilisées par les applications iOS, elles sont parfois la cible d'attaques, notamment de périphériques jailbreakés. Si votre application ne fonctionne pas avec des informations très sensibles, cela peut constituer un risque acceptable. Tandis qu'iOS met constamment à jour la sécurité des frameworks, nous sommes toujours à la merci de l'utilisateur mettant à jour le système d'exploitation, en utilisant un code d'accès fort, sans jailbreaker leur appareil..
Le trousseau est destiné aux plus petites données et vous pouvez avoir une plus grande quantité de données à sécuriser indépendante de l'authentification du périphérique. Bien que les mises à jour iOS ajoutent de nouvelles fonctionnalités intéressantes, telles que le mot de passe de l'application, vous devrez peut-être prendre en charge des versions iOS inférieures et bénéficier d'une sécurité renforcée. Pour certaines de ces raisons, vous voudrez peut-être chiffrer les données vous-même..
Le dernier article de cette série traite du cryptage des données vous-même à l'aide du cryptage AES. Bien que l'approche soit plus avancée, elle vous permet de contrôler totalement le mode et le moment du cryptage de vos données.
Alors restez à l'écoute. Et en attendant, consultez quelques-uns de nos autres articles sur le développement d'applications iOS!