Chapter 15: Batten the Hatches with Security Services

iOS is likely the first platform most developers encounter that employs a true least-privilege security model. Most modern operating systems employ some kind of privilege separation, allowing different processes to run with different permissions, but it is almost always used in a very rough way. Most applications on UNIX, OS X, and Windows either run as the current user, or run as an administrative user that can do nearly anything. Attempts to segment privileges further, whether with Security Enhanced Linux (SELinux) or Windows User Account Control (UAC), have generally led developers to revolt. The most common questions about SELinux are not how to best develop for it, but how to turn it off.

With the Mac App Store, and particularly OS X 10.8, Apple has expanded some of iOS’s least privilege approach to the desktop. Time will tell if it’s successful.

Coming from these backgrounds, developers tend to be shocked when encountering the iOS security model. Rather than ensure maximal flexibility, Apple’s approach has been to give developers the fewest privileges it can and see what software developers are incapable of making with those privileges. Then Apple provides the fewest additional privileges that allow the kinds of software it wants for the platform. This approach can be very restrictive on developers, but it’s also kept iOS quite stable and free of malware. Apple is unlikely to change its stance on this approach, so understanding and dealing with the security model is critical to iOS development.

This chapter shows the way around the iOS security model, dives into the numerous security services that iOS offers, and provides the fundamentals you need to really understand Apple’s security documentation. Along the way, you’ll gain a deeper understanding of how certificates and encryption work in practice, so that you can leverage these features to really improve the security of your products.

The code for this chapter is available in the online sample code. A clear-cut project called FileExplorer is also available that enables you to investigate the public parts of the file system.

Understanding the iOS Sandbox

The heart of the iOS security model is the sandbox. When an application is installed, it’s given its own home directory in the file system, readable only by that application. This makes it difficult to share information between applications, but also makes it difficult for malicious or poorly written software to read or modify your data.

Applications are not separated from each other using standard UNIX file permissions. All applications run as the same user ID (501, mobile). Calling stat on another application’s home directory fails, however, because of operating system restrictions. Similar restrictions prevent your application from reading /var/log while allowing access to /System/Library/Frameworks.

Within your sandbox, there are four important top-level directories: your .app bundle, Documents, Library, and tmp. Although you can create new directories within your sandbox, it’s not well defined how iTunes will deal with them. I recommend keeping everything in one of these top-level directories. You can always create subdirectories under Library if you need more organization.

Your .app bundle is the package built by Xcode and copied to the device. Everything within it is digitally signed, so you can’t modify it. In particular, this includes your Resources directory. If you want to modify files that you install as part of your bundle, you’ll need to copy them elsewhere first, usually somewhere in Library.

The Documents directory is where you store user-visible data, particularly files like word-processing documents or drawings that the user assigns a filename. These files can be made available to the desktop through file sharing if UIFileSharingEnabled is turned on in Info.plist.

The Library directory stores files that shouldn’t be directly visible to users. Most files should go into Library/Application Support. These files are backed up, so if you want to avoid that, you can attach the attribute NSURLIsExcluedFromBackupKey to the files using the method NSURL setResourceValue:forKey:error:.

The addition of setResourceValues:error: to NSURL was a very strange move by Apple in iOS 5. NSURL represents a URL, not the resource at that URL. This method makes more sense as part of NSFileManager. I’ve opened a radar on this, and hopefully it will become more consistent.

The Library/Caches directory is special because it isn’t backed up, but it is preserved between application upgrades. This is where you want to put most things you don’t want copied to the desktop.

The tmp directory is special because it’s neither backed up nor preserved between application upgrades, which makes it ideal for temporary files, as the name implies. The system may also delete files in a program’s tmp directory when that program isn’t running.

When considering the security of the user’s data, backups are an important consideration. Users may choose whether to encrypt the iTunes backup with a password. If there is data that shouldn’t be stored unencrypted on the desktop machine, you store it in the keychain (see the “Using Keychains” section, later in this chapter). iTunes backs up the keychain only if backup encryption is enabled.

If you have information you don’t want the user to have access to, you can store it in the keychain or in Library/Caches because these are not backed up. This is weak protection, however, because the user can always jailbreak the phone to read any file or the keychain. There is no particular way to prevent the owner of a device from reading data on that device. iOS security is about protecting the user from attackers, not about protecting the application from the user.

Securing Network Communications

The greatest risk to most systems is their network communication. Attackers don’t need access to the device, only to the device’s network. The most dangerous areas are generally coffee shops, airports, and other public Wi-Fi networks. It’s your responsibility to make sure that the user’s information is safe, even on hostile networks.

The first and easiest solution is to use Hypertext Transfer Protocol Secure (HTTPS) for your network communication. Most iOS network APIs automatically handle HTTPS, and the protocol eliminates many of the easiest attacks. In the simplest deployment, you put a self-signed certificate on the web server, turn on HTTPS, and configure NSURLConnection to accept untrusted certificates, as discussed shortly. This is still vulnerable to several kinds of attacks, but it’s easy to deploy and addresses the most basic attacks.

How Certificates Work

Hopefully, you have encountered public-private key infrastructure (PKI) systems before. This section gives a quick overview of the technology, and then discusses how it affects the security of your application.

Asymmetric cryptography is based on the mathematical fact that you can find two very large numbers (call them A and B) that are related in such a way that anything encrypted with one can be decrypted with the other, and vice versa. Key A cannot decrypt things that key A encrypted, nor can key B decrypt things that key B encrypted. Each can decrypt only the other’s encrypted data (called ciphertext). There is no real difference between key A and key B, but for the purposes of public key cryptography, one is called the public key, which generally everyone is allowed to know, and the other is called the private key, which is secret.You can use a public key to encrypt data such that only a computer with the private key can decrypt it. This is an important property that is used repeatedly in public key systems. If you want to prove that some entity (person or machine) has the private key, you make up a random number, encrypt it with the entity’s public key, and send it. That entity decrypts the message with the entity’s private key and sends it back to you. Because only the private key could have decrypted the message, the entity you’re communicating with must have the private key.

This property also allows you to digitally sign data. Given some data, you first hash it with some well-known hashing algorithm and then encrypt it with your private key. The resulting ciphertext is the signature. To validate the signature, you hash the data again with the same algorithm, decrypt the signature using the public key, and compare the hashes. If they match, you know the signature was created by some entity that had access to the private key.

Just because an entity has access to the private key doesn’t prove it’s who it says it is. You need to ask two questions. First, how well is the private key protected? Anyone with access to the private key can forge a signature. Second, how do you know that the public key you have is related to the entity you care about? If I approach you on the street and hand you a business card that says I’m the President of the United States, it hardly proves anything. I’m the one who handed you the business card. Similarly, if a server presents you with a public key that claims to be for www.apple.com, why should you believe it? This is where a certificate chain comes in, and it’s relevant to both questions.

A certificate is made up of a public key, some metadata about the certificate (more on that later), and a collection of signatures from other certificates. In most cases, there is a short chain of certificates, each signing the one below it. In very rare cases, there may be multiple signatures on one certificate. An example of a certificate chain is shown in Figure 15-1.

9781118449974-fg1501.tif

Figure 15-1 The certificate chain for daw.apple.com

In this example, the server daw.apple.com presents a certificate that includes its own public key, signed by an intermediate certificate from VeriSign, which is signed by a root certificate from VeriSign. Mathematically, you can determine that the controllers of each of these certificates did sign the next certificate in the chain, but why would you trust any of them? You trust them because Apple trusts the VeriSign root certificate, which has signed the intermediate certificate, which has signed the Apple certificate. Apple ships the VeriSign root certificate in the trusted root store of every iOS device, along with more than a hundred other trusted root certificates. The trusted root store is a list of certificates that is treated as explicitly trustworthy. Explicitly trusted certificates are called anchors. You can set your own anchors if don’t want to trust Apple’s list.

This brings you to the much-misused term self-signed certificate. For cryptographic reasons, every certificate includes a signature from itself. A certificate that has only this signature is called self-signed. Often, when people talk about a self-signed certificate, they mean a certificate that you shouldn’t trust. But the VeriSign root certificate is a self-signed certificate, and it’s one of the most trusted certificates in the world. Every root certificate, by definition, is a self-signed certificate. What’s the difference? It isn’t how many signatures a certificate has in its chain that matters, but how well all of the private keys in the chain are protected, and whether the identity of the owner has been authenticated.

If you generate your own self-signed certificate and protect the private key very well, then that’s more secure than a certificate that VeriSign issues you. In both cases, you’re dependent on protecting your private key, but in the latter case, you also have to worry about VeriSign protecting its private key. VeriSign spends a lot of money and effort doing that, but protecting two keys is always more risky than protecting just one of them.

This isn’t to say that commercial certificates from VeriSign, DigiTrust, and other providers are bad. But you don’t get a commercial certificate to improve the security of your system. You get one for convenience because the commercial certs are already in the root key store. But remember, you control the root key store in your own application. This leads to a surprising fact: There is no security reason to purchase a commercial certificate to secure your application’s network protocol to your own server.

Commercial certificates are valuable only for websites visited by browsers or other software you don’t control. Generating your own certificate and shipping the public key in your application is marginally more secure than using a commercial certificate. If you already have a commercial certificate for your server, using it for your application’s network protocol is somewhat more convenient; it’s just not more secure. This is not to say that it’s okay to trust random certificates (that is, turning off certificate validation). It’s to say that it’s slightly better to trust only your certificates than to trust commercial certificates.

Certificates can be corrupt or not, valid or not, and trusted or not. These are separate attributes that need to be understood individually. The first question is whether a certificate is corrupt. A certificate is corrupt if it doesn’t conform to the X.509 data format or if its signatures are incorrectly computed. A corrupt certificate should never be used for anything, and the iOS certificate function generally rejects them automatically.

X.509 refers to the data format specification and semantics originally defined by ITU-T (www.itu.int/ITU-T). The current version (v3) is defined by IETF RFC 5280 (www.ietf.org/rfc/rfc5280.txt).

Checking Certificate Validity

Given that a certificate is not corrupt, is it valid? Certificates contain a great deal of metadata about the public key they contain. The public key is just a very large number. It doesn’t represent anything by itself. It’s the metadata that gives that number meaning. A certificate is valid if the metadata it presents is consistent and appropriate for the requested use.

The most important piece of metadata is the subject. For servers, this is generally the fully qualified domain name (FQDN), such as www.example.org. The first test of validity is a name match. If you walk into a bank and identify yourself as “John Smith,” you might be asked for your driver’s license. If you hand over a license that says “Susan Jones,” that would not help in identifying you no matter how authentic the driver’s license. Similarly, if you’re visiting a site named www.example.org and the site presents a certificate with a common name www.badguy.com, you should generally reject it. Unfortunately, it’s not always that simple.

What if you visit example.org and it presents a certificate that says www.example.org? Should you accept that certificate? Most people would assume that example.org and www.example.org refer to the same server (which may or may not be true), but certificates use a simple string match. If the strings don’t match, the certificate is invalid. Some servers present wild card certificates with subjects like *.example.org, and iOS will accept those, but there are still some cases when it will reject a certificate because of a name mismatch you believe it should accept. Unfortunately, iOS doesn’t make this easy to manage, but it can be done.

The primary tool for determining whether to accept a certificate is the NSURLConnection delegate method connection:willSendRequestForAuthenticationChallenge:. In this method, you’re supposed to determine whether you’re willing to authenticate to this server and if so to provide the credentials. The following code authenticates to any server that presents a non-corrupt certificate, whether or not the certificate is valid or trusted:

- (void)connection:(NSURLConnection *)connection

  willSendRequestForAuthenticationChallenge:

  (NSURLAuthenticationChallenge *)challenge

{

  SecTrustRef trust = challenge.protectionSpace.serverTrust;

  NSURLCredential *cred;

  cred = [NSURLCredential credentialForTrust:trust];

  [challenge.sender useCredential:cred

       forAuthenticationChallenge:challenge];  

}

This code extracts the trust object, discussed later, and creates a credential object for it. HTTPS connections always require a credential object, even if you’re not passing credentials to the server.

In the next example, you’re trying to connect to the IP address 72.14.204.113, which is encrypted.google.com. The certificate you receive is *.google.com, which is a mismatch. The string 72.14.204.113 doesn’t include the string .google.com. You decide to accept any trusted certificate that includes google.com in its subject. To compile this example, you need to link Security.framework into your project.

ConnectionViewController.m (Connection)

- (void)connection:(NSURLConnection *)connection

  willSendRequestForAuthenticationChallenge:

  (NSURLAuthenticationChallenge *)challenge

{

  NSURLProtectionSpace *protSpace = challenge.protectionSpace;

  SecTrustRef trust = protSpace.serverTrust;

  SecTrustResultType result = kSecTrustResultFatalTrustFailure;

  

  OSStatus status = SecTrustEvaluate(trust, &result);

  if (status == errSecSuccess &&

      result == kSecTrustResultRecoverableTrustFailure) {

    SecCertificateRef cert = SecTrustGetCertificateAtIndex(trust,

                                                           0);

    CFStringRef subject = SecCertificateCopySubjectSummary(cert);

    

    NSLog(@”Trying to access %@. Got %@.”, protSpace.host,

          subject);

    CFRange range = CFStringFind(subject, CFSTR(“.google.com”),

                                 kCFCompareAnchored|

                                 kCFCompareBackwards);

    if (range.location != kCFNotFound) {

      status = RNSecTrustEvaluateAsX509(trust, &result);

    }

    CFRelease(subject);

  }

  if (status == errSecSuccess) {

    switch (result) {

      case kSecTrustResultInvalid:

      case kSecTrustResultDeny:

      case kSecTrustResultFatalTrustFailure:

      case kSecTrustResultOtherError:

// We’ve tried everything:

      case kSecTrustResultRecoverableTrustFailure:  

        NSLog(@”Failing due to result: %lu”, result);

        [challenge.sender cancelAuthenticationChallenge:challenge];

        break;

        

      case kSecTrustResultProceed:

      case kSecTrustResultConfirm:

      case kSecTrustResultUnspecified: {

        NSLog(@”Success with result: %lu”, result);

        NSURLCredential *cred;

        cred = [NSURLCredential credentialForTrust:trust];

        [challenge.sender useCredential:cred

             forAuthenticationChallenge:challenge];  

        }

        break;

        

      default:

        NSAssert(NO, @”Unexpected result from trust evaluation:%ld”,

                 result);

        break;

    }

  }

  else {

    // Something was broken

    NSLog(@”Complete failure with code: %lu”, status);

    [challenge.sender cancelAuthenticationChallenge:challenge];

  }

}

In this routine, you’re passed a challenge object and extract the trust object. You evaluate the trust object (SecTrustEvaluate) and receive a recoverable failure. Typically, a recoverable failure is something like a name mismatch. You fetch the certificate’s subject and determine whether it’s “close enough” (in this case, checking if it includes .google.com). If you’re okay with the name you were passed, you reevaluate the certificate as a simple X.509 certificate rather than as part of an SSL handshake (that is, you evaluate it while ignoring the hostname). This is done with a custom function RNSecTrustEvaluateAsX509, shown here.

static OSStatus RNSecTrustEvaluateAsX509(SecTrustRef trust,

                                         SecTrustResultType *result

                                         )

{

  OSStatus status = errSecSuccess;

  SecPolicyRef policy = SecPolicyCreateBasicX509();

  SecTrustRef newTrust;

  CFIndex numberOfCerts = SecTrustGetCertificateCount(trust);

  CFMutableArrayRef certs;

  certs = CFArrayCreateMutable(NULL,

                               numberOfCerts,

                               &kCFTypeArrayCallBacks);

  for (NSUInteger index = 0; index < numberOfCerts; ++index) {

    SecCertificateRef cert;

    cert = SecTrustGetCertificateAtIndex(trust, index);

    CFArrayAppendValue(certs, cert);

  }

  status = SecTrustCreateWithCertificates(certs,

                                          policy,

                                          &newTrust);

  if (status == errSecSuccess) {

    status = SecTrustEvaluate(newTrust, result);

  }

  CFRelease(policy);

  CFRelease(newTrust);

  CFRelease(certs);

  

  return status;

}

This function creates a new trust object by copying all the certificates from the original trust object created by the URL loading system. This trust object uses the simpler X.509 policy, which only checks the validity and trust of the certificate itself, without considering the hostname as the original SSL policy does.

A certificate may also be invalid because it has expired. Unfortunately, while you can reevaluate the certificate using any date you want using SecTrustSetVerifyDate, there is no easy, public way to determine the validity dates for the certificate. The following private methods allow you to work out the valid range:

CFAbsoluteTime SecCertificateNotValidBefore(SecCertificateRef);

CFAbsoluteTime SecCertificateNotValidAfter(SecCertificateRef);

As with all private methods, these may change at any time, and may be rejected by Apple. The only other practical way to parse the certificate is to export it with SecCertificateCopyData and parse it again using OpenSSL. Building and using OpenSSL on iOS is beyond the scope of this book. Search the Web for “OpenSSL iOS” for several explanations on how to build this library.

After evaluating the trust object, the final result will be a SecTrustResultType. Several results represent “good” or “possibly good” certificates:

kSecTrustResultProceed—The certificate is valid, and the user has explicitly accepted it.

kSecTrustResultConfirm—The certificate is valid, and you should ask the user whether to accept it.

kSecTrustResultUnspecified—The certificate is valid, and the user has not explicitly accepted or rejected it. Generally, you accept it in this case.

kSecTrustResultRecoverableTrustFailure—The certificate is invalid, but in a way that may be acceptable, such as a name mismatch, expiration, or lack of trust (such as a self-signed certificate).

The following results indicate that the certificate should not be accepted:

kSecTrustResultDeny—The certificate is valid, and the user has explicitly rejected it.

kSecTrustResultInvalid—The validation was unable to complete, likely because of a bug in your code.

kSecTrustResultFatalTrustFailure—The certificate itself was defective or corrupted.

kSecTrustResultOtherError—The validation was unable to complete, likely because of a bug in Apple’s code. You should never see this error.

Determining Certificate Trust

So far, you’ve found out how to determine whether a certificate is valid, but that doesn’t mean it’s trusted. Returning to the example of identifying yourself at the bank, if you present your Metallica fan club membership card, it probably won’t be accepted as identification. The bank has no reason to believe that your fan club has done a good job making sure you’re who you say you are. That’s the same situation an application faces when presented with a certificate signed by an unknown authority.

To be trusted, a certificate must ultimately be signed by one of the certificates in the trust object’s list of anchor certificates. Anchor certificates are those certificates that are explicitly trusted by the system. iOS ships with more than a hundred of them from companies and government agencies. Some are global names like VeriSign and DigiTrust; others are more localized like QuoVadis and Vaestorekisterikeskus. Each of these organizations went through a complex audit process and paid significant amounts of money to be in the root store, but that doesn’t mean your application needs to trust them.

If you generate your own certificate, you can embed the public key in your application and configure your trust object to accept only that certificate or certificates signed by it. This gives you greater control over your security and can save you some money.

For this example, you create a self-signed root certificate.

1. Open Keychain Access.

2. Select Keychain Access menu⇒Certificate Assistant⇒Create a Certificate.

3. Enter any name you like, set the Identity Type to Self Signed Root, set the Certificate Type to SSL Client, and create the certificate. You receive a warning that this is a self-signed certificate. That is the intent of this process, so you click Continue. Your newly created certificate displays a warning that “This root certificate is not trusted.” That’s also as expected because it isn’t in the root keychain.

4. Back in the Keychain Access window, select the login keychain and select the category Certificates.

5. Find your certificate and drag it to the desktop to export it. This file includes only the public key. Keychain does not export the private key by default. Drag the public key file into your Xcode project.

You can test that a certificate you’ve received is signed by your certificate as follows:

SecTrustRef trust = ...; // A trust to validate

NSError *error;

NSString *path = [[NSBundle mainBundle] pathForResource:@”MyCert”

                                                 ofType:@”cer”];

NSData *certData = [NSData dataWithContentsOfFile:path

                                          options:0

                                            error:&error];

SecCertificateRef certificate;

certificate = SecCertificateCreateWithData(NULL,

                                  (__bridge CFDataRef)certData);

CFArrayRef certs = CFArrayCreate(NULL,

                                 (const void**)&certificate,

                                 1,

                                 &kCFTypeArrayCallBacks);

SecTrustSetAnchorCertificates(trust, certs);

CFRelease(certs);

CFRelease(certificate);

You load the certificate from your resource bundle into an NSData, convert it into a SecCertificate, and set it as the anchor for the trust object. The trust object will now accept only the certificates passed to SecTrustSetAnchorCertificates and will ignore the system’s anchors. If you want to accept both, you can use SecTrustSetAnchorCertificatesOnly to reconfigure the trust object.

Using these techniques, you can correctly respond to any certificate in your connection:willSendRequestForAuthenticationChallenge: method and control which certificates you accept or reject.

Employing File Protection

iOS provides hardware-level encryption of files. Files marked for protection are encrypted using a per-device key, which is encrypted using the user’s password or PIN. Ten seconds after the device is locked, the unencrypted per-device key is removed from memory. When the user unlocks the device, the password or personal identification number (PIN) is used to decrypt the per-device key again, which is then used to decrypt the files.

The weakest link in this scheme is the user’s password. On an iPhone, users almost exclusively use a 4-digit PIN, which offers only 10,000 combinations (far fewer are used in practice). In May 2011, ElcomSoft Co. Ltd demonstrated that it could brute-force a 4-digit PIN in about 20–40 minutes. This doesn’t protect against forensics or device theft, but does protect against attackers who have access to the device only for a few minutes. On iPad, typing a real password is much more convenient, so the security is similar to file encryption on a laptop.

For a developer, the specifics of the iOS encryption scheme aren’t critical. The scheme is effective enough for users to expect it on any application that holds sensitive information.

You can configure the protection of individual files that you create with NSFileManager or NSData. The options, shown in the following list, have slightly different names. NSFileManager applies string attributes to the file, whereas NSData uses numeric options during creation, but the meanings are the same. The FileManager constants begin with NSFileProtection..., and the NSData constants begin with NSDataWritingFileProtection....

...None—The file is not protected and can be read or written at any time. This is the default value.

...Complete—Any file with this setting is protected ten seconds after the device is locked. This is the highest level of protection, and the setting you should generally use. Files with this setting may not be available when your program is running in the background. When the device is unlocked, these files are unprotected.

...CompleteUnlessOpen—Files with this setting are protected ten seconds after the device is locked unless they’re currently open. This allows your program to continue accessing the file while running in the background. When the file is closed, it will be protected if the device is locked.

...CompleteUntilFirstUserAuthentication—Files with this setting are protected only between the time the device boots and the first time the user unlocks the device. The files are unprotected from that point until the device is rebooted. This allows your application to open existing files while running in the background. You can create new files using ...CompleteUnlessOpen. This is better than the None setting, but should be avoided if at all possible because it provides very limited protection.

To create a new file with file protection turned on, convert it to an NSData and then use writeToFile:options:error:. This is preferable to creating the file and then using NSFileManager to set its protection attribute.

[data writeToFile:dataPath

          options:NSDataWritingFileProtectionComplete

            error:&writeError];

To create a protected file in the background, you can apply the option ...CompleteUnlessOpen, which allows you to read as long as it’s open when the device locks. You should generally avoid this option unless you’re actually in the background. The easiest way to create a protected file when you may or may not be in the background is as follows:

[data writeToFile:path

          options:NSDataWritingFileProtectionComplete

            error:&error] ||

[data writeToFile:path

          options:NSDataWritingFileProtectionCompleteUnlessOpen

            error:&error];

If you use this technique, upgrade your file protection at startup with a routine like this:

-(void)upgradeFilesInDirectory:(NSString *)dir

                         error:(NSError **)error {

  NSFileManager *fm = [NSFileManager defaultManager];

  NSDirectoryEnumerator *dirEnum = [fm enumeratorAtPath:dir];

  for (NSString *path in dirEnum) {

    NSDictionary *attrs = [dirEnum fileAttributes];

    if (![[attrs objectForKey: NSFileProtectionKey]

        isEqual:NSFileProtectionComplete]) {

      attrs = [NSDictionary dictionaryWithObject:

              NSFileProtectionComplete forKey:NSFileProtectionKey];

      [fm setAttributes:attrs ofItemAtPath:path error:error];

    }

  }

}

If your application needs to know whether protected data is available, you can use one of the following:

Implement the methods applicationProtectedDataWillBecomeUnavailable: and applicationProtectedDataDidBecomeAvailable: in your application delegate.

Observe the notifications UIApplicationProtectedDataWillBecomeUnavailable and UIApplicationProtectedDataDidBecomeAvailable (these constants lack the traditional Notification suffix).

Check [[UIApplication sharedApplication] protectedDataAvailable].

For foreground-only applications, file protection is very easy. Because it’s so simple and it’s hardware-optimized, unless you have a good reason not to, you’ll generally want to protect your files. If your application runs in the background, you need to give more careful thought to how to apply file protection, but still be sure to protect all sensitive information as well as possible.

Using Keychains

File protection is intended to protect data. Keychain is intended to protect secrets. In this context, a secret is a small piece of data used to access other data. The most common secrets are passwords and private keys.

The keychain is protected by the operating system and is encrypted when the device is locked. In practice, it works very similarly to file protection. Unfortunately, the Keychain API is anything but friendly. Many people have written wrappers around the Keychain API. However, my recommendation is Apple’s KeyChainItemWrapper from the GenericKeychain sample code. This is what I’ll discuss in this section after a brief introduction to the low-level data structures.

KeyChainItemWrapper has some problems, most significantly that it’s not ARC-compliant. You can fix that by turning off ARC for the KeyChainItemWrapper.m or by adding the few __bridge casts that are required. If you search the Web for “GenericKeyChain ARC,” you’ll find several posts by people who have already done this. It also isn’t as flexible as we’d like. A number of potential replacements are available. None of them are good enough for us to recommend yet, but this will likely change. Watch iosptl.com for updates.

An item in the keychain is called a SecItem, but it’s stored in a CFDictionary. There is no SecItemRef type. There are five classes of SecItem: generic password, Internet password, certificate, key, and identity. In most cases, you want to use a generic password. Many problems come from developers trying to use an Internet password, which is more complicated and provides little benefit unless you’re implementing a web browser. KeyChainItemWrapper uses only generic password items, which is one reason I like it. Storing private keys and identities is rare in iOS applications and won’t be discussed in this book. Certificates that contain only public keys should generally be stored in files rather than in the keychain.

You eventually need to search the keychain for the item you want. There are many pieces of the key to search for, but the best way is to assign your own identifier and search for that. Generic password items include the attribute kSecAttrGeneric, which you can use to store your identifier. This is how KeyChainItemWrapper operates.

Keychain items have several searchable attributes and a single encrypted value. For a generic password item, some of the more important attributes are the account (kSecAttrAccount), service (kSecAttrService), and identifier (kSecAttrGeneric). The value is generally the password.

So with that background, it’s time to see how to use KeychainItemWrapper. First, as shown in the following code, you create one with initWithIdentifier:accessGroup:. I discuss access groups in the section “Sharing Data with Access Groups,” but for now leave it nil.

KeychainItemWrapper *

wrapper = [[KeychainItemWrapper alloc]

                             initWithIdentifier:@”MyKeychainItem”

                                    accessGroup:nil];

You can now read from and write to wrapper like you would an NSDictionary. It automatically synchronizes with the keychain. The __bridge casts are to allow you to pass Core Foundation constants to a Cocoa method under ARC.

id kUsernameKey = (__bridge id)kSecAttrAccount;

id kPasswordKey = (__bridge id)kSecValueData;

NSString *username = [wrapper objectForKey:kUsernameKey];

[wrapper setObject:password forKey:kPasswordKey];

Note that I am using a Core Foundation type (kSecAttrAccount) where an id is expected (objectForKey:), with no type casting and no __bridge. This is a new feature.

KeychainItemWrapper caches reads, but doesn’t write. Writing to the keychain can be expensive, so you don’t want to do it too often. The keychain is not a place to store sensitive data that changes often. That should be written in an encrypted file, as described in the earlier section, “Employing File Protection.”

Sharing Data with Access Groups

The iOS sandbox creates a significant headache for application suites. If you have multiple applications that work together, there is no easy way to share information between them. Of course, you can save the data on a server, but the user still needs to enter credentials for each of your applications.

iOS offers a solution to this with access groups. Multiple applications can share keychain data as long as they share an access group. To create an access group, open the target in Xcode. At the bottom of the Summary pane, enable Entitlements. Then add a new keychain access group, as shown in Figure 15-2.

9781118449974-fg1502.tif

Figure 15-2 Creating the serversettings access group

Utilizing KeychainItemWrapper, you can use this access group by passing the identifier to initWithIdentifier:accessGroup:. For more information on this feature, see the documentation for SecItemAdd in the Keychain Services Reference at developer.apple.com.

Storing small pieces of sensitive information in the keychain is quite easy with KeychainItemWrapper. Unless you have a very good reason not to, I recommend using it instead of directly accessing the Keychain API, which is much more complicated.

Using Encryption

Most of the time, iOS handles all your encryption needs for you. It automatically encrypts and decrypts HTTPS for network traffic and manages encrypted files using file protections. If you have certificates, SecKeyEncrypt and SecKeyDecrypt handle asymmetric (public/private key) encryption for you.

But what about simple, symmetric encryption using a password? iOS has good support for this, but limited documentation. The available documentation is in /usr/include/CommonCrypto. Most of it assumes that you have some background in cryptography and doesn’t warn you of common mistakes. This unfortunately has led to a lot of bad AES code examples on the Internet. In an effort to improve things, I developed RNCryptor as an easy-to-use encryption library based on CommonCrypto. In this section, I discuss many of the underlying details of how RNCryptor works and why. This will let you modify RNCryptor with confidence, or enable you to build your own solution. If you just want a prebuilt solution, skip this section and read the RNCryptor docs. You can find the source for RNCryptor at github.com/rnapier/RNCryptor.

Overview of AES

The Advanced Encryption Standard, or AES, is a symmetric encryption algorithm. Given a key, it converts plaintext into ciphertext. The same key is used to convert ciphertext back into plaintext. Originally named Rijndael, in 2001 the algorithm was selected by the U.S. government as its standard for encryption.

It’s a very good algorithm. Unless you need another algorithm for compatibility with an existing system, always use AES for symmetric encryption. The best cryptographers in the world have carefully scrutinized it, and it’s hardware-optimized on iOS devices, making it extremely fast.

AES offers three key lengths: 128, 192, and 256 bits. There are slight differences in the algorithm for each length. Unless you have very specialized needs, I recommend AES-128. It offers an excellent tradeoff of security and performance, including time performance and battery life performance.

At its heart, AES is just a mathematical function that takes a fixed-length key and a 16-byte block and returns a different 16-byte block. This has several implications:

AES uses a fixed-length key, not a variable-length password. You must convert passwords to keys in order to use them in AES. We explain this further in the next section, “Converting Passwords to Keys with PBKDF2.”

AES can only transform a 16-byte block into another 16-byte block. In order to work on plaintext that isn’t exactly 16-bytes long, some mechanism outside of AES must be applied. See the sections “AES Mode and Padding” and “The Initialization Vector (IV)” for how AES works with data that is not exactly 16 bytes long.

Every possible block of 16 bytes is legal as plaintext or as ciphertext. This means that AES provides no authentication against accidental or intentional corruption of the ciphertext. See the section “Authentication with HMAC” for how to add error detection to AES.

Every possible key can be applied to every possible block. This means that AES provides no mechanism for detecting that the key was incorrect. See the section “Bad Passwords” for more information on this problem.

Although AES supports three key lengths, it has only one block size. This sometimes creates confusion over the constant kCCBlockSizeAES128. The “128” in this constant refers to the block size, not the key size. So when using AES-192 or AES-256, you still pass kCCBlockSizeAES128 as the block size.

Converting Passwords to Keys with PBKDF2

A key is not the same thing as a password. A key is a very large number used to encrypt and decrypt data. All possible keys for an encryption system are called its key space. A password is something a human can type. Long passwords that include spaces are sometimes called passphrases, but for simplicity, I just use the word “password” no matter the construction. If you try to use a password as an AES key, you significantly shrink the number of available keys. If the user selects a random 16-character password using the 94 characters on a standard keyboard, that only creates about a 104-bit key space, approximately one ten-millionth the size of the full AES key space. Real users select passwords from a much smaller set of characters. Worse yet, if the user has a password longer than 16 bytes (16 single-byte characters or 8 double-byte characters), you will throw away part of it when using AES-128.

You need a way to convert a password into a useable key that makes it as hard as possible on the attacker to search every possible password. The answer is a password-based key derivation function. Specifically, you will use PBKDF2, which is defined by RSA Laboratories’ Public-Key Cryptography Standards (PKCS) #5. You don’t need to know the internals of PBKDF2 or PKCS #5, but it’s important to know the names because they show up in the documentation. What is important is that PBKDF2 converts a password into a key.

To use PBKDF2, you need to generate a salt, which is just a large random number. The standard recommends at least 64 bits. The salt is combined with the password to prevent identical passwords from generating identical keys. You then iterate through the PBKDF2 function a specific number of times, and the resulting data is your key. This process is called stretching. To decrypt the data, you need to preserve the salt and the number of iterations. Typically, the salt is saved with the encrypted data, and the number of iterations is a constant in your source code, but you can also save the number of iterations with the encrypted data.

The important fact here is that the salt, the number of iterations, and the final ciphertext are all public information. Only the key and the original password are secrets.

Generating the salt is easy. It’s just a large random number. You can create it with a method like randomDataOfLength:, shown in the following code:

RNCryptor.m (RNCryptor)

const NSUInteger kPBKDFSaltSize = 8;

+ (NSData *)randomDataOfLength:(size_t)length {

  NSMutableData *data = [NSMutableData dataWithLength:length];

  

  int result = SecRandomCopyBytes(kSecRandomDefault,

                                  length,

                                  data.mutableBytes);

  NSAssert(result == 0, @”Unable to generate random bytes: %d”,

           errno);

  return data;

}

...

NSData *salt = [self randomDataOfLength:kPBKDFSaltSize];

Originally, the standard called for 1,000 iterations of PBKDF2, but this has gone up as CPUs have improved. I recommend between 10,000 and 100,000 iterations on an iPhone 4 and 50,000 to 500,000 iterations on a modern MacBook Pro. The reason for the large number is to slow down brute-force attacks. An attacker generally tries passwords rather than raw AES keys because the number of practical passwords is much smaller. By requiring 10,000 iterations of the PBKDF2 function, the attacker must waste about 80ms per attempt on an iPhone 4. That adds up to 13 minutes of search time for a 4-digit PIN, and months or years to search for even a very simple password. The extra 80ms for a single key generation is generally negligible. Going up to 100,000 iterations adds nearly a second to key generation on an iPhone, but provides much better protection if the password guessing is done on a desktop, even if the password is very weak.

PBKDF2 requires a pseudorandom function (PRF), which is just a function that can generate a very long series of statistically random numbers. iOS supports various sizes of SHA-1 and SHA-2 for this. SHA256, which is a specific size of SHA-2, is often a good choice.

RNCryptor uses SHA-1 as its PRF for historical compatibility reasons. This will likey change to SHA256 in the future.

Luckily, it’s easier to use PBKDF2 than it is to explain it. The following method accepts a password string and salt data and returns an AES key.

RNCryptor.m (RNCryptor)

+ (NSData *)keyForPassword:(NSString *)password salt:(NSData *)salt

                  settings:(RNCryptorKeyDerivationSettings)keySettings

{

  NSMutableData *derivedKey = [NSMutableData

                                dataWithLength:keySettings.keySize];

  int result = CCKeyDerivationPBKDF(keySettings.PBKDFAlgorithm,

                                    password.UTF8String,

                                    password.length,

                                    salt.bytes,

                                    salt.length,

                                    keySettings.PRF,

                                    keySettings.rounds,

                                    derivedKey.mutableBytes,

                                    derivedKey.length);

  // Do not log password here

  NSAssert(result == kCCSuccess,

    @”Unable to create AES key for password: %d”, result);

  return derivedKey;

}

The password salt and number of iterations must be stored along with the cipher text.

AES Mode and Padding

AES is a block cipher, which means that it operates on a fixed-sized block of data. AES works on exactly 128 bits (16 bytes) of input at a time. Most things you want to encrypt, however, are not exactly 16 bytes long. Many things you want to encrypt aren’t even a multiple of 16 bytes. In order to address this issue, you need to choose an appropriate mode.

A mode is an algorithm for chaining blocks together so that arbitrary data can be encrypted. Modes are applicable to any block cipher, including AES.

If you don’t need the details on how AES modes work, just trust me that the right answer on iOS devices is CBC with PKCS#7 padding and skip to the section, “The Initialization Vector (IV).”

The simplest mode is electronic codebook (ECB). Never use this mode in an iOS app. ECB is appropriate only for cases when you need to encrypt a huge number of random blocks. This is such a rare situation that you shouldn’t even consider it for iOS apps. ECB simply takes each block and applies the block cipher (AES) and outputs the result. It is extremely insecure. The problem is that if two plaintext blocks are the same, the ciphertext will be the same. This leaks a lot of information about the plaintext and is subject to numerous attacks. Do not use ECB mode.

The most common block cipher mode is cipher-block chaining (CBC). The ciphertext of each block is XORed with the next plaintext block prior to encryption. This is a very simple mode, but very effective in solving the problems with ECB. It has two major problems: Encryption cannot be done in parallel, and the plaintext must be an exact multiple of the block size (16 bytes). Parallel encryption is not a major concern on iOS platforms. iPhone and iPad perform AES encryption on dedicated hardware and don’t have sufficient hardware to parallelize the operation. The block size problem is dealt with by padding.

Padding is the practice of expanding plaintext to the correct length prior to encryption. It needs to be done in a way that the padding can be unambiguously removed after decryption. There are several ways to achieve this, but the only way supported by iOS is called PKCS #7 padding. This is requested with the option kCCOptionPKCS7Padding. In PKCS #7 padding, the encryption system appends n copies of the byte n. So, if your last block were 15 bytes long, it would append one 0x01 byte. If your last block were 14 bytes long, it would append two 0x02 bytes. After decryption, the system looks at the last byte and deletes that number of padding bytes. It also performs an error check to make sure that the last n bytes are actually the value n. This has surprising implications that we discuss in the section “Bad Passwords.”

There is a special case if the data is exactly the length of the block. In order to be unambiguous, the PKCS #7 has to append a full block of 0x10.

PKCS #7 padding means that the ciphertext can be up to a full block longer than the plaintext. Most of the time this is fine, but in some situations, this is a problem. You may need the ciphertext to fit in the same space as the plaintext. There is a CBC-compatible method called ciphertext stealing that can do this, but it’s not supported on iOS with CBC.

CBC is the most common cipher mode, making it the easiest to exchange with other systems. Unless you have a strong reason to use something else, use CBC. This is the mode recommended by Apple’s security team, based on my discussions with them.

If padding is impossible in your situation, I recommend cipher feedback mode (CFB). It’s less widely supported than CBC, but it is a fine mode and doesn’t require padding. Output feedback (OFB) is also fine for the same reasons. I have no particular advice on choosing one over the other. They are both good.

Counter (CTR) mode is useful if you need to avoid the overhead of an initialization vector, but is easy to use insecurely. I discuss CTR, when to use it and how, in the section “The Initialization Vector (IV).

XTS is a specialized mode for random-access data, particularly encrypted file systems. I don’t have enough relevant information to recommend it. FileVault on Mac uses it, so Apple clearly has an interest in it.

All of the modes offered by iOS are unauthenticated. This means that modifying the ciphertext will not necessarily create errors. In most cases, it will just change the resulting plaintext. It’s possible for attackers who know some of the contents of an encrypted document to modify it in predictable ways without the password. There are encryption modes (called authenticated modes) that protect against this, but none are supported by iOS. See the section “Authentication with HMAC,” for more information.

The many encryption modes can be confusing, but in almost all cases, the most common solution is best: CBC with PKCS #7 padding. If padding is problematic for you, then I recommend CFB.

The Initialization Vector (IV)

As discussed in the earlier section “AES Mode and Padding,” in chaining modes such as CBC, each block influences the encryption of the next block. This ensures that two identical blocks of plaintext will not generate identical blocks of ciphertext.

The first block is a special case because there’s no previous block. Chaining modes allow you to define an extra block called the initialization vector (IV) to begin the chain. This is often labeled optional, but you need to always provide one. Otherwise, an all-zero block is used, and that leaves your data vulnerable to certain attacks.

As with the salt, the IV is just a random series of bytes that you save with the ciphertext and use during decryption.

iv = [self randomDataOfLength:kAlgorithmIVSize];

You then store this IV along with the ciphertext and use it during decryption. In some cases, adding this IV creates unacceptable overhead, particularly in high-performance network protocols and disk encryption. In that case, you still cannot use a fixed IV (such as NULL). You must use a mode that doesn’t require a random IV. Counter mode (CTR) uses a nonce rather than an IV. A nonce is essentially a predictable IV. While chaining modes like CBC require that the IV be random, nonce-based modes require that the nonce be unique. This requirement is very important. In CTR mode, a given nonce/key pair must never, ever be reused. This is usually implemented by making the nonce a monotonically increasing counter starting at 0. If the nonce counter ever needs to be reset, then the key must be changed.

The advantage of a nonce is that it doesn’t have to be stored with the ciphertext the way an IV must. If a key is chosen randomly at the beginning of a communication session, then both sides of the communication can use the current message number as the nonce. As long as the number of messages cannot exceed the largest possible nonce, this is a very efficient way to communicate.

The nonce uniqueness requirement is often more difficult to implement than it seems. Randomly selecting a nonce, even in a large nonce-space, may not be sufficient if the random number generator is not sufficiently random. A nonce-based mode with a non-unique nonce is completely insecure. IV-based modes, on the other hand, are weaker if the IV is reused, but they aren’t completely broken.

In most iOS applications, the correct solution is CBC with PKCS #7 padding and a random IV sent along with the data.

Authentication with HMAC

None of the modes discussed so far protect against accidental or malicious modification of the data. AES does not provide any authentication. If an attacker knows that a certain string occurs at a certain location in your data, the attacker can change that data to anything else of the same size. This could change “$100” to “$500” or “[email protected]” to “[email protected]”.

Many block cipher modes provide authentication. Unfortunately, iOS does not support any of them. To authenticate data, you need to do it yourself. The best way to do this is to add an authentication code to the data. The best choice on iOS is a hash-based message authentication code (HMAC).

An HMAC requires its own key, just like encryption. If you’re using a password, you can use PBKDF2 to generate the HMAC key using a different salt. If you’re using a random encryption key, you’ll need to also generate a random HMAC key. If you use a salt, you’ll include the HMAC salt with the ciphertext, just like the encryption salt. See the section “Converting Passwords to Keys with PBKDF2” for more information.

Because a HMAC can hash anything, it can be computed on the plaintext or on the ciphertext. It is best to encrypt first and then HMAC the ciphertext. This allows you to verify the data prior to decrypting it. It also protects against certain kinds of attacks.

In practice, the code required to HMAC is very similar to the encryption code. You either pass all the ciphertext to a single HMAC call, or you call the HMAC routines multiple times with blocks of ciphertext.

Bad Passwords

The lack of authentication has a commonly misunderstood side effect. AES cannot directly detect when you pass the wrong key (or password). Systems that use AES often appear to detect this, but that’s actually just luck.

Most AES systems use CBC encryption with PKCS #7 padding. As I explained in the earlier section “AES Mode and Padding,” the PKCS #7 padding scheme adds a predictable pattern to the end of the plaintext. When you decrypt, the padding provides a kind of error detection. If the plaintext does not end in a legal padding value, the decryptor returns an error. But if the last decrypted byte happens to be 0x01, this appears to be legal padding, and you won’t be able to detect a bad password. For systems without padding (such as CFB or CTR), even this check is not possible.

If you use a password and PBKDF2 to generate your encryption key and HMAC key, the HMAC will give you password validation. Because the HMAC key is based on the password, and the password is wrong, the HMAC won’t validate. But if you use a random encryption key and random HMAC key, you still won’t be able to detect a bad password.

To some extent, this is a security feature. If the attacker cannot easily detect the difference between a correct and incorrect password, it’s harder to break the encryption. In almost all cases where you want to report a bad password, it’s because you’re using PBKDF2 and HMAC will solve the problem. Otherwise, you may have to add error detection inside your plaintext to be certain.

Note that even with HMAC, you cannot easily distinguish a corrupted ciphertext from a bad password.

Performing One-Shot Encryption

That’s most of the theory you need. iOS provides all the math functions, so you can ignore the implementation details.

The first example is one-shot encryption and decryption routines. These take an NSData and return an NSData. They use the convenience function CCCrypt from CommonCryptor.

The encryption routine accepts plaintext data and a password and returns ciphertext data, an IV, a salt, a HMAC salt, and a HMAC.

RNCryptManager.m (CryptPic)

#import <CommonCrypto/CommonCryptor.h>

const CCAlgorithm kAlgorithm = kCCAlgorithmAES128;

const NSUInteger kAlgorithmKeySize = kCCKeySizeAES128;

const NSUInteger kAlgorithmBlockSize = kCCBlockSizeAES128;

const NSUInteger kAlgorithmIVSize = kCCBlockSizeAES128;

const NSUInteger kPBKDFSaltSize = 8;

const NSUInteger kPBKDFRounds = 10000;  // ~80ms on an iPhone 4

+ (NSData *)encryptedDataForData:(NSData *)data

                        password:(NSString *)password

                              iv:(NSData **)iv

                            salt:(NSData **)salt

                        HMACSalt:(NSData **)HMACSalt

                            HMAC:(NSData **)HMAC

                           error:(NSError **)error {

  NSAssert(iv, @”IV must not be NULL”);

  NSAssert(salt, @”salt must not be NULL”);

  

  *iv = [self randomDataOfLength:kAlgorithmIVSize];

  *salt = [self randomDataOfLength:kPBKDFSaltSize];

  

  NSData *key = [self AESKeyForPassword:password salt:*salt];

  

  size_t outLength;

  NSMutableData *

  cipherData = [NSMutableData dataWithLength:data.length +

                kAlgorithmBlockSize];

  CCCryptorStatus

  result = CCCrypt(kCCEncrypt, // operation

                   kAlgorithm, // Algorithm

                   kCCOptionPKCS7Padding, // options

                   key.bytes, // key

                   key.length, // keylength

                   (*iv).bytes,// iv

                   data.bytes, // dataIn

                   data.length, // dataInLength,

                   cipherData.mutableBytes, // dataOut

                   cipherData.length, // dataOutAvailable

                   &outLength); // dataOutMoved

  if (result == kCCSuccess) {

    cipherData.length = outLength;

  }

  else {

   // ... Handle Error ...

    return nil;

  }

  

  if (HMAC) {

    NSAssert(HMACSalt, @”HMAC salt must not be NULL if HMAC is passed.”);

    *HMACSalt = [self randomDataOfLength:kPBKDFSaltSize];

    NSData *HMACKey = [self AESKeyForPassword:password salt:*HMACSalt];

    NSMutableData *

      HMACOut = [NSMutableData dataWithLength:CC_SHA256_DIGEST_LENGTH];

    CCHmac(kCCHmacAlgSHA256,

           [HMACKey bytes], [HMACKey length],

           [cipherData bytes], [cipherData length],

           [HMACOut mutableBytes]);

    *HMAC = HMACOut;

  }

  return cipherData;

}

The decryption routine accepts ciphertext data, a password, IV, salt, HMAC salt, and HMAC.

RNCryptManager.m (CryptPic)

+ (NSData *)decryptedDataForData:(NSData *)data

                        password:(NSString *)password

                              iv:(NSData *)iv

                            salt:(NSData *)salt

                        HMACSalt:(NSData *)HMACSalt

                            HMAC:(NSData *)HMAC

                           error:(NSError **)error {

  if (HMAC) {

    NSData *HMACKey = [self AESKeyForPassword:password salt:HMACSalt];

    NSMutableData *

      HMACOut = [NSMutableData dataWithLength:CC_SHA256_DIGEST_LENGTH];

    CCHmac(kCCHmacAlgSHA256,

           [HMACKey bytes],

           [HMACKey length],

           [data bytes],

           [data length],

           [HMACOut mutableBytes]);

    if (! [HMAC isEqualToData:HMACOut]) {

      // ... Handle error ...

      return nil;

    }

  }

  NSData *key = [self AESKeyForPassword:password salt:salt];

  

  size_t outLength;

  NSMutableData *

  decryptedData = [NSMutableData dataWithLength:data.length];

  CCCryptorStatus

  result = CCCrypt(kCCDecrypt, // operation

                   kAlgorithm, // Algorithm

                   kCCOptionPKCS7Padding, // options

                   key.bytes, // key

                   key.length, // keylength

                   iv.bytes,// iv

                   data.bytes, // dataIn

                   data.length, // dataInLength,

                   decryptedData.mutableBytes, // dataOut

                   decryptedData.length, // dataOutAvailable

                   &outLength); // dataOutMoved

  

  if (result == kCCSuccess) {

    [decryptedData setLength:outLength];

  }

  else {

    if (result != kCCSuccess) {

      // ... Handle Error ...

      return nil;

    }

  }

  

  return decryptedData;

}

Improving CommonCrypto Performance

The CCCrypt function is fairly straightforward. It has a lot of parameters, and you need to generate a key, but once you have your data in place, it’s just one function call. As presented in the section “Performing One-Shot Encryption,” however, CCCrypt requires enough memory to hold two copies of your plaintext. It also requires that all the plaintext be available when it gets started.

You can save half the memory by reusing the buffer in CCCrypt. The dataIn and dataOut parameters can point to the same buffer as long as it’s as large as the ciphertext. For AES, that’s the size of the plaintext plus one 16-byte block.

This still requires that all the plaintext be available in memory at the same time. That can be expensive for large files, especially on a mobile device. It also prevents you from decrypting as the data is read from the network. This is particularly useful in cases when you want to store data on an untrusted server. HTTPS protects it on the network but that doesn’t help if you don’t trust the server. It’s better to use file protection locally on the device and use AES to protect the file remotely.

CCCrypt is just a convenience function around the normal CommonCrypto routines: CCCryptorCreate, CCCryptorUpdate, and CCCryptorFinal. In this example, you use these to handle encryption and decryption with NSStream objects. The full source code is available in the CryptPic sample code for this chapter.

This routine handles either encrypting or decrypting, based on the operation parameter (kCCEncrypt or kCCDecrypt). First, it reads or writes the IV and salt at the beginning of the stream. The _CM...Data methods are helpers for dealing with NSStream. They’re available in the sample code.

RNCryptManager.m (CryptPic)

  switch (operation) {

    case kCCEncrypt:

      // Generate a random IV for this file.

      iv = [self randomDataOfLength:kAlgorithmIVSize];

      salt = [self randomDataOfLength:kPBKDFSaltSize];

      if (! [outStream _CMwriteData:iv error:error] ||

          ! [outStream _CMwriteData:salt error:error]) {

        return NO;

      }

      break;

    case kCCDecrypt:

      // Read the IV and salt from the encrypted file

      if (! [inStream _CMgetData:&iv

                       maxLength:kAlgorithmIVSize

                           error:error] ||

          ! [inStream _CMgetData:&salt

                       maxLength:kPBKDFSaltSize

                           error:error]) {

        return NO;

      }

      break;

    default:

      NSAssert(NO, @”Unknown operation: %d”, operation);

      break;

  }

Next, it generates the key from the password and creates the CCCryptor object. This is the object that performs the encryption or decryption.

  NSData *key = [self AESKeyForPassword:password salt:salt];

  

  // Create the cryptor

  CCCryptorRef cryptor = NULL;

  CCCryptorStatus result;

  result = CCCryptorCreate(operation,             // operation

                           kAlgorithm,            // algorithim

                           kCCOptionPKCS7Padding, // options

                           key.bytes,             // key

                           key.length,            // keylength

                           iv.bytes,              // IV

                           &cryptor);             // OUT cryptorRef

Next, it allocates some buffers to use. According to the documentation, you can use a single buffer to manage the plaintext and ciphertext, but there is a bug in CCCryptorUpdate that prevents this (radar://9930555). If you use padding and call CCCryptorUpdate multiple times, you can’t do “in place” encryption. That isn’t a major problem in this case because the buffer size is small.

CCCryptorGetOutputLength returns the size of the buffer required to process the requested number of bytes, including any extra data that may be needed for the final block. You could also use kMaxReadSize + kAlgorithmBlockSize, which is always greater than or equal to the result of CCCryptorGetOutputLength. There’s no problem with allocating a little too much memory here. Using NSMutableData rather than malloc lets ARC take care of the memory management for you, even if there’s an error.

  dstBufferSize = CCCryptorGetOutputLength(cryptor, // cryptor

                                      kMaxReadSize, // input length

                                             true); // final

  NSMutableData *

  dstData = [NSMutableData dataWithLength:dstBufferSize];

  

  NSMutableData *

  srcData = [NSMutableData dataWithLength:kMaxReadSize];

  uint8_t *srcBytes = srcData.mutableBytes;

  uint8_t *dstBytes = dstData.mutableBytes;

Now the routine reads a block of data, encrypts or decrypts it, and writes it to the output stream. processResult:bytes:length:toStream:error: just checks the result and handles the file writing in a way that simplifies error handling. The important call is CCCryptorUpdate. This reads data from srcBytes and writes them to dstBytes. It updates dstLength with the number of bytes written.

  ssize_t srcLength;

  size_t dstLength = 0;

  

  while ((srcLength = [inStream read:srcBytes

                           maxLength:kMaxReadSize]) > 0 ) {

    result = CCCryptorUpdate(cryptor,       // cryptor

                             srcBytes,      // dataIn

                             srcLength,     // dataInLength

                             dstBytes,      // dataOut

                             dstBufferSize, // dataOutAvailable

                             &dstLength);   // dataOutMoved

    

    if (![self processResult:result

                       bytes:dstBytes

                      length:dstLength

                        toStream:outStream

                       error:error]) {

      CCCryptorRelease(cryptor);

      return NO;

    }

  }

When you’ve read the entire file (srcLength == 0), there may still be some unprocessed data in the CCCryptor. CCCryptorUpdate processes only data in block-sized units (16 bytes for AES). If padding was enabled, you need to call CCCryptorFinal to deal with whatever’s left over. If you did not enable padding, you can skip this step, but it’s generally not worth writing special code to avoid it.

  result = CCCryptorFinal(cryptor,        // cryptor

                          dstBytes,       // dataOut

                          dstBufferSize,  // dataOutAvailable

                          &dstLength);    // dataOutMoved

  if (![self processResult:result

                     bytes:dstBytes

                    length:dstLength

                      toStream:outStream

                     error:error]) {

    CCCryptorRelease(cryptor);

    return NO;

  }

  

  CCCryptorRelease(cryptor);

  return YES;

Note the calls to CCCryptorRelease. Unlike other ...Release functions, this immediately frees the memory. There’s no retain counting on CCCryptor. CCCryptorRelease also overwrites the memory with zeros, which is good security practice for sensitive data structures.

The fact that CCCryptorRelease overwrites the memory with zeros is not documented in CCCommonCryptor.h, but it can be verified in the source code. CommonCrypto is open source, and the source is available from http://opensource.apple.com. Look in the OS X tree, not the iOS tree.

You can see this code in action in the CryptPic sample project. For a more advanced version of this approach, including handling HMAC, see “RNCryptor” in the “Further Reading” section.

Combining Encryption and Compression

It’s sometimes a good idea to compress data before encrypting it. There’s a theoretical security benefit to doing so, but generally it’s just to make the data smaller. The important thing to remember is that you must compress before you encrypt. You can’t compress encrypted data. If you could, that would suggest patterns in the ciphertext, which would indicate a poor encryption algorithm. In most cases, encrypting and then compressing leads to a larger output than the original plaintext.

Summary

iOS provides a rich collection of security frameworks to make it as easy as possible to secure your users’ data. This chapter showed you how to secure network communications, files, and passwords. You also found out how to properly validate certificates so that you can ensure that your application communicates only with trusted sources. Securing your application requires a few extra lines of code, but taking care of the basics is generally not difficult using the code provided in this chapter.

Further Reading

Apple Documentation

The following documents are available in the iOS Developer Library at developer.apple.com or through the Xcode Documentation and API Reference.

Certificate, Key, and Trust Services Programming Guide

iOS Application Programming Guide: “The Application Runtime Environment”

Secure Coding Guide (/usr/lib/CommonCrypto)

WWDC Sessions

The following session videos are available at developer.apple.com.

WWDC 2011, “Session 208: Securing iOS Applications”

WWDC 2012, “Session 704: The Security Framework”

WWDC 2012, “Session 714: Protecting the User’s Data”

Other Resources

Aleph One. Phrack, Volume 7, Issue Forty-Nine, “Smashing The Stack For Fun And Profit” (1996). Fifteen years later, this is still one of the best introductions to buffer overflows available, with examples.www.phrack.org/issues.html?issue=49&id=14#article

Boneh, Dan. Stanford “Cryptography” Course. There is no better free resource for learning how to do cryptography correctly than this course. This is not a quick overview. You’ll discover the math behind cryptosystems and how they’re put together correctly and incorrectly. You’ll learn how to reason about the security of systems and mathematically prove security theorems about them. It’s more math than computer programming. I highly recommend it, but it is a significant commitment. It’s broken into two six-week sections.https://www.coursera.org/course/crypto

Granoff, Mark. Lockbox. While not yet ideal, this is the most promising Keychain wrapper in my opinion.https://github.com/granoff/Lockbox

Napier, Rob. “RNCryptor.” This is my framework for AES encryption, based on CommonCryptor. Its purpose is to make it easy to use AES correctly, and it implements all the features discussed in this chapter.https://github.com/rnapier/RNCryptor

Schneier, Bruce, Applied Cryptography (John Wiley & Sons 1996). Anyone interested in the guts of cryptography should read this book. The main problem is that after reading it, you may think you can create your own cryptography implementations. You shouldn’t. Read this book as a fascinating, if dated, introduction to cryptography. Then put it down and use a well-established implementation.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.137.167.107