Security in iOS applications

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 that 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 a 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 watch and manage data from applications installed on the iPhone. Can easily getUserDefaults 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 number of available money, 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 {returnnil}
	    return String(data: data, encoding: String.Encoding.utf8)
	}

Let's write a small check on 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 keep 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. There are also a lot of open libraries on the Internet that help to use the 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 login 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 complicated, 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 records the username and password and saves them to Keychain as an encrypted string.

Let's see what happens inside:

- The login and password are written to the salt as a string
- sha256 fills the SHA-2 hash
- HKDF is the key generation function ( KDF ) based on the message authentication code ( HMAC )

We created the salt variable for that to complicate the task of 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 sensitive data in UserDefaults and why Keychain is needed.

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.

Also popular now: