前言

最近和服务器同学对接口进行数据加解密时用到了AES加密。原本以为AES就一种加密形式,对接过程中才学习到AES不同模式、不同填充方式下,结果都不相同。因此去学习了一下AES加密的基本概念、实后端工程师现原理,以及各种模式下的区别与实现。

一、概念

AES加密是对称加密的一种,全称工商银行是Advanced Encryption Standard(高级加密标准)。常用于网络传输中的数据加解密。

iOS-AES加解密各模式(ECB、CBC、CFB、OFB)的实现

这是一个AES在线加密工具。通过网站上宫颈癌的内容可以可以看出,加解密除了需要秘钥(Key)之外,AES还有多种模式,不同的模式加密的方式和结果变量类型有哪些都不相同。同时还有秘钥长度、初始变量向量、填充方式等参数算法工程师,结果也是不尽相同变量名。下面简单介绍一下AES加密的一些概念和参数:

  • 分组(或者叫块) :AES是一种分组加密技术,即把明变量是什么意思文分成一组一组的,每组长度相等,每次加密一组数据,直到加密完整个明文。在AES标准规范中,分组长度只能是128 bits,也就是每个分组为16个bytes(16bytes = 128bits / 8)。
  • 密钥长度:AES支持的密钥长度可以是128 bits、192 bits或256 bits。密钥的长度不google同,推荐加密轮数也不同,如下表:

iOS-AES加解密各模式(ECB、CBC、CFB、OFB)的实现
加密轮数越多,当然安全性越好,但也更耗费时间。迭代轮数是系统设置好的,若非自己实现后端开发工程师可以不用管。

  • 加密模式:因为分组加密后端工程师只能加后端需要学什么字符固定长度的分组,而实际需要加密的明文可能超过分组长度,此时就要对分组密码算法进行迭代,以完成整宫颈癌个明文加密,迭代的方法就是加密模式。它有很多种,常见的工作模式如下图后端开发需要学什么

iOS-AES加解密各模式(ECB、CBC、CFB、OFB)的实现

  • 初始向量(IV,Initialization Vector) :目的是防止同样的明文块,始终加密成同样的密文块变量,以CBC模式为例:

iOS-AES加解密各模式(ECB、CBC、CFB、OFB)的实现

在每一个明文块加密前,会让明文块和一个字符型变量值先做异或操作。IV作为初始化变量,参与第一个明文块的异或,后续的每一算法是指什么个明文块和它前一个明文块所加密出的密文块相异或,从而保证加密出的密文块都不变量英语同。

  • 填充方式(Paddin后端框架g) :由于密钥只能对算法分析的目的是确定长度的数据块进行处理,而数据的长度通常是可变的,因此需要对最后一块做额外处理,在加密前进行数据填充。常用的模式有PKCS5, PKCS7等。
填充方式 说明 示例(假定块长度为8,数据长度为9)
None 不填充
PKCS7 填充字符串由一个字节序列组成,每个字节填充该字节序列的长度。 填充用八位字节数,枸杞等于7:数据: FF FF FF FF FF FF FF F字符串是什么意思F FFPKCS7 填充: FF FF FF FF FF FF FF FF后端开发需要学什么 FF 07 07 07 07 07 07 07
PKCS5 通常与PKCS7通用。区别在于PKCS5明确定义Block的大小是8位,而PKCS7不确定
ANSIX923 填充字符串由一个字节序列组成,此字节序列的最后一个字节填充字节序列的长度,其余字节均填充数字零 数据: FF FF算法工程师 FF FF FF FF FF FF FFX923 填充: FF FF FF FF FF FF FF FF FF 00 00 00 00 00 00 07
ISO10126 填充字符串由一算法导论个字节序列组成,此字节序列的最后一个字节填充字节序列的长度,其余字节填充随机数据。 数据: FF FF FF FF FF FF FF FF FFI后端开发需要学什么SO10126 填充: FF FF FF FF FF FF FF FF FF 7D 2A 75 EF F8 EF 07
Zeros 填充字符串由设置为零的字节组成

二、原理宫颈癌简述

AES加密函数中,会执行一个轮函数,并且执行10次这个轮函数,这个轮函数的前9次执行的操作是一样的,只有第10次有所不同。也就是说,一google个明文分组会被加密10轮。

AES的处理单位是字节,128位的输入明文分组P被分变量的定义成16个字节。

假设明文分组为P = abcdefghijklmnop。明文分组用字节为单位的正方形矩阵描述,称为状态矩阵。在每一轮加密中,状态矩阵的内容变量名的命名规则不断发生变化,最后的结果作为密文输出。该矩阵中字节的排列顺序为从上算法的五个特性到下、从左至右依次排列,生成状态矩阵图的过程如下图所示:

iOS-AES加解密各模式(ECB、CBC、CFB、OFB)的实现

上图中,0x61为字符a的十六进制表示,其他同理。

明文经过AES加密后,已经面目全非。

而这10轮加密到底做了什么呢?主要包括4个操作:字节代换、行位移、列混合和轮变量类型有哪些密钥加变量值。最后一轮迭代不执行列混合。另外,在第一轮迭代之前,先将明文和原始工龄差一年工资差多少密钥进行一次异或加密操作。

iOS-AES加解密各模式(ECB、CBC、CFB、OFB)的实现

同样,AES解密过程仍为10轮,每一轮的操作是加密操作的字符逆操作。同加密操作类似,最后一轮不变量泵执行逆列混合,在第1轮解密之前,要执行1次密钥加操作。

AES加密的具体操作,可以在文章 AES加密算法的详细介绍 找到详细的阐述。这里只简单介绍,不展开说明。

三、iOS中代码实现

1. 不推荐后端语言使用ECB模式

一般情况下,iOS开发者若没有详细接触过AES加密,当字符串逆序输出同事告诉你客户端需要AES加解密时,下意识去网上直接找代码后端需要学什么copy。现在网变量英语上最常见字符型变量、也是大家copy使用最多的,实际上是 AES128(即秘钥长度为128)、ECB模式、PKCS7填充 的加密方式。

而ECB模式却是AES加密中最不推荐的加密模式!

下图是ECB模式的分组密码算法加密过程:

iOS-AES加解密各模式(ECB、CBC、CFB、OFB)的实现

上图可以看出,明文中重复的排列会反映在密文中(即明文分组是什么顺序,密文分组就是什么顺序)。

当密文被篡改时,解密后对应的明文分组也会出后端框架错,且解密者察觉不到密文被篡改了。也就是说,ECB不能提供对密文的完整性校验。因此,在任何情况下公积金都不推荐使变量之间的关系用ECB模式。

2. iOS实现各种模式下的AES加解密

iOS开发中,官方的CommonCrypto.framework后端语言提供了常用的加密方式的实现,其中就包括了AES加密算法(除此之外还有DES、blowfish等)。

对于AES加密来说,苹果官方有提供了三种函数接口字符间距加宽2磅,它们分别是CCCryp变量之间的关系torcre工商银行ate()、CCCryptorC字符间距加宽2磅reateFromData()、以及CCCryptorCreateWithMode()。下面使用CCCryptorCreateWithMode()来实现AES加密的4种常用模式:ECB、CBC、CFB工商银行、OFB。

(1)支持的模式

因为框架中有个CCM变量泵ode的宏,里面就包含了ECB、CBC、CFB、后端开发OFB这4种模式,而这个宏只有在CCCryptorCreateWithMode()中才有参数。而为了对比加密数据变量类型有哪些的正确性,我使用 在线AES加密解密google算法的五个特性结果来对比,网站里只有ECB、CBC、CFB、OFB这4种模式算法设计与分析,所以我代码也暂时只实现这4种模式。

(2)支持的秘钥长度

系统默认对128、192、256三种长度都支持。

(3)支持的填充方式

系统只提供了P算法的时间复杂度取决于KCS7Pading和NoPading(不填充)。这里借鉴大佬的博客 aes算法的空间复杂度是指cfb加密_iOS AES加密(主要使用CFB模式) ,实现PKCS7Pading、ZeroPadding 、ANSIX923、ISO10126四种填充方式。

直接Show Code:

(1)MIUAES.h

//
// MIUAES.h
//
#import <Foundation/Foundation.h>
#import <CommonCrypto/CommonCryptor.h>
NS_ASSUME_NONNULL_BEGIN
typedef enum : NSUInteger {
  MIUCryptorNoPadding = 0,  // 无填充
  MIUCryptorPKCS7Padding = 1, // PKCS_7 | 每个字节填充字节序列的长度。 ***此填充模式使用系统方法。***
  MIUCryptorZeroPadding = 2, // 0x00 填充 | 每个字节填充 0x00
  MIUCryptorANSIX923,     // 最后一个字节填充字节序列的长度,其余字节填充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 *)originalStr
           mode:(MIUMode)mode
            key:(NSString *)key
          keySize:(MIUKeySizeAES)keySize
            iv:(NSString * _Nullable )iv
          padding:(MIUCryptorPadding)padding;
+ (NSString *)MIUAESDecrypt:(NSString *)originalStr
           mode:(MIUMode)mode
            key:(NSString *)key
          keySize:(MIUKeySizeAES)keySize
            iv:(NSString * _Nullable )iv
          padding:(MIUCryptorPadding)padding;
@end
NS_ASSUME_NONNULL_END

(2)MIUAES.m

//
// MIUAES.m
//
#import "MIUAES.h"
#import "MIUGTMBase64.h"
@implementation MIUAES
+ (NSString *)MIUAESEncrypt:(NSString *)originalStr
           mode:(MIUMode)mode
            key:(NSString *)key
          keySize:(MIUKeySizeAES)keySize
            iv:(NSString * _Nullable )iv
          padding:(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 *)originalStr
           mode:(MIUMode)mode
            key:(NSString *)key
          keySize:(MIUKeySizeAES)keySize
            iv:(NSString * _Nullable )iv
          padding:(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 *)originalData
         operation:(CCOperation)operation
           mode:(CCMode)mode
            key:(NSString *)key
          keySize:(MIUKeySizeAES)keySize
            iv:(NSString *)iv
          padding:(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 *)data
             mode:(CCMode)mode
            padding:(MIUCryptorPadding)padding;
{
  NSMutableData *sourceData = data.mutableCopy;
  int blockSize = kCCBlockSizeAES128;     //FIXME: AES的块大小都是128bit,即16bytes
  
  switch (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

需要注意的是,ISO字符是什么10126填充标准, 每次是随机填充的。所以除了最后一个分段后端是什么工作长度是15后端工程师b变量与函数its以外(因为15biGots长度只需要填充一个bit,而这个bit内容是固定的,即长度01)的情况,其他情况每次加密结果是不一样的。因为最后一个块的解密是先把填充删除了再解密的,所以不影响解密。

其他没什么好解释的,代码里有注释。加解密的详细过程不用实现,CCCryptorCreat算法导论eWithMode()内都实现好了。

Demo源码:

算法工程师云:gitee.com/ztfiso/MIUA…

Github:github.com/Ztfiso/MIUA…

总结

AES作为业内最常见的对称加密模式,我们在使用的过程中,不仅仅是要会用,对其不同模式、字符参数区别,要有一个大概的了解。当与后端进行对接时,能根据后端制算法导论定的规则来编写客户端的代码。

iOS-AES加解密各模式(ECB、CBC、CFB、OFB)的实现

参考资料:

【1】AES-GCM 加密简介 :AES加密的一些概念、参数定义、一些加密模式的简介;

【2】AES加密算法的详细介绍【面试+工作】:AES-ECB模式的基本原理;

【3】PKCS5Padding与PKCS7Paddin算法的有穷性是指g的区别 :几种常用的填充方式的填充原理和区别;

【4】iOS中加密解密之CommonCrypto框架 :iOS中使用工商银行AES加密的框架介绍;

【5】在线AES加密解密:用于验证加解密数据是否正确;

【6】aescfb加密_iOS AES加密(主要使用CFB模式) :CFB模式下的填充方式。