Good afternoon, Habr! I present to your attention the translation of an article about the basic fundamentals of confidential data security in iOS applications
“Application Security Musts for every iOS App” by Arlind Aliu.
Application security is one of the most important aspects of software development. Application users hope that the information they provide is reliably protected. Therefore, it is impossible to simply provide confidential information to anyone.
Fortunately, in this article we will discuss the mistakes that developers make in their applications, as well as how to fix them.
Continued under the cut.
Data storage in the wrong place
I conducted a study of several applications from the AppStore and many have the same error: confidential information is stored where it should not be.
If you store personal data in
UserDefaults , then you put it at risk.
UserDefaults are stored in a property list file, which is located inside the Settings folder in your application. Data is stored in the application without the slightest hint of encryption.
')
Having installed a third-party program on mac, such as iMazing, you can not even hack the phone, but immediately see all the
UserDefaults data from the application installed from the AppStore. Such programs allow you to view and manage data from applications installed on the iPhone. You can easily get
UserDefaults of any application.
This is the main reason why I decided to write an article - I found a bunch of applications in the AppStore that store data in
UserDefaults , such as: tokens, active and renewable subscriptions, the amount of money available, and so on. All this data can be easily obtained and used with malicious intent, ranging from managing paid subscriptions in the app and ending with hacking at the network level and worse.
And now about how to store data.
Remember,
UserDefaults should store only a small amount of information, such as settings inside the application, that is, data that is not confidential to the user.
Use Apple’s dedicated security services to store personal data. The Keychain API service allows you to store a certain amount of user data in an encrypted database. There you can store passwords and other important user data, such as credit card information, or even small important notes.
Also, there may be encrypted keys and certificates with which you work.
API service Keychain
Below is an example of how to save a user password in the Keychain.
class KeychainService { func save(_ password: String, for account: String) { let password = password.data(using: String.Encoding.utf8)! let query: [String: Any] = [kSecClass as String: kSecClassGenericPassword, kSecAttrAccount as String: account, kSecValueData as String: password] let status = SecItemAdd(query as CFDictionary, nil) guard status == errSecSuccess else { return print("save error") } }
The
kSecClass: kSecClassGenericPassword dictionary
part means that the information that needs to be encrypted is the password. Then we add a new password to the keychain by calling the
SecItemAdd method. Retrieving data from a bundle is similar to saving.
func retrivePassword(for account: String) -> String? { let query: [String: Any] = [kSecClass as String: kSecClassGenericPassword, kSecAttrAccount as String: account, kSecMatchLimit as String: kSecMatchLimitOne, kSecReturnData as String: kCFBooleanTrue] var retrivedData: AnyObject? = nil let _ = SecItemCopyMatching(query as CFDictionary, &retrivedData) guard let data = retrivedData as? Data else {return nil} return String(data: data, encoding: String.Encoding.utf8) }
Let's write a small check of the correctness of storing and retrieving data.
func testPaswordRetrive() { let password = "123456" let account = "User" keyChainService.save(password, for: account) XCTAssertEqual(keyChainService.retrivePassword(for: account), password) }
At first glance it may seem that the Keychain API is quite difficult to use, especially if you need to store more than one password, so I urge you to use the Facade pattern for this purpose. It allows you to save and modify data depending on the needs of the application.
If you want to learn more about this pattern, as well as how to create a simple wrapper for complex subsystems, then this
article will help you. Also on the Internet is full of open libraries that help to use Keychain API, for example,
SAMKeychain and
SwiftKeychainWrapper .
Password Preservation and Authorization
In my career as a developer, I am constantly confronted with the same problem. Developers either store passwords in the application, or create a request to the server, which directly sends the username and password.
If you store data in
UserDefault , then after reading the information from the first part of the article, you already understand how much you risk. Keeping passwords in Keychains will seriously increase the security level of your application, but again, before you save confidential information anywhere, you need to encrypt it beforehand.
Suppose a hacker can attack us through our network. This way he will get passwords in the form of a raw text. It’s better, of course, to hash all passwords.
Encryption of personal data
Hashing may seem too difficult, if you do everything yourself, so in this article we will use the library
CryptoSwift . It contains a lot of standard reliable encryption algorithms used in Swift.
Let's try to save and retrieve the password from the keychain using
CryptoSwift algorithms.
func saveEncryptedPassword(_ password: String, for account: String) { let salt = Array("salty".utf8) let key = try! HKDF(password: Array(password.utf8), salt: salt, variant: .sha256).calculate().toHexString() keychainService.save(key, for: account) }
The above function writes the username and password and saves them to Keychain as an encrypted string.
Let's see what's going on inside:
- The login and password are written to the salt variable as a string
- sha256 fills SHA-2 hash
- HKDF is a key generation function (
KDF ) based on the message authentication code (
HMAC )
We created a salt variable to make it harder for hackers. We could only encrypt the password, but in this case, the attacker may have a list of the most frequently used passwords, he will easily encrypt them and compare them with our encrypted password. Then find the password for a particular account is not difficult.
Now we can log in using our account and the generated key.
authManager.login(key, user)
Of course, the server needs to know what is encrypted in our salt variable. The backend will be able to compare keys using the same algorithm to identify the user.
Using this approach, you greatly increase the security of your application.
To complete
Never neglect the security of your application. In this article, we, first of all, figured out what the consequences may be when storing confidential data in
UserDefaults and what Keychain is for.
In the second part, we will talk about a more serious security level, encrypting data before storing it, and also discuss how to transfer information with personal data to the server correctly.