Implementation of iOS-AES encryption and decryption modes (ECB, CBC, CFB, OFB)

Preface

Recently, I used AES encryption when encrypting and decrypting data on the interface with my server classmates. I originally thought that AES was just a form of encryption, but during the docking process I learned that different AES modes and padding methods produce different results. Therefore, I went to learn the basic concepts and implementation principles of AES encryption, as well as the differences and implementations in various modes.

1. Concept

AES encryption is a type of symmetric encryption, and its full name is Advanced Encryption Standard. Commonly used for data encryption and decryption in network transmission.

This is an AES online encryption tool. It can be seen from the content on the website that in addition to requiring a secret key (Key) for encryption and decryption, AES also has multiple modes. Different modes have different encryption methods and results. There are also parameters such as key length, initial vector, and filling method, and the results are also different. Here is a brief introduction to some concepts and parameters of AES encryption:

  • Grouping (or block)  : AES is a block encryption technology, which divides plaintext into groups, each group is of equal length, and encrypts one group of data at a time until the entire plaintext is encrypted. In the AES standard specification, the packet length can only be 128 bits, that is, each packet is 16 bytes (16bytes = 128bits / 8).
  • Key length : The key length supported by AES can be 128 bits, 192 bits or 256 bits. The length of the key is different, and the recommended number of encryption rounds is also different, as shown in the following table:

  • Encryption mode : Because block encryption can only encrypt fixed-length blocks, and the actual plaintext that needs to be encrypted may exceed the block length, the block cipher algorithm must be iterated to complete the encryption of the entire plaintext. The iterative method is the encryption mode. There are many types of it. The common working modes are as follows:

  • Initialization Vector (IV, Initialization Vector)  : The purpose is to prevent the same plaintext block from always being encrypted into the same ciphertext block. Take CBC mode as an example:

Before each plaintext block is encrypted, the plaintext block is XORed with a value. IV serves as an initialization variable and participates in the XOR of the first plaintext block. Each subsequent plaintext block is XORed with the ciphertext block encrypted by the previous plaintext block, thereby ensuring that the encrypted ciphertext blocks are different.

  • Padding  : Since the key can only process data blocks of a certain length, and the length of the data is usually variable, 最后一块additional processing is required to pad the data before encryption. Commonly used modes include PKCS5, PKCS7, etc.
Filling method illustrate Example (assuming block length 8, data length 9)
None No padding
PKCS7 A padding string consists of a sequence of bytes, each byte padded with the length of that byte sequence. Number of octets to pad, equal to 7: Data: FF FF FF FF FF FF FF FF FFPKCS7 Pad: FF FF FF FF FF FF FF FF FF 07 07 07 07 07 07 07
PKCS5 Usually common with PKCS7. The difference is that PKCS5 clearly defines the size of the Block to be 8 bits, while PKCS7 is not sure
ANSIX923 A padding string consists of a byte sequence, the last byte of which is padded with the length of the byte sequence, and the remaining bytes are padded with the number zero Data: FF FF FF FF FF FF FF FF FFX923 Filling: FF FF FF FF FF FF FF FF 00 00 00 00 00 00 07
ISO10126 The padding string consists of a byte sequence, the last byte of this byte sequence is padded with the length of the byte sequence, and the remaining bytes are padded with random data. 数据: FF FF FF FF FF FF FF FF FFISO10126 填充: FF FF FF FF FF FF FF FF FF 7D 2A 75 EF F8 EF 07
Zeros The padding string consists of bytes set to zero

2. Brief description of the principle

In the AES encryption function, a round function will be executed, and the 执行10次这个轮函数first 9 operations of this round function are the same, only the 10th time is different. In other words, a plaintext packet will be encrypted for 10 rounds.

The processing unit of AES is bytes, and the 128-bit input plaintext packet P is divided into 16 bytes.

Assume that the plaintext grouping is P = abcdefghijklmnop. Plaintext packets are described by a square matrix in bytes, called the state matrix. In each round of encryption, the contents of the state matrix continue to change, and the final result is output as ciphertext. The order of bytes in this matrix is ​​from top to bottom and from left to right. The process of generating the state matrix diagram is shown in the following figure:

In the above figure, 0x61 is the hexadecimal representation of the character a, and the others are the same.

After the plaintext is encrypted by AES, it has been changed beyond recognition.

And what exactly did these 10 rounds of encryption do? It mainly includes 4 operations: byte substitution, row displacement, column mixing and round key addition. The last iteration does not perform column blending. In addition, before the first round of iteration, an XOR encryption operation is performed on the plaintext and the original key.

Similarly, the AES decryption process is still 10 rounds, and the operation of each round is the reverse operation of the encryption operation. Similar to the encryption operation, the last round of reverse column mixing is not performed. Before the first round of decryption, a key addition operation is performed.

The specific operations of AES encryption can be found in detail in the article Detailed Introduction to the AES Encryption Algorithm. This is only a brief introduction without further explanation.

3. Code implementation in iOS

1. ECB mode is not recommended

Under normal circumstances, if an iOS developer has not been exposed to AES encryption in detail, when a back-end colleague tells you that the client needs AES encryption and decryption, he or she will subconsciously go online to find the code and copy it directly. The most common encryption method on the Internet now, and the one most commonly used by everyone, is actually the encryption method of AES128 (that is, the key length is 128), ECB mode, and PKCS7 padding.

The ECB mode is the least recommended encryption mode in AES encryption!

The following figure shows the encryption process of the block cipher algorithm in ECB mode:

As can be seen from the above figure, repeated arrangements in the plaintext will be reflected in the ciphertext (that is, the order in which the plaintext is grouped is the order in which the ciphertext is grouped).

When the ciphertext is tampered with, the corresponding plaintext grouping after decryption will also be incorrect, and the decryptor will not notice that the ciphertext has been tampered with. In other words, ECB cannot provide integrity verification of ciphertext. Therefore, ECB mode is not recommended under any circumstances.

2. iOS implements AES encryption and decryption in various modes

In iOS development, the official CommonCrypto.framework provides implementation of commonly used encryption methods, including the AES encryption algorithm (in addition to DES, blowfish, etc.).

For AES encryption, Apple officially provides three function interfaces, which are CCCryptorcreate(), CCCryptorCreateFromData(), and CCCryptorCreateWithMode(). CCCryptorCreateWithMode() is used below to implement the four common modes of AES encryption: ECB, CBC, CFB, OFB.

(1) Supported modes

Because there is a CCMode macro in the framework, which contains four modes: ECB, CBC, CFB, and OFB, and this macro only has parameters in CCCryptorCreateWithMode(). In order to compare the correctness of the encrypted data, I used the results of online AES encryption and decryption to compare. There are only 4 modes on the website: ECB, CBC, CFB, and OFB, so my code only implements these 4 modes for the time being.

(2) Supported key length

By default, the system supports three lengths: 128, 192, and 256.

(3) Supported filling methods

The system only provides PKCS7Pading and NoPading (no padding). Here we learn from the boss's blog aescfb encryption_iOS AES encryption (mainly using CFB mode) to implement four filling methods: PKCS7Pading, ZeroPadding, ANSIX923, and ISO10126.

Show Code directly:

(1)MIUAES.h

//
//  MIUAES.h
//#import <Foundation/Foundation.h>
#import <CommonCrypto/CommonCryptor.h>NS_ASSUME_NONNULL_BEGINtypedef enum : NSUInteger {MIUCryptorNoPadding = 0,    // 无填充MIUCryptorPKCS7Padding = 1, // PKCS_7 | 每个字节填充字节序列的长度。 ***此填充模式使用系统方法。***MIUCryptorZeroPadding = 2,  // 0x00 填充 | 每个字节填充 0x00MIUCryptorANSIX923,       // 最后一个字节填充字节序列的长度,其余字节填充0x00。MIUCryptorISO10126          // 最后一个字节填充字节序列的长度,其余字节填充随机数据。
}MIUCryptorPadding;typedef enum {MIUKeySizeAES128          = 16,MIUKeySizeAES192          = 24,MIUKeySizeAES256          = 32,
}MIUKeySizeAES;typedef enum {MIUModeECB        = 1,MIUModeCBC        = 2,MIUModeCFB        = 3,MIUModeOFB        = 7,
}MIUMode;@interface MIUAES : NSObject+ (NSString *)MIUAESEncrypt:(NSString *)originalStrmode:(MIUMode)modekey:(NSString *)keykeySize:(MIUKeySizeAES)keySizeiv:(NSString * _Nullable )ivpadding:(MIUCryptorPadding)padding;+ (NSString *)MIUAESDecrypt:(NSString *)originalStrmode:(MIUMode)modekey:(NSString *)keykeySize:(MIUKeySizeAES)keySizeiv:(NSString * _Nullable )ivpadding:(MIUCryptorPadding)padding;@endNS_ASSUME_NONNULL_END 

(2)MIUAES.m

//
//  MIUAES.m
//#import "MIUAES.h"
#import "MIUGTMBase64.h"@implementation MIUAES+ (NSString *)MIUAESEncrypt:(NSString *)originalStrmode:(MIUMode)modekey:(NSString *)keykeySize:(MIUKeySizeAES)keySizeiv:(NSString * _Nullable )ivpadding:(MIUCryptorPadding)padding;
{NSData *data = [originalStr dataUsingEncoding:NSUTF8StringEncoding];data = [self MIUAESWithData:data operation:kCCEncrypt mode:mode key:key keySize:keySize iv:iv padding:padding];//base64加密(可自己去实现)return [MIUGTMBase64 stringByEncodingData:data];
}+ (NSString *)MIUAESDecrypt:(NSString *)originalStrmode:(MIUMode)modekey:(NSString *)keykeySize:(MIUKeySizeAES)keySizeiv:(NSString * _Nullable )ivpadding:(MIUCryptorPadding)padding
{//base64解密(可自己去实现)NSData *data = [MIUGTMBase64 decodeData:[originalStr dataUsingEncoding:NSUTF8StringEncoding]];data = [self MIUAESWithData:data operation:kCCDecrypt mode:mode key:key keySize:keySize iv:iv padding:padding];return [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
}+ (NSData *)MIUAESWithData:(NSData *)originalDataoperation:(CCOperation)operationmode:(CCMode)modekey:(NSString *)keykeySize:(MIUKeySizeAES)keySizeiv:(NSString *)ivpadding:(MIUCryptorPadding)padding
{NSAssert((mode != kCCModeECB && iv != nil && iv != NULL) || mode == kCCModeECB, @"使用 CBC 模式,initializationVector(即iv,填充值)必须有值");CCCryptorRef cryptor = NULL;CCCryptorStatus status = kCCSuccess;NSMutableData * keyData = [[key dataUsingEncoding: NSUTF8StringEncoding] mutableCopy];NSMutableData * ivData = [[iv dataUsingEncoding: NSUTF8StringEncoding] mutableCopy];#if !__has_feature(objc_arc)[keyData autorelease];[ivData autorelease];
#endif[keyData setLength:keySize];[ivData setLength:keySize];//填充模式(系统API只提供了两种)CCPadding paddingMode = (padding == ccPKCS7Padding) ? ccPKCS7Padding : ccNoPadding ;NSData *sourceData = originalData;if (operation == kCCEncrypt) {sourceData =  [self bitPaddingWithData:originalData mode:mode padding:padding];    //FIXME: 实际上的填充模式}status = CCCryptorCreateWithMode(operation, mode, kCCAlgorithmAES, paddingMode, ivData.bytes, keyData.bytes, keyData.length, NULL, 0, 0, 0, &cryptor);if ( status != kCCSuccess ){NSLog(@"Encrypt Error:%d",status);return nil;}//确定处理给定输入所需的输出缓冲区大小尺寸。size_t bufsize = CCCryptorGetOutputLength( cryptor, (size_t)[sourceData length], true );void * buf = malloc( bufsize );size_t bufused = 0;size_t bytesTotal = 0;//处理(加密,解密)一些数据。如果有结果的话,写入提供的缓冲区.status = CCCryptorUpdate( cryptor, [sourceData bytes], (size_t)[sourceData length],buf, bufsize, &bufused );if ( status != kCCSuccess ){NSLog(@"Encrypt Error:%d",status);free( buf );return nil;}bytesTotal += bufused;if (padding == MIUCryptorPKCS7Padding) {status = CCCryptorFinal( cryptor, buf + bufused, bufsize - bufused, &bufused );if ( status != kCCSuccess ){NSLog(@"Encrypt Error:%d",status);free( buf );return nil;}bytesTotal += bufused;}NSData *result = [NSData dataWithBytesNoCopy:buf length: bytesTotal];if (operation == kCCDecrypt) {//解密时移除填充result = [self removeBitPaddingWithData:result mode:mode operation:operation andPadding:padding];}CCCryptorRelease(cryptor);return result;
}// 填充需要加密的字节
+ (NSData *)bitPaddingWithData:(NSData *)datamode:(CCMode)modepadding:(MIUCryptorPadding)padding;
{NSMutableData *sourceData = data.mutableCopy;int blockSize = kCCBlockSizeAES128;       //FIXME: AES的块大小都是128bit,即16bytesswitch (padding) {case MIUCryptorPKCS7Padding:{if (mode == kCCModeCFB || mode == kCCModeOFB) {//MARK: CCCryptorCreateWithMode方法在这两个模式下,并不会给块自动填充,所以需要手动去填充NSUInteger shouldLength = blockSize * ((sourceData.length / blockSize) + 1);NSUInteger diffLength = shouldLength - sourceData.length;uint8_t *bytes = malloc(sizeof(*bytes) * diffLength);for (NSUInteger i = 0; i < diffLength; i++) {// 补全缺失的部分bytes[i] = diffLength;}[sourceData appendBytes:bytes length:diffLength];}}break;case MIUCryptorZeroPadding:{int pad = 0x00;int diff = blockSize - (sourceData.length % blockSize);for (int i = 0; i < diff; i++) {[sourceData appendBytes:&pad length:1];}}break;case MIUCryptorANSIX923:{int pad = 0x00;int diff = blockSize - (sourceData.length % blockSize);for (int i = 0; i < diff - 1; i++) {[sourceData appendBytes:&pad length:1];}[sourceData appendBytes:&diff length:1];}break;case MIUCryptorISO10126:{int diff = blockSize - (sourceData.length % blockSize);for (int i = 0; i < diff - 1; i++) {int pad  = arc4random() % 254 + 1;      //FIXME: 因为是随机填充,所以相同参数下,每次加密都是不一样的结果(除了分段后最后一个分段的长度为15bytes的时候加密结果相同)[sourceData appendBytes:&pad length:1];}[sourceData appendBytes:&diff length:1];}break;default:break;}return sourceData;
}+ (NSData *)removeBitPaddingWithData:(NSData *)sourceData mode:(CCMode)mode operation:(CCOperation)operation andPadding:(MIUCryptorPadding)padding
{int correctLength = 0;int blockSize = kCCBlockSizeAES128;Byte *testByte = (Byte *)[sourceData bytes];char end = testByte[sourceData.length - 1];if (padding == MIUCryptorPKCS7Padding) {if ((mode == kCCModeCFB || mode == kCCModeOFB) && (end > 0 && end < blockSize + 1)) {correctLength = (short)sourceData.length - end;}else{return sourceData;}}else if (padding == MIUCryptorZeroPadding && end == 0) {for (int i = (short)sourceData.length - 1; i > 0 ; i--) {if (testByte[i] != end) {correctLength = i + 1;break;}}}else if ((padding == MIUCryptorANSIX923 || padding == MIUCryptorISO10126) && (end > 0 && end < blockSize + 1)){correctLength = (short)sourceData.length - end;}NSData *data = [NSData dataWithBytes:testByte length:correctLength];return data;
}@end 

It should be noted that the ISO10126 filling standard is filled randomly every time. Therefore, except for the case where the last segment length is 15 bits (because the 15 bits length only needs to be filled with one bit, and the content of this bit is fixed, that is, the length is 01), the encryption results are different each time in other cases. Because the decryption of the last block removes the padding first and then decrypts it, it does not affect the decryption.

There is nothing else to explain, there are comments in the code. The detailed process of encryption and decryption does not need to be implemented, it is already implemented in CCCryptorCreateWithMode().

Demo source code:

Code cloud: gitee.com/ztfiso/MIUA…

Github: github.com/Ztfiso/MIUA…

Summarize

AES is the most common symmetric encryption mode in the industry. When we use it, we must not only be able to use it, but also have a general understanding of its different modes and parameter differences. When connecting with the backend, the client code can be written according to the rules set by the backend.

Guess you like

Origin blog.csdn.net/u013712343/article/details/132472634