iOS encrypts and decrypts data with key

Keywords: iOS encoding SHA1 Programming

Introduction

In the development of iOS App, we need to encrypt the account, password and other personal private information to ensure the security of user information. Then these private information can be saved in the keychain, because the invisibility of the keychain can ensure the security of the user's private information.
It should be noted that it is not safe to store plaintext in the keychain. Therefore, the user's private information can be encrypted by the algorithm and then stored in the key chain, which further ensures the user's information security.

Common encryption algorithms

Encryption algorithm = symmetric encryption algorithm + asymmetric encryption algorithm

The encryption algorithm is usually divided into symmetric encryption algorithm and asymmetric pseudoplane algorithm.

1, Symmetric encryption algorithm:

  1. Principle: both sides of information receiving need to know the key and encryption and decryption algorithm in advance, and the key is the same, then the data can be encrypted and decrypted.
  2. The common ones are: AES, DES, 3DES:
  • AES (Advanced Encryption Standard): Advanced Encryption Standard, is the next generation of encryption algorithm standard, fast, high security level.
  • DES (Data Encryption Standard): Data Encryption Standard, fast, applicable to encrypt a large number of data.
  • 3DES (Triple DES): Based on DES, a piece of data is encrypted three times with three different keys, with higher strength than des.

2, Asymmetric encryption algorithm:

  1. Principle: communication parties A and B form a key pair (private key + public key), then a sends its own public key to B, and B also sends its own public key to a, that is, communication parties exchange their public keys. If a wants to send a secret telegram to B, it needs to encrypt the telegram with the public key given by B, and then send it to B. After receiving the secret telegram, B needs to decrypt the telegram with his private key if he wants to know what information a has conveyed to him. The same is true when B sends a message to a.
  2. The common ones are: RSA, DSA, ECC.
  • RSA: it was proposed by RSA company. Long data encryption is supported.
  • DSA (Digital Signature Algorithm): data signature algorithm is a set of digital signature guarantee (DSS).
  • ECC (Elliptic Curves Cryptography): elliptic curve cryptography.

Private key and key: the concept of private key and public key is mentioned in asymmetric encryption algorithm. Its function can be described as follows: the content encrypted by public key can only be decrypted by private key, and the content encrypted by private key can only be decrypted by public key. Generally speaking, public key encryption and private key decryption should be exactly "private key signature and public key verification".

Linear discrete algorithm (non encryption and decryption algorithm)##

For example, MD5, SHA1, HMAC are linear discrete algorithms. These algorithms only generate a series of irreversible ciphertext, and are often used to check whether the data has been tampered in the transmission process. If the same algorithm is used, but different ciphertext is generated, then it can be said that the data has been modified in the transmission process.

Hash functions, such as MD5 and SHA, are not encryption algorithms. Many people call them encryption algorithms, which is incorrect. These algorithms have no concept of key, similar to fingerprint, and are irreversible. MD5 is 128 bit, SHA has 128256 alleles, such as SHA128, SHA256, and SHA384. Our commonly used Base64 does not belong to the category of encryption algorithm. It just uses Base64 coding to display some special byte [] arrays in some encrypted ciphertexts explicitly. After conversion, the length will increase, which is a reversible process.

Choice of encryption algorithm

  • Asymmetric encryption algorithm runs much slower than symmetric encryption algorithm. When we need to encrypt a large amount of data, we usually use symmetric encryption algorithm to improve the encryption and decryption speed.
  • Symmetric encryption algorithm can't realize digital signature, only asymmetric algorithm can be used when signature is needed.

Compromise:

  • Because the key management of symmetric encryption algorithm is a complex process, the key management directly determines the security of data, so when the amount of data is very small, we usually use asymmetric encryption algorithm.
  • We usually use asymmetric encryption algorithm to manage the key of symmetric algorithm, and then use symmetric encryption algorithm to encrypt data.
  • In this way, the advantages of the two kinds of encryption algorithms are integrated, which not only improves the speed of encryption and decryption, but also can manage the key safely and conveniently.

key management

You can use public and private keys for asymmetric encryption, and symmetric keys for symmetric encryption.

When you authenticate from a password protected file or keychain, you can extract the private key it contains. Once you have the private key, you can also use SecKeyCopyPublicKey(:) to extract the public key. That's why we usually store the private key in the keychain and do not save the public key.

1, Generate key, save private key

Keeping a private key in a keychain is a great way to secure it. The key data is encrypted on disk and accessible only to your app or the apps you authorize. However, to use the key, you do eventually copy a plain-text version of it into system memory, even if briefly. Though this presents a reasonably small attack surface, there is still the chance that if your app is compromised, the key could become compromised as well. As an added layer of protection, on devices that support it and for certain operations, you can store a private key in the Secure Enclave.

In Apple's Certificate, Key, and Trust Services Programming Guide, it is mentioned that saving the private key in the keychain still has the risk of damage. Apple recommends that the private key be stored in Secure Enclave, and only 256 bit elliptic curve private key can be stored.

/** Generating key */
+ (BOOL)generatePrivateKey:(NSString *)attrLabel error:(NSError **)error {
    if (!attrLabel) {
        if (error) {
            *error = [NSError errorWithDomain:ALDataEncryptionErrorDomain code:ALDataEncryptionInvalidParameter userInfo:@{NSLocalizedDescriptionKey:@"The 'attrLabel' parameter passed to a function were not valid."}];
        }
        return NO;
    }

    CFErrorRef e = NULL;
    SecAccessControlRef sacObject = SecAccessControlCreateWithFlags(kCFAllocatorDefault,
                                                                    kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly,
                                                                    kSecAccessControlPrivateKeyUsage,
                                                                    &e);
    if (e) {
        if (sacObject) CFRelease(sacObject);

        if (error) {
            *error = (__bridge NSError *)e;
        }
        return NO;
    }

    NSDictionary *parameters = @{
                                 (id)kSecAttrTokenID: (id)kSecAttrTokenIDSecureEnclave,
                                 (id)kSecAttrKeyType: (id)kSecAttrKeyTypeECSECPrimeRandom,
                                 (id)kSecAttrKeySizeInBits: @256,                                 (id)kSecAttrLabel: attrLabel,
                                 (id)kSecPrivateKeyAttrs: @{
                                         (id)kSecAttrAccessControl: (__bridge_transfer id)sacObject,
                                         (id)kSecAttrIsPermanent: @YES,
                                         }
                                 };

    CFErrorRef errorRef = NULL;
    SecKeyRef privateKey = SecKeyCreateRandomKey((__bridge CFDictionaryRef)parameters, &errorRef);

    if (errorRef) {
        if (privateKey) CFRelease(privateKey);
        if (error) {
            *error = (__bridge NSError *)errorRef;
        }
        return NO;
    } else if (privateKey) {
        CFRelease(privateKey);

        return YES;
    } else {
        if (privateKey) CFRelease(privateKey);

        return NO;
    }
}

The above function is used to generate a key.

  • The value of the property kSecAttrTokenID kSecAttrTokenIDSecureEnclave means that the generated key (in this case, the private key) is automatically saved in Secure Enclave.
  • The value of the attribute kSecAttrKeyType, kSecAttrKeyTypeECSECPrimeRandom, indicates which type of key is generated, in this case, ECC (elliptic curve key) key.
  • The value of the property ksecattrkeysizenbits indicates that only 256 bit private keys are stored.
  • The property kSecAttrIsPermanent indicates whether the private key is persistent. Here we set it to YES, which means that it is stored in the key chain by default.

2, Get private key from Keychain

+ (SecKeyRef)retrievePrivateFromKeychain:(NSString *)attrLabel error:(NSError **)error CF_RETURNS_RETAINED {
    if (!attrLabel) {
        if (error) {
            *error = [NSError errorWithDomain:ALDataEncryptionErrorDomain code:ALDataEncryptionInvalidParameter userInfo:@{NSLocalizedDescriptionKey:@"The 'attrLabel' parameter passed to a function were not valid."}];
        }
        return NULL;
    }

    // Query private key object from the keychain.
    NSDictionary *params = @{
                             (id)kSecClass: (id)kSecClassKey,
                             (id)kSecAttrKeyType: (id)kSecAttrKeyTypeECSECPrimeRandom,
                             (id)kSecAttrKeySizeInBits: @256,
                             (id)kSecAttrLabel: attrLabel,
                             (id)kSecReturnRef: @YES,
                             };

    // Retrieve the key from the keychain.  No authentication is needed at this point.

    SecKeyRef privateKey = NULL;

    OSStatus status = SecItemCopyMatching((__bridge CFDictionaryRef)params, (CFTypeRef *)&privateKey);

    if (status == errSecSuccess) {
        return privateKey;
    } else {
        if (error) {
            *error = [NSError errorWithDomain:NSOSStatusErrorDomain code:status userInfo:@{NSLocalizedDescriptionKey:ALNSStringFromOSStatusDesc(status)}];
        }
        return NULL;
    }
}

In this way, the private key can be retrieved from the key chain, digitally signed with the key or encrypted with the key.

Later, you retrieve the key from the keychain in the usual way, as described in Storing Keys in the Keychain. You also use the key to sign a block of code exactly as you would normally, as described in Signing and Verifying. Alternatively, you can use the key for encryption as described in Using Keys for Encryption and in particular for symmetric encryption using the eciesEncryptionCofactorX963SHA256AESGCM algorithm. Note that in these cases, you are restricted to the elliptic curve algorithms because the Secure Enclave holds only elliptic curve keys.

3, Delete private key

/** Remove private key from Keychain */
+ (BOOL)deletePrivateKeyFromKeychain:(NSString *)attrLabel error:(NSError **)error {
    if (!attrLabel) {
        if (error) {
            *error = [NSError errorWithDomain:ALDataEncryptionErrorDomain code:ALDataEncryptionInvalidParameter userInfo:@{NSLocalizedDescriptionKey:@"The 'attrLabel' parameter passed to a function were not valid."}];
        }
        return NO;
    }
    
    NSDictionary *query = @{
                            (id)kSecAttrTokenID: (id)kSecAttrTokenIDSecureEnclave,
                            (id)kSecClass: (id)kSecClassKey,
                            (id)kSecAttrKeyClass: (id)kSecAttrKeyClassPrivate,
                            (id)kSecAttrLabel: attrLabel,
                            (id)kSecReturnRef: @YES,
                            };

    OSStatus status = SecItemDelete((__bridge CFDictionaryRef)query);

    if (status == errSecSuccess) {
        return YES;
    } else {
        if (error) {
            *error = [NSError errorWithDomain:NSOSStatusErrorDomain code:status userInfo:@{NSLocalizedDescriptionKey:ALNSStringFromOSStatusDesc(status)}];
        }
        return NO;
    }
}

After the private key is retrieved from the key chain, a key is created through the Duffy Herman key exchange protocol. The key can be used as a symmetric key to encrypt data information.

Diffie Hellman key exchange (D-H) is a security protocol. It allows both parties to establish a key through the insecure channel without any prior information of the other party. This key can be used as a symmetric key to encrypt communication content in subsequent communication. (from Baidu Encyclopedia)

+ (NSString *)generateExchangeKeyWithAttrLabel:(NSString *)aKeyAttrLabel otherAttrLabel:(NSString *)bKeyAttrLabel sharedData:(NSData *)sharedData error:(NSError **)error {

    if (!aKeyAttrLabel || !bKeyAttrLabel || !sharedData) {
        if (error) {
            NSString *descriptionKey = [NSString stringWithFormat:@"%s : One or more parameters passed to a function were not valid.", __func__];
            *error = [NSError errorWithDomain:ALDataEncryptionErrorDomain code:ALDataEncryptionInvalidParameter userInfo:@{NSLocalizedDescriptionKey:descriptionKey}];
        }
        return nil;
    }

    SecKeyRef aPrivateKey = [self retrievePrivateFromKeychain:aKeyAttrLabel error:error];
    SecKeyRef bPrivateKey = [self retrievePrivateFromKeychain:bKeyAttrLabel error:error];

    if (aPrivateKey == NULL || bPrivateKey == NULL) {
        if (aPrivateKey) CFRelease(aPrivateKey);
        if (bPrivateKey) CFRelease(bPrivateKey);

        return nil;
    }

    SecKeyRef aPublicKey = SecKeyCopyPublicKey(aPrivateKey);

    CFErrorRef errorRef = NULL;

    NSDictionary *params = @{(id)kSecKeyKeyExchangeParameterRequestedSize : @(sharedData.length),
                              (id)kSecKeyKeyExchangeParameterSharedInfo : sharedData};

    CFDataRef dataRef = SecKeyCopyKeyExchangeResult(bPrivateKey,
                                                    kSecKeyAlgorithmECDHKeyExchangeCofactorX963SHA256,
                                                    aPublicKey,
                                                    (__bridge CFDictionaryRef)params,
                                                    &errorRef);
    if (errorRef) {
        if (dataRef) CFRelease(dataRef);
        if (error) {
            *error = (__bridge NSError *)errorRef;
        }
    } else if (dataRef) {
        NSData *nsData = (__bridge NSData *)dataRef;
        NSString *dataString = [[NSString alloc] initWithData:nsData encoding:NSUTF8StringEncoding];
        if (dataString == nil) { 
	        // When NSData is converted to NSString, nil may be returned
            NSData *data = ALUTF8NSData(nsData);
            dataString = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
        }

        CFRelease(dataRef);

        if (dataString) {
            if (aPrivateKey) CFRelease(aPrivateKey);
            if (aPublicKey) CFRelease(aPublicKey);
            if (bPrivateKey) CFRelease(bPrivateKey);

            return dataString;
        } else if (error) {
            *error = [NSError errorWithDomain:ALDataEncryptionErrorDomain code:ALDataEncryptionNSDataConvertToNSStringFailure userInfo:@{NSLocalizedDescriptionKey:@"The NSData convert to NSString failure."}];
        }
    }

    if(aPublicKey) CFRelease(aPublicKey);
    if(aPrivateKey) CFRelease(aPrivateKey);
    if(bPrivateKey) CFRelease(bPrivateKey);

    return nil;
}

3, Encryption and decryption of data with key

static NSString *const KEY_A = @"key_A";
static NSString *const KEY_B = @"key_B";

+ (NSString *)encryption:(NSString *)plainText error:(NSError **)error {
    if (!plainText) {
        if (error) {
            *error = [NSError errorWithDomain:ALDataEncryptionErrorDomain code:ALDataEncryptionInvalidParameter userInfo:@{NSLocalizedDescriptionKey:@"The `encrypted` string passed to a function were not valid."}];
        }
        return nil;
    }

    BOOL generatePrivateKeyA = [self generatePrivateKey:KEY_A error:error];
    BOOL gengratePrivateKeyB = [self generatePrivateKey:KEY_B error:error];

    if (generatePrivateKeyA && gengratePrivateKeyB) {
        NSString *identifier = [NSBundle mainBundle].bundleIdentifier;
        NSData *sharedData = [identifier dataUsingEncoding:NSUTF8StringEncoding];
        NSString *key = [self generateExchangeKeyWithAttrLabel:KEY_A otherAttrLabel:KEY_B sharedData:sharedData error:error];
        if (key) {
            NSString *iv = [[[UIDevice currentDevice] identifierForVendor] UUIDString];

            NSData *plainData = [plainText dataUsingEncoding:NSUTF8StringEncoding];

            NSData *encryptedData = [plainData aes256EncryptWithIv:iv forKey:key];

            if (encryptedData) {
                return [encryptedData base64EncodedStringWithOptions:NSDataBase64EncodingEndLineWithLineFeed];
            } else if (error) {
                *error = [NSError errorWithDomain:ALDataEncryptionErrorDomain code:ALDataEncryptionFailure userInfo:@{NSLocalizedDescriptionKey:@"The AES256 encrypt failure"}];
                return nil;
            }
        }
    }

    return nil;
}

+ (NSString *)decryption:(NSString *)plainText error:(NSError **)error {
    if (!plainText) {
        if (error) {
            *error = [NSError errorWithDomain:ALDataEncryptionErrorDomain code:ALDataEncryptionInvalidParameter userInfo:@{NSLocalizedDescriptionKey:@"The `decrypted` string passed to a function were not valid."}];
        }
        return nil;
    }

    NSString *identifier = [NSBundle mainBundle].bundleIdentifier;
    NSData *sharedData = [identifier dataUsingEncoding:NSUTF8StringEncoding];
    NSString *key = [self generateExchangeKeyWithAttrLabel:KEY_A otherAttrLabel:KEY_B sharedData:sharedData error:error];

    if (key) {
        NSString *iv = [[[UIDevice currentDevice] identifierForVendor] UUIDString];
        NSData *plainData = [[NSData alloc] initWithBase64EncodedString:plainText options:NSDataBase64DecodingIgnoreUnknownCharacters];
        NSData *decryptedData = [plainData aes256DecryptWithIv:iv forKey:key];
        if (decryptedData) {
            NSString *result = [[NSString alloc] initWithData:decryptedData encoding:NSUTF8StringEncoding];
            return result;
        } else if (error) {
            *error = [NSError errorWithDomain:ALDataEncryptionErrorDomain code:ALDataDecryptionFailure userInfo:@{NSLocalizedDescriptionKey:@"The AES256 decrypt failure"}];
            return nil;
        }
    }

    return nil;
}

The above is to use AES256 symmetric encryption algorithm to encrypt and decrypt strings.

Finally, after encryption and decryption, the generated private key is removed from the key chain.

+ (BOOL)cleanPrivateKeyFromKeychainWithError:(NSError * __autoreleasing *)error {
    NSError *err = nil;
    BOOL deletePrivateKeyA = [self deletePrivateKeyFromKeychain:KEY_A error:&err];
    if (err) {
        if (error) *error = err;
        return deletePrivateKeyA;
    }
    BOOL deletePrivateKeyB = [self deletePrivateKeyFromKeychain:KEY_B error:&err];
    if (err) {
        if (error) *error = err;
        return deletePrivateKeyB;
    }
    
    return deletePrivateKeyA && deletePrivateKeyB;
}

Matters needing attention

  • Some of the technologies mentioned in this article are new features of iOS 10.0. Please do a good job of version control when using them.

  • After our test, it is found that when generating the private key, the parameter protection of the function SecAccessControlRef SecAccessControlCreateWithFlags() is set to ksecattraccessiblehenpasscodesetthisdeviceonly, and the exchanged Data can be obtained by using cfdataref ﹣ nullable seckeycopykeyexchangeresult() function. However, when the user manually deletes the passcode and adds the passcode again, the exchanged key will not be obtained all the time and will be returned to nil. There is bound to be a loophole.

  • As for the above problems, it's not clear what causes them, so we'll replace it with ksecattr accessible when unlocked this device only later. Fortunately, the above problems have been solved. Even after deletion, adding passcode again will get the exchanged key normally.

  • However, we found a problem later. After we changed the key to ksecattraccessiblewinenlockedthisdeviceonly, we still couldn't get the exchanged Data on the devices of iOS 10.0.1 and iOS 10.0.2 system versions, that is, the function SecKeyCopyKeyExchangeResult() always returned nil, and returned an error error error error domain = com. Apply. Localauthentication code = - 1009 "ACL operation is not allowed: 'ock'" UserInfo={NSLocalizedDescription=ACL operation is not allowed: 'ock'}. It can be acquired normally on iOS 10.1.1, 10.2.1, 10.3.1 and 10.3.2. What can I do? I'm helpless, too.

  • So when using the examples in this tutorial, you should be very careful with the above pits. Remember, remember.

Reference link

  • https://developer.apple.com/library/content/documentation/Security/Conceptual/CertKeyTrustProgGuide/SecureKeyGen.html
  • https://developer.apple.com/reference/uikit/uidevice/1620059-identifierforvendor?language=objc
  • http://www.cocoachina.com/ios/20151231/14835.html
  • http://blog.csdn.net/lee244868149/article/details/51790397
Published 36 original articles, won praise 10, visited 100000+
Private letter follow

Posted by illushinz on Mon, 09 Mar 2020 20:38:32 -0700