Dans cet article, nous examinerons les utilisations avancées du chiffrement pour les données utilisateur dans les applications iOS. Commençons par un examen approfondi du chiffrement AES, puis examinons quelques exemples de mise en œuvre du chiffrement AES dans Swift..
Dans le dernier message, vous avez appris à stocker des données à l’aide du trousseau, ce qui est utile pour de petites informations telles que des clés, des mots de passe et des certificats..
Si vous stockez une grande quantité de données personnalisées que vous souhaitez rendre disponible uniquement après l'authentification de l'utilisateur ou du périphérique, il est préférable de chiffrer les données à l'aide d'une structure de chiffrement. Par exemple, vous pouvez avoir une application qui peut archiver des messages de discussion privés enregistrés par l'utilisateur ou des photos privées prises par l'utilisateur, ou qui peut stocker les informations financières de l'utilisateur. Dans ces cas, vous voudrez probablement utiliser le cryptage.
Il existe deux flux communs dans les applications pour chiffrer et déchiffrer les données à partir d'applications iOS. Un écran de mot de passe est présenté à l'utilisateur ou l'application est authentifiée auprès d'un serveur qui renvoie une clé pour déchiffrer les données..
Ce n'est jamais une bonne idée de réinventer la roue en matière de cryptage. Par conséquent, nous allons utiliser la norme AES fournie par la bibliothèque iOS Common Crypto..
AES est une norme qui chiffre les données à l'aide d'une clé. La même clé utilisée pour chiffrer les données est utilisée pour déchiffrer les données. Il existe différentes tailles de clé et AES256 (256 bits) est la longueur recommandée pour les données sensibles..
RNCryptor est un wrapper de chiffrement populaire pour iOS qui prend en charge AES. RNCryptor est un excellent choix car il vous permet de fonctionner très rapidement sans vous soucier des détails sous-jacents. Il est également open source afin que les chercheurs en sécurité puissent analyser et auditer le code..
D'autre part, si votre application traite des informations très sensibles et que vous pensez que votre application sera ciblée et fissurée, vous voudrez peut-être écrire votre propre solution. La raison en est que, lorsque de nombreuses applications utilisent le même code, cela peut faciliter le travail du pirate informatique en leur permettant d'écrire une application de crack qui trouve des modèles communs dans le code et leur applique des correctifs..
Gardez toutefois à l'esprit que la rédaction de votre propre solution ne ralentit qu'un attaquant et empêche des attaques automatisées. La protection que vous obtenez de votre propre implémentation est qu'un pirate informatique devra passer du temps et se consacrer uniquement à la résolution de son problème.
Que vous choisissiez une solution tierce ou que vous choisissiez la vôtre, il est important de connaître le fonctionnement des systèmes de cryptage. De cette façon, vous pouvez décider si un framework particulier que vous souhaitez utiliser est vraiment sécurisé. Par conséquent, le reste de ce didacticiel se concentrera sur la rédaction de votre propre solution personnalisée. Avec les connaissances acquises grâce à ce tutoriel, vous serez en mesure de savoir si vous utilisez un framework particulier en toute sécurité..
Nous allons commencer par la création d'une clé secrète qui sera utilisée pour chiffrer vos données..
Une erreur très courante dans le cryptage AES consiste à utiliser directement le mot de passe d'un utilisateur en tant que clé de cryptage. Que se passe-t-il si l'utilisateur décide d'utiliser un mot de passe commun ou faible? Comment obliger les utilisateurs à utiliser une clé aléatoire et suffisamment forte (suffisamment d'entropie) pour le chiffrement, puis à s'en souvenir?
La solution est étirement de la clé. L'extension de clé tire la clé d'un mot de passe en la hachant plusieurs fois avec un sel. Le sel est juste une séquence de données aléatoires, et c'est une erreur commune d'omettre ce sel - le sel donne à la clé son entropie vitale, et sans le sel, la même clé serait dérivée si le même mot de passe était utilisé par quelqu'un autre.
Sans le sel, un dictionnaire de mots pourrait être utilisé pour déduire des clés communes, qui pourraient ensuite être utilisées pour attaquer les données de l'utilisateur. Cela s'appelle une "attaque par dictionnaire". Les tables avec des clés communes correspondant à des mots de passe non salés sont utilisées à cette fin. On les appelle "tables arc-en-ciel".
Un autre piège lors de la création d'un sel est d'utiliser une fonction de génération de nombre aléatoire qui n'a pas été conçue pour la sécurité. Un exemple est le rand()
fonction en C, accessible depuis Swift. Cette sortie peut devenir très prévisible!
Pour créer un sel sécurisé, nous allons utiliser la fonction SecRandomCopyBytes
créer des octets aléatoires cryptographiquement sécurisés, c'est-à-dire des nombres difficiles à prédire.
Pour utiliser le code, vous devez ajouter les éléments suivants dans votre en-tête de pontage:#importation
Voici le début du code qui crée un sel. Nous ajouterons à ce code au fur et à mesure:
var salt = Données (nombre: 8) salt.withUnsafeMutableBytes (saltBytes: UnsafeMutablePointer) -> Annuler dans let saltStatus = SecRandomCopyBytes (kSecRandomDefault, salt.count, saltBytes) //…
Nous sommes maintenant prêts à faire des étirements de touches. Heureusement, nous disposons déjà d'une fonction pour effectuer l'étirement actuel: la fonction de dérivation de clé par mot de passe (PBKDF2). PBKDF2 exécute une fonction plusieurs fois pour dériver la clé; L'augmentation du nombre d'itérations augmente le temps nécessaire pour utiliser un jeu de clés lors d'une attaque par force brute. Il est recommandé d’utiliser PBKDF2 pour générer votre clé..
var setupSuccess = true var clé = Data (répétant: 0, nombre: kCCKeySizeAES256) var salt = Données (nombre: 8) salt.withUnsafeMutableBytes (saltBytes: UnsafeMutablePointer) -> Annuler dans let saltStatus = SecRandomCopyBytes (kSecRandomDefault, salt.count, saltBytes) si saltStatus == errSecSuccess let passwordData = password.data (en utilisant: String.Encoding.utf8)! key.withUnsafeMutableBytes (keyBytes: UnsafeMutablePointer ) dans let derivationStatus = CCKeyDerivationPBKDF (CCPBKDFAlgorithm (kCCPBKDF2), mot de passe, passwordData. else setupSuccess = false
Vous vous demandez peut-être maintenant dans quels cas vous ne souhaitez pas demander aux utilisateurs de fournir un mot de passe dans votre application. Peut-être qu'ils s'authentifient déjà avec un système d'authentification unique. Dans ce cas, demandez à votre serveur de générer une clé AES 256 bits (32 octets) à l'aide d'un générateur sécurisé. La clé doit être différente pour différents utilisateurs ou appareils. Lors de l'authentification sur votre serveur, vous pouvez transmettre au serveur un ID de périphérique ou d'utilisateur via une connexion sécurisée, et renvoyer la clé correspondante..
Ce schéma a une différence majeure. Si la clé provient du serveur, l'entité qui contrôle ce serveur a la capacité de pouvoir lire les données cryptées si le périphérique ou les données ont déjà été obtenus. Il est également possible que la clé fuie ou soit exposée plus tard..
D'autre part, si la clé est dérivée de quelque chose que seul l'utilisateur connaît, le mot de passe de l'utilisateur, alors seul l'utilisateur peut déchiffrer ces données. Si vous protégez des informations telles que des données financières privées, seul l'utilisateur doit pouvoir les déverrouiller. Si de toute façon ces informations sont connues de l'entité, il peut être acceptable que le serveur déverrouille le contenu via une clé côté serveur..
Maintenant que nous avons une clé, chiffrons certaines données. Il existe différents modes de cryptage, mais nous utiliserons le mode recommandé: chaînage de blocs de chiffrement (CBC). Cela fonctionne sur nos données un bloc à la fois.
Un piège courant avec CBC est le fait que chaque bloc de données non crypté suivant est traité avec XOR avec le bloc crypté précédent pour renforcer le cryptage. Le problème ici est que le premier bloc n'est jamais aussi unique que tous les autres. Si un message à chiffrer commençait de la même façon qu'un autre message à chiffrer, la sortie chiffrée initiale serait la même chose, ce qui donnerait à l'attaquant un indice lui permettant de déterminer le contenu du message..
Pour contourner cette faiblesse potentielle, nous allons commencer les données à sauvegarder avec ce que l’on appelle un vecteur d’initialisation (IV): un bloc d’octets aléatoires. Le IV sera traité par XOR avec le premier bloc de données utilisateur, et comme chaque bloc dépend de tous les blocs traités jusque-là, le message entier sera crypté de manière unique, même s'il contient les mêmes données qu'un autre. message. En d'autres termes, des messages identiques cryptés avec la même clé ne produiront pas des résultats identiques. Ainsi, bien que les sels et les solutions intraveineuses soient considérés comme publics, ils ne doivent pas être séquentiels ni réutilisés.
Nous allons utiliser le même sécurisé SecRandomCopyBytes
fonction pour créer l'IV.
var iv = Data.init (count: kCCBlockSizeAES128) iv.withUnsafeMutableBytes (ivBytes: UnsafeMutablePointer) dans let ivStatus = SecRandomCopyBytes (kSecRandomDefault, kCCBlockSizeAES128, ivBytes) si ivStatus! = errSecSuccess setupSuccess = false
Pour compléter notre exemple, nous allons utiliser le CCCrypt
fonctionner avec soit kCCEncrypt
ou kCCDecrypt
. Comme nous utilisons un chiffrement de bloc, si le message ne tient pas bien dans un multiple de la taille du bloc, nous devrons indiquer à la fonction d'ajouter automatiquement un remplissage à la fin..
Comme d'habitude en cryptage, il est préférable de respecter les normes établies. Dans ce cas, la norme PKCS7 définit comment compiler les données. Nous disons à notre fonction de cryptage d’utiliser cette norme en fournissant le KCCOptionPKCS7Padding
option. En résumé, voici le code complet pour chiffrer et déchiffrer une chaîne.
class func encryptData (_ clearTextData: Données, avec mot de passe Mot de passe: Chaîne) -> Dictionnairevar setupSuccess = true var outDictionary = Dictionnaire .init () clé var = Données (répétant: 0, nombre: kCCKeySizeAES256) var sel = Données (nombre: 8) salt.withUnsafeMutableBytes (saltBytes: UnsafeMutablePointer ) -> Annuler dans let saltStatus = SecRandomCopyBytes (kSecRandomDefault, salt.count, saltBytes) si saltStatus == errSecSuccess let passwordData = password.data (en utilisant: String.Encoding.utf8)! key.withUnsafeMutableBytes (keyBytes: UnsafeMutablePointer ) dans let derivationStatus = CCKeyDerivationPBKDF (CCPBKDFAlgorithm (kCCPBKDF2), mot de passe, passwordData. else setupSuccess = false var iv = Data.init (nombre: kCCBlockSizeAES128) iv.withUnsafeMutableBytes (ivBytes: UnsafeMutablePointer ) dans let ivStatus = SecRandomCopyBytes (kSecRandomDefault, kCCBlockSizeAES128, ivBytes) si ivStatus! = errSecSuccess = nombre: taille) let cryptStatus = iv.withUnsafeBytes ivBytes in encrypted.withUnsafeMutableBytes encryptedBytes in clearTextData.withUnsafeBytes clearTextData.with (s) (s). key.count, ivBytes, clearTextBytes, clearTextData.count, encryptedBytes, size, & numberOfBytesEncrypted) if cryptStatus == Int32 (kCCSuccess) crypteded.count = numberOfBytesEncrypted) si cryptStatus == Int32 (kCCSuccess) encrypted.count = numberOfBytesEncrypted) " = Encrypted = iv outDictionary ["EncryptionSalt"] = salt return outDictionary;
Et voici le code de décryptage:
class func decryp (fromDictionary Dictionary: Dictionnaire, withPassword password: String) -> Données var setupSuccess = true let encrypted = dictionnaire ["EncryptionData"] let iv = dictionnaire ["EncryptionIV"] let salt = dictionnaire ["EncryptionSalt"] var clé = Données (répétition: 0, nombre : kCCKeySizeAES256) salt? .withUnsafeBytes (saltBytes: UnsafePointer ) -> Annuler dans let passwordData = password.data (using: String.Encoding.utf8)! key.withUnsafeMutableBytes (keyBytes: UnsafeMutablePointer ) dans let derivationStatus = CCKeyDerivationPBKDF (CCPBKDFAlgorithm (kCCPBKDF2), mot de passe, passwordData.count, saltBytes, salt! .count, CCPeudoRandomAlgorithm var decryptSuccess = false let size = (crypté? .count)! + kCCBlockSizeAES128 var clearTextData = Data.init (nombre: taille) if (setupSuccess) var numberOfBytesDecrypted: size_t = 0 keyBytes in CCCrypt (CCOperation (kCCDecrypt), CCAlgorithm (kCCAlgorithmAES128), CCOptions (kCCOptionPKCS7Padding), keyBytes, key.count, ivBytes, encryptedBytes, (encrypted? .count), clear, ! == Int32 (kCCSuccess) clearTextData.count = numberOfBytesDecrypted decryptSuccess = true Renvoie decryptSuccess? clearTextData: Data.init (count: 0)
Enfin, voici un test pour vous assurer que les données sont correctement déchiffrées après le chiffrement:
class func encryptionTest () let clearTextData = "du texte clair à chiffrer" .data (en utilisant: String.Encoding.utf8)! let dictionary = encryptData (clearTextData, withPassword: "123456") let decrypted = decryp (fromDictionary: dictionnaire, withPassword: "123456") let decryptedString = String (données: décryptées, encodage: String.Encoding.utf8) print ("décryptées clear cleartext> résultat - ", decryptedString ??" Erreur: impossible de convertir les données en chaîne ")
Dans notre exemple, nous emballons toutes les informations nécessaires et les renvoyons sous forme de fichier. dictionnaire
afin que toutes les pièces puissent ensuite être utilisées pour décrypter avec succès les données. Il vous suffit de stocker l'IV et le sel, soit dans le trousseau, soit sur votre serveur..
Ceci termine la série en trois parties sur la sécurisation des données au repos. Nous avons vu comment stocker correctement les mots de passe, les informations sensibles et les grandes quantités de données utilisateur. Ces techniques constituent la base de la protection des informations utilisateur stockées dans votre application..
C'est un risque énorme que le périphérique d'un utilisateur soit perdu ou volé, en particulier lors de récents exploits visant à accéder à un périphérique verrouillé. Bien que de nombreuses vulnérabilités système soient corrigées par une mise à jour logicielle, le périphérique lui-même est aussi sécurisé que le code d'authentification et la version de l'utilisateur iOS. Par conséquent, il appartient au développeur de chaque application de protéger efficacement les données sensibles stockées..
Tous les sujets abordés jusqu'à présent utilisent les frameworks Apple. Je vais laisser une idée avec vous pour réfléchir. Que se passe-t-il lorsque la bibliothèque de chiffrement d'Apple est attaquée??
Lorsqu'une architecture de sécurité couramment utilisée est compromise, toutes les applications qui en dépendent sont également compromises. N'importe quelle bibliothèque liée dynamiquement iOS, en particulier sur les appareils jailbreakés, peut être corrigée et remplacée par des logiciels malveillants.
Cependant, une bibliothèque statique fournie avec le fichier binaire de votre application est protégée contre ce type d'attaque, car si vous essayez de la corriger, vous finissez par modifier le fichier binaire de l'application. Cela cassera la signature de code de l'application, l'empêchant d'être lancée. Si vous avez importé et utilisé, par exemple, OpenSSL pour votre chiffrement, votre application ne serait pas vulnérable à une attaque généralisée des API Apple. Vous pouvez compiler OpenSSL vous-même et le lier statiquement à votre application..
Il y a donc toujours plus à apprendre et l'avenir de la sécurité des applications sur iOS évolue constamment. L'architecture de sécurité iOS prend encore en charge les périphériques cryptographiques et les cartes à puce! En terminant, vous connaissez maintenant les meilleures pratiques pour la sécurisation des données inactives; il vous appartient donc de les suivre.!
En attendant, consultez d'autres contenus sur le développement d'applications iOS et la sécurité des applications.