Implementación de modos de cifrado y descifrado iOS-AES (ECB, CBC, CFB, OFB)

Prefacio

Recientemente, utilicé el cifrado AES al cifrar y descifrar datos en la interfaz con mis compañeros de servidor. Originalmente pensé que AES era solo una forma de cifrado, pero durante el proceso de acoplamiento aprendí que diferentes modos AES y métodos de relleno producen resultados diferentes. Por lo tanto, fui a aprender los conceptos básicos y los principios de implementación del cifrado AES, así como las diferencias e implementaciones en varios modos.

1. Concepto

El cifrado AES es un tipo de cifrado simétrico y su nombre completo es Estándar de cifrado avanzado. Comúnmente utilizado para el cifrado y descifrado de datos en la transmisión de red.

Esta es una herramienta de cifrado en línea AES. Se puede ver en el contenido del sitio web que, además de requerir una clave secreta (Clave) para el cifrado y descifrado, AES también tiene múltiples modos, diferentes modos tienen diferentes métodos y resultados de cifrado. También hay parámetros como la longitud de la clave, el vector inicial y el método de llenado, y los resultados también son diferentes. A continuación se ofrece una breve introducción a algunos conceptos y parámetros del cifrado AES:

  • Agrupación (o bloque)  : AES es una tecnología de cifrado de bloques que divide el texto sin formato en grupos, cada grupo tiene la misma longitud, y cifra un grupo de datos a la vez hasta que se cifra todo el texto sin formato. En la especificación estándar AES, la longitud del paquete solo puede ser de 128 bits, es decir, cada paquete tiene 16 bytes (16 bytes = 128 bits / 8).
  • Longitud de la clave : la longitud de la clave admitida por AES puede ser de 128 bits, 192 bits o 256 bits. La longitud de la clave es diferente y el número recomendado de rondas de cifrado también es diferente, como se muestra en la siguiente tabla:

  • Modo de cifrado : debido a que el cifrado de bloques solo puede cifrar bloques de longitud fija y el texto sin formato real que debe cifrarse puede exceder la longitud del bloque, el algoritmo de cifrado de bloques debe iterarse para completar el cifrado de todo el texto sin formato. El método iterativo es el modo de encriptación. Hay muchos tipos, los modos de trabajo comunes son los siguientes:

  • Vector de inicialización (IV, vector de inicialización)  : el propósito es evitar que el mismo bloque de texto sin formato siempre se cifre en el mismo bloque de texto cifrado. Tome el modo CBC como ejemplo:

Antes de cifrar cada bloque de texto sin formato, se realiza una operación XOR al bloque de texto sin formato con un valor. IV sirve como variable de inicialización y participa en el XOR del primer bloque de texto sin formato. Cada bloque de texto sin formato posterior se aplica XOR con el bloque de texto cifrado cifrado por el bloque de texto sin formato anterior, asegurando así que los bloques de texto cifrado cifrados sean diferentes.

  • Relleno  : dado que la clave solo puede procesar bloques de datos de una determinada longitud y la longitud de los datos suele ser variable, 最后一块se requiere un procesamiento adicional para rellenar los datos antes del cifrado. Los modos comúnmente utilizados incluyen PKCS5, PKCS7, etc.
Método de llenado ilustrar Ejemplo (asumiendo una longitud de bloque 8, longitud de datos 9)
Ninguno Sin relleno
PKCS7 Una cadena de relleno consta de una secuencia de bytes, cada byte rellenado con la longitud de esa secuencia de bytes. Número de octetos para rellenar, igual a 7: Datos: FF FF FF FF FF FF FF FF FFPKCS7 Pad: FF FF FF FF FF FF FF FF 07 07 07 07 07 07 07
PKCS5 Generalmente común con PKCS7. La diferencia es que PKCS5 define claramente el tamaño del bloque en 8 bits, mientras que PKCS7 no está seguro.
ANSIX923 Una cadena de relleno consta de una secuencia de bytes, el último byte del cual se rellena con la longitud de la secuencia de bytes y los bytes restantes se rellenan con el número cero. Datos: FF FF FF FF FF FF FF FF FFX923 Relleno: FF FF FF FF FF FF FF FF 00 00 00 00 00 00 07
ISO10126 La cadena de relleno consta de una secuencia de bytes, el último byte de esta secuencia de bytes se rellena con la longitud de la secuencia de bytes y los bytes restantes se rellenan con datos aleatorios. 数据: FF FF FF FF FF FF FF FF FFISO10126 填充: FF FF FF FF FF FF FF FF 7D 2A 75 EF F8 EF 07
Ceros La cadena de relleno consta de bytes establecidos en cero.

2. Breve descripción del principio

En la función de cifrado AES, se ejecutará una función de ronda, y las 执行10次这个轮函数primeras 9 operaciones de esta función de ronda son iguales, solo la décima vez es diferente. En otras palabras, un paquete de texto plano se cifrará durante 10 rondas.

La unidad de procesamiento de AES son los bytes, y el paquete de texto plano de entrada P de 128 bits se divide en 16 bytes.

Supongamos que la agrupación de texto plano es P = abcdefghijklmnop. Los paquetes de texto sin formato se describen mediante una matriz cuadrada en bytes, llamada matriz de estado. En cada ronda de cifrado, el contenido de la matriz de estado continúa cambiando y el resultado final se genera como texto cifrado. El orden de los bytes en esta matriz es de arriba a abajo y de izquierda a derecha. El proceso de generación del diagrama de matriz de estado se muestra en la siguiente figura:

En la figura anterior, 0x61 es la representación hexadecimal del carácter a, y los demás son iguales.

Una vez que AES cifra el texto sin formato, se modifica hasta quedar irreconocible.

¿Y qué hicieron exactamente estas 10 rondas de cifrado? Incluye principalmente 4 operaciones: sustitución de bytes, desplazamiento de filas, mezcla de columnas y adición de claves redondas. La última iteración no realiza la combinación de columnas. Además, antes de la primera ronda de iteración, se realiza una operación de cifrado XOR en el texto sin formato y la clave original.

De manera similar, el proceso de descifrado AES todavía consta de 10 rondas y la operación de cada ronda es la operación inversa de la operación de cifrado. De manera similar a la operación de cifrado, no se realiza la última ronda de mezcla inversa de columnas. Antes de la primera ronda de descifrado, se realiza una operación de adición de clave.

Las operaciones específicas del cifrado AES se pueden encontrar en detalle en el artículo Introducción detallada al algoritmo de cifrado AES. Esta es sólo una breve introducción sin más explicaciones.

3. Implementación de código en iOS

1. No se recomienda el modo BCE

En circunstancias normales, si un desarrollador de iOS no ha estado expuesto al cifrado AES en detalle, cuando un colega de back-end le dice que el cliente necesita cifrado y descifrado AES, inconscientemente se conectará a Internet para buscar el código y copiarlo directamente. . El método de cifrado más común en Internet ahora, y el más utilizado por todos, es en realidad el método de cifrado AES128 (es decir, la longitud de la clave es 128), el modo ECB y el relleno PKCS7.

¡El modo ECB es el modo de cifrado menos recomendado en el cifrado AES!

La siguiente figura muestra el proceso de cifrado del algoritmo de cifrado de bloques en modo ECB:

Como se puede ver en la figura anterior, la disposición repetida en el texto sin formato se reflejará en el texto cifrado (es decir, el orden en que se agrupa el texto sin formato es el orden en que se agrupa el texto cifrado).

Cuando se manipula el texto cifrado, la agrupación de texto sin formato correspondiente después del descifrado también será incorrecta y el descifrador no notará que el texto cifrado ha sido manipulado. En otras palabras, el BCE no puede verificar la integridad del texto cifrado. Por tanto, no se recomienda el modo ECB bajo ninguna circunstancia.

2. iOS implementa cifrado y descifrado AES en varios modos

En el desarrollo de iOS, el CommonCrypto.framework oficial proporciona la implementación de métodos de cifrado de uso común, incluido el algoritmo de cifrado AES (además de DES, Blowfish, etc.).

Para el cifrado AES, Apple proporciona oficialmente tres interfaces de funciones: CCCryptorcreate(), CCCryptorCreateFromData() y CCCryptorCreateWithMode(). CCCryptorCreateWithMode() se utiliza a continuación para implementar los cuatro modos comunes de cifrado AES: ECB, CBC, CFB, OFB.

(1) Modos admitidos

Porque hay una macro CCMode en el marco, que contiene cuatro modos: ECB, CBC, CFB y OFB, y esta macro solo tiene parámetros en CCCryptorCreateWithMode (). Para comparar la exactitud de los datos cifrados, utilicé los resultados del cifrado y descifrado AES en línea para comparar. Solo hay 4 modos en el sitio web: ECB, CBC, CFB y OFB, por lo que mi código solo implementa estos 4 modos. Siendo por el momento.

(2) Longitud de clave admitida

De forma predeterminada, el sistema admite tres longitudes: 128, 192 y 256.

(3) Métodos de llenado admitidos

El sistema solo proporciona PKCS7Pading y NoPading (sin relleno). Aquí aprendemos del blog del jefe aescfb criptografía_iOS Cifrado AES (principalmente usando el modo CFB) para implementar cuatro métodos de llenado: PKCS7Pading, ZeroPadding, ANSIX923 e ISO10126.

Mostrar código directamente:

(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 

Cabe señalar que el estándar de llenado ISO10126 se completa al azar cada vez. Por lo tanto, excepto en el caso en el que la longitud del último segmento es de 15 bits (porque la longitud de 15 bits solo necesita completarse con un bit y el contenido de este bit es fijo, es decir, la longitud es 01), el resultado del cifrado son diferentes cada vez en otros casos. Debido a que el descifrado del último bloque primero elimina el relleno y luego lo descifra, no afecta el descifrado.

No hay nada más que explicar, hay comentarios en el código. No es necesario implementar el proceso detallado de cifrado y descifrado, ya está implementado en CCCryptorCreateWithMode().

Código fuente de demostración:

Nube de código: gitee.com/ztfiso/MIUA…

Github: github.com/Ztfiso/MIUA…

Resumir

AES es el modo de cifrado simétrico más común en la industria. Cuando lo usamos, no solo debemos poder usarlo, sino también tener una comprensión general de sus diferentes modos y diferencias de parámetros. Al conectarse con el backend, el código del cliente se puede escribir de acuerdo con las reglas establecidas por el backend.

Supongo que te gusta

Origin blog.csdn.net/u013712343/article/details/132472634
Recomendado
Clasificación