iOS Reverse Engineering – Encryption Algorithms

πŸ‘‡πŸ‘‡Follow and reply “Join Group” to be added to the Programmer Group ChatπŸ‘‡πŸ‘‡
iOS Reverse Engineering - Encryption Algorithms
Author | iOS_BigBook
Source | Juejin
https://juejin.cn/post/7010665487809904647

1. Classification of Encryption Algorithms

Hash (hash) functions: Not encryption algorithms. For example, MD5, SHA1/256/512
Symmetric encryption algorithms: DES, 3DES, AES (Advanced Encryption Standard, the keychain on Mac computers uses AES)
Asymmetric encryption algorithms: RSA

1. Hash

Hash, generally translated as “hash”, can also be directly translated as “hashing”, which transforms an input of arbitrary length into a fixed-length output through a hashing algorithm; this output is the hash value.
This transformation is a compression mapping, meaning that the space of the hash value is usually much smaller than the input space. Different inputs may hash to the same output, so it is impossible to determine a unique input value from the hash value. In simple terms, it is a function that compresses a message of arbitrary length into a fixed-length message digest; the most famous hash algorithm is MD5.
Characteristics of Hash:
  • The algorithm is public

  • For the same data, the result is the same

  • For different data operations, such as MD5, the result is by default 128 bits, which is 32 characters (hexadecimal representation)

  • Cannot be reversed

  • Mainly used as information summary, information fingerprint, for data identification

Uses:
  • Encryption of user passwords: Because it cannot be restored, the encryption is very good; even if leaked, the real user password cannot be reversed

  • Search engines

  • Copyright

  • Digital signatures

Password Encryption Methods:
  • Directly using MD5

  • MD5 with salt

  • HMAC encryption scheme

  • Add some complexity

When we need to send the password to the server, we need to encrypt the password. If we use RSA asymmetric encryption, the efficiency will be very low and there are security risks because after encrypting with RSA, the password will be decrypted and matched. However, this means the server needs to store the password in plaintext, which is very unsafe:
For password transmission over the network, there are two principles:
  • Plaintext transmission of user’s private information is not allowed over the network

  • Plaintext storage of user’s private information is not allowed locally

Therefore, MD5 conversion can be used; even if the ciphertext is lost, the user’s original password cannot be cracked. Let’s look at the MD5 conversion code:
- (NSString *)md5String {
    const char *str = self.UTF8String;
    uint8_t buffer[CC_MD5_DIGEST_LENGTH];
    // Pass the string pointer, the length of the string, and the space to store the ciphertext; the space is generally 16 bytes, and after encryption, the ciphertext will be stored in hexadecimal format in the space,
    CC_MD5(str, (CC_LONG)strlen(str), buffer);
    // After obtaining the ciphertext, we concatenate it to get a ciphertext string
    return [self stringFromBytes:buffer length:CC_MD5_DIGEST_LENGTH];
}

- (NSString *)stringFromBytes:(uint8_t *)bytes length:(int)length {
    NSMutableString *strM = [NSMutableString string];

    for (int i = 0; i < length; i++) {
        [strM appendFormat:"%02x", bytes[i]];
    }
    return [strM copy];
}

We encrypt 123456: we get this hexadecimal string
e10adc3949ba59abbe56e057f20f883e
However, now that MD5 cannot be cracked, because the ciphertext corresponding to the same string remains unchanged, we can match the corresponding string through the ciphertext. For example, on CMD5, we can match common strings through passwords.
iOS Reverse Engineering - Encryption Algorithms
At this point, we need to salt the string before performing MD5 conversion; salting means appending a complex string to the original string before performing MD5 conversion. However, there is a downside: the salt is fixed and must be written into the program, which can also be brute-forced. Therefore, we can use the HMAC method for encryption:

HMAC:

  • Uses a key for encryption and performs hashing twice

  • In actual development, the key comes from the server and is dynamic

  • One account corresponds to one key, and it can also be updated. When we register or change devices, we send the key to the client, which caches it locally. Then, each time we log in, we retrieve it locally to encrypt the password. This way, we can add a device lock, requiring consent from the original device to log in on a new device.

Apple also provides the CCHmac method:
#pragma mark - HMAC Hash Function
- (NSString *)hmacMD5StringWithKey:(NSString *)key {
    const char *keyData = key.UTF8String;
    const char *strData = self.UTF8String;
    uint8_t buffer[CC_MD5_DIGEST_LENGTH];

    CCHmac(kCCHmacAlgMD5, keyData, strlen(keyData), strData, strlen(strData), buffer);

    return [self stringFromBytes:buffer length:CC_MD5_DIGEST_LENGTH];
}
Because if we only use the MD5 ciphertext for each login, it will lead to the same information being sent to the server each time, which is quite dangerous. If a hacker intercepts it, they can use the request data to crack it later,
So we can add a timestamp (server time); this way, each request is different. Even if a hacker intercepts it, it doesn’t matter because of the delay issue, so the timestamp is only accurate to the minute. The method of adding a timestamp is as follows:
  • 1. Before logging in, request the server’s timestamp

  • 2. Then, on the client side, encrypt the password using HMAC and the key to obtain ciphertext 1

  • 3. Concatenate ciphertext 1 with the timestamp and perform another HMAC and key encryption to obtain ciphertext 2

  • 4. Send ciphertext 2 to the server for verification. The server retrieves the stored user password ciphertext (ciphertext 1 concatenated with the current timestamp) and performs HMAC and key encryption to match it with the ciphertext sent from the client. If they match, verification is successful.

  • If they do not match, concatenate ciphertext 1 with the timestamp from one minute prior to the server’s timestamp and match again. If they match, it passes; if not, it returns that the password is incorrect.

Search Engines:

For each word, obtain the hash value and sum these hash values for searching. Thus, regardless of the order of the input words, the content will be the same sentence. For example, Zhang San, Li Si, Wang Wu, the hash values of these three words are the same and are all 32-bit hexadecimal numbers, regardless of which comes first, the sum is the same.

Copyright:

For the original file, obtain a hash value through hash operations. The original file contains a hash value. If a pirated file is uploaded to Baidu Cloud, it will be rejected due to the incorrect hash value. Moreover, Baidu Cloud has a feature for instant upload; this is because the cloud already has the file. By matching the hash, it is found that the file already exists, so there is no need to upload it again.

Digital Signatures:

Why use the term signature? Because foreigners like to use checks, and the signature on a check proves that it is yours. Digital signatures are methods for verifying digital information.
The client has payment information, and third parties may attack and tamper with the payment information, which could cause the customer to pay more. How to prevent this?
  • Convert the payment information into MD5

  • Then perform RSA encryption on the MD5 ciphertext to obtain a ciphertext, which is the digital signature.

  • After passing it to the server, the server decrypts the ciphertext with RSA to obtain MD5 ciphertext 1, converts the payment information into MD5 to obtain ciphertext 2, and then matches ciphertext 1 and ciphertext 2. If they are the same, it means the payment information has not been modified.

Because the hash is always a 128-bit number, regardless of how large the number is, there will definitely be some data that produces the same hash value; this is called a hash collision.

2. Symmetric Encryption:

Symmetric encryption means plaintext can be converted to ciphertext using a key, and ciphertext can be decrypted back to plaintext using the same key.
Common Algorithms:
  • DES: Data Encryption Standard (used less because its strength is insufficient)

  • 3DES: Uses three keys to perform three encryptions on the same data, enhancing strength, but using three keys is cumbersome, so it is used less.

  • AES: Advanced Encryption Standard, used for keychain access, also used by the FBI for encryption.

XOR is generally used for conventional encryption of game data.
Application Modes:
  • ECB: Electronic Codebook mode, each block of data is encrypted independently. The most basic encryption mode, meaning that the same plaintext will always be encrypted into the same ciphertext without an initialization vector. Since each block of data is independent, it is vulnerable to password replay attacks and is rarely used in practice.

  • CBC: Cipher Block Chaining mode, uses a key and an initialization vector (IV) to perform encryption on the data. The plaintext is XORed with the previous ciphertext before encryption, so by choosing different initialization vectors, the same password will produce different ciphertext. This is currently the most widely used mode. The ciphertext generated by CBC is contextually related, but errors in plaintext will not propagate to subsequent blocks. However, if one block is lost, all subsequent blocks will be invalid, meaning that the data is all interconnected. The encryption and decryption of the subsequent data block depend on the previous data block. If one block is damaged, the entire data cannot be parsed.

  • CBC can effectively ensure the integrity of the ciphertext. If a data block is lost or altered during transmission, subsequent data will be unable to decrypt properly.

By encrypting through terminal commands, we will find the AES terminal encryption and decryption commands:
/**
 *  Terminal test commands
 *
 *  DES(ECB) encryption
 *  $ echo -n hello | openssl enc -des-ecb -K 616263 -nosalt | base64
 *
 * DES(CBC) encryption
 *  $ echo -n hello | openssl enc -des-cbc -iv 0102030405060708 -K 616263 -nosalt | base64
 *
 *  AES(ECB) encryption
 *  $ echo -n hello | openssl enc -aes-128-ecb -K 616263 -nosalt | base64
 *
 *  AES(CBC) encryption
 *  $ echo -n hello | openssl enc -aes-128-cbc -iv 0102030405060708 -K 616263 -nosalt | base64
 *
 *  DES(ECB) decryption
 *  $ echo -n HQr0Oij2kbo= | base64 -D | openssl enc -des-ecb -K 616263 -nosalt -d
 *
 *  DES(CBC) decryption
 *  $ echo -n alvrvb3Gz88= | base64 -D | openssl enc -des-cbc -iv 0102030405060708 -K 616263 -nosalt -d
 *
 *  AES(ECB) decryption
 *  $ echo -n d1QG4T2tivoi0Kiu3NEmZQ== | base64 -D | openssl enc -aes-128-ecb -K 616263 -nosalt -d
 *
 *  AES(CBC) decryption
 *  $ echo -n u3W/N816uzFpcg6pZ+kbdg== | base64 -D | openssl enc -aes-128-cbc -iv 0102030405060708 -K 616263 -nosalt -d
 *
 *  Note:
 *      1> The encryption process is to encrypt first, then base64 encode
 *      2> The decryption process is to first base64 decode, then decrypt
 */

Encryption Code:

Both encryption and decryption actually use the CCCrypt function, just with different types passed in.
    /** AES - ECB */
    NSString * key = @"abc";
    NSString * encStr = [[EncryptionTools sharedEncryptionTools] encryptString:@"hello" keyString:key iv:nil];

    NSLog(@"The result of encryption is: %@", encStr);

    NSLog(@"The result of decryption is: %@", [[EncryptionTools sharedEncryptionTools] decryptString:encStr keyString:key iv:nil]);

    /** AES - CBC encryption */
    uint8_t iv[8] = {1,2,3,4,5,6,7,8};
    NSData * ivData = [NSData dataWithBytes:iv length:sizeof(iv)];

    NSLog(@"CBC encryption: %@", [[EncryptionTools sharedEncryptionTools] encryptString:@"hello" keyString:@"abc" iv:ivData]);

    NSLog(@"Decryption: %@", [[EncryptionTools sharedEncryptionTools] decryptString:@"u3W/N816uzFpcg6pZ+kbdg==" keyString:key iv:ivData]);

    - (NSString *)encryptString:(NSString *)string keyString:(NSString *)keyString iv:(NSData *)iv {

    // Set the key
    NSData *keyData = [keyString dataUsingEncoding:NSUTF8StringEncoding];
    uint8_t cKey[self.keySize];
    bzero(cKey, sizeof(cKey));
    [keyData getBytes:cKey length:self.keySize];

    // Set iv
    uint8_t cIv[self.blockSize];
    bzero(cIv, self.blockSize);
    int option = 0;
    if (iv) {
        [iv getBytes:cIv length:self.blockSize];
        option = kCCOptionPKCS7Padding;
    } else {
        option = kCCOptionPKCS7Padding | kCCOptionECBMode;
    }

    // Set output buffer
    NSData *data = [string dataUsingEncoding:NSUTF8StringEncoding];
    size_t bufferSize = [data length] + self.blockSize;
    void *buffer = malloc(bufferSize);

    // Start encryption
    size_t encryptedSize = 0;
    // Encryption and decryption both use this -- CCCrypt
    CCCryptorStatus cryptStatus = CCCrypt(kCCEncrypt,
                                          self.algorithm,
                                          option,
                                          cKey,
                                          self.keySize,
                                          cIv,
                                          [data bytes],
                                          [data length],
                                          buffer,
                                          bufferSize,
                                          &encryptedSize);

    NSData *result = nil;
    if (cryptStatus == kCCSuccess) {
        result = [NSData dataWithBytesNoCopy:buffer length:encryptedSize];
    } else {
        free(buffer);
        NSLog(@"[Error] Encryption failed | Status code: %d", cryptStatus);
    }

    return [result base64EncodedStringWithOptions:0];
}

- (NSString *)decryptString:(NSString *)string keyString:(NSString *)keyString iv:(NSData *)iv {

    // Set the key
    NSData *keyData = [keyString dataUsingEncoding:NSUTF8StringEncoding];
    uint8_t cKey[self.keySize];
    bzero(cKey, sizeof(cKey));
    [keyData getBytes:cKey length:self.keySize];

    // Set iv
    uint8_t cIv[self.blockSize];
    bzero(cIv, self.blockSize);
    int option = 0;
    if (iv) {
        [iv getBytes:cIv length:self.blockSize];
        option = kCCOptionPKCS7Padding; // CBC encryption!
    } else {
        option = kCCOptionPKCS7Padding | kCCOptionECBMode; // ECB encryption!
    }

    // Set output buffer
    NSData *data = [[NSData alloc] initWithBase64EncodedString:string options:0];
    size_t bufferSize = [data length] + self.blockSize;
    void *buffer = malloc(bufferSize);

    // Start decryption
    size_t decryptedSize = 0;
    /** CCCrypt is the core function of symmetric encryption algorithm (encryption/decryption)
     Parameters:
     1. kCCEncrypt for encryption / kCCDecrypt for decryption
     2. Encryption algorithm, default AES/DES
     3. Encryption option
        kCCOptionPKCS7Padding | kCCOptionECBMode; // ECB encryption!
        kCCOptionPKCS7Padding; // CBC encryption!
     4. Encryption key
     5. Key length
     6. iv initialization vector, ECB does not need to specify
     7. Data to be encrypted
     8. Length of the data to be encrypted
     9. Buffer (address) to store the ciphertext
     10. Size of the buffer
     11. Size of the encryption result
     */
    CCCryptorStatus cryptStatus = CCCrypt(kCCDecrypt,
                                          self.algorithm,
                                          option,
                                          cKey,
                                          self.keySize,
                                          cIv,
                                          [data bytes],
                                          [data length],
                                          buffer,
                                          bufferSize,
                                          &decryptedSize);

    NSData *result = nil;
    if (cryptStatus == kCCSuccess) {
        result = [NSData dataWithBytesNoCopy:buffer length:decryptedSize];
    } else {
        free(buffer);
        NSLog(@"[Error] Decryption failed | Status code: %d", cryptStatus);
    }

    return [[NSString alloc] initWithData:result encoding:NSUTF8StringEncoding];
}

3. Problems with Encryption Algorithms

When we encrypt, we directly convert the string to NSData and pass it to the system’s encryption function. However, for hackers, since the encryption function is a system function, they can hook the system function to access the data that is about to be encrypted, which is very unsafe.
We can intercept the system function CCCrypt through symbolic breakpoints because the data being encrypted is the seventh parameter; we can check the data in register x6.
iOS Reverse Engineering - Encryption Algorithms
Author | iOS_BigBook
Link | Click the lower left “Read Original” to enter the author’s homepage

-End-

Recently, some friends asked me to help find some interview questions materials, so I rummaged through my collection of 5T materials and compiled them. It can be said to be essential for programmer interviews! All materials have been organized into a cloud storage, welcome to download!

iOS Reverse Engineering - Encryption Algorithms

Click the card above, follow, and reply 【<strong>Interview Questions</strong>】 to get it

Look here for more good contentiOS Reverse Engineering - Encryption AlgorithmsShare good articles with more people↓↓

Leave a Comment