iOS开发-AES加密

作者: 张囧瑞 | 来源:发表于2018-07-04 22:36 被阅读1893次

    上次的文章中对常用的加密算法进行了一些简单的介绍,这次我们就挑一个出来说说,今天的主角的是对称加密中的当头大哥AES加密。

    AES加密简介

    AES全称Advanced Encryption Standard,中文名称叫高级加密标准,在密码学中被叫做Rijndael加密法,这个标准已经替代原来的DES,成为美国政府采用的一种区块加密标准。微信小程序中的加密传输就是使用的AES加密算法。

    aes1.png

    根据上图,解释一下各个部分的作用:

    明文P:没有经过加密的数据。

    密钥K:用来加密明文的密码,在对称加密中,加密和解密的密钥是相同的,所以密钥要保证安全,如果一旦密钥泄漏了,那么数据就基本上不存在安全性了。

    AES加密函数:设AES加密函数为E,则C = E(K,P),其中K为密钥,C为密文。所以通过加密函数E,可以把明文+密钥生成密文。

    密文C:经过加密处理后的数据。

    AES解密函数:如加密函数一样,设AES解密函数为D,则P = D(C,P),也就是说,可以通过解密函数D,将密文+密钥生成明文。

    AES的基本结构

    AES为分组密码,分组密码就是把明文分成一组一组的,每组的长度相等,每次加密一组数据,一直到整个明文被加密完毕。

    在AES标准规范中,分组长度只能是128位,也就是说每个分组为16个字节(每个字节8位)。密钥的长度可以使用128位、192位或256位。密钥的长度不同,推荐加密的轮数也不同。一般加密的轮数如下:

    AES | 密钥长度(32位比特字) | 分组长度(32位比特字) | 加密轮数

    --- | --- | --- | ---

    AES-128 | 4 | 4 | 10

    AES-192 | 6 | 4 | 12

    AES-256 | 8 | 4 | 14

    这个加密轮数是什么意思呢?

    就比如说上边的加密公式C = E(K,P),如果加密10轮,就是需要执行10次这个函数,这个轮函数的前9次都是相同的,只有第10次时有所不同。

    AES处理的单位是字节,128位的输入明文分组P和输入密钥K都被分为16个字节,分别记为P = P0 P1 ... P15 和 K = K0 K1 ... K15,如明文分组P = abcdefghijklmnop,其中的字符a对应P0,p对应P15。一般地,明文分组用字节为单位的正方形矩阵 描述,成为状态矩阵。在算法的每一轮中,状态矩阵的内容不断发生变化,最后的结果作为密文输出。该矩阵的排列顺序为从上到下、从左至右依次排列,如图所示:

    aes2.png

    现在假设明文分组P = abcdefghijklmnop,则对应的矩阵图为:

    aes3.png

    图中使用十六进制表示,即使用0x61表示a,可以看到,经过AES加密之后,得到的结果已经看不出原来的样子。

    同样的,128位密钥也是用字节为单位的矩阵表示,矩阵的每一列被成为1个32位比特字。通过密钥编排函数该密钥矩阵被扩展成一个44个字组成的序列W[0],w[1],...,w[43],该序列的前4个元素W[0],W[1],W[2],W[3]是原始密钥,用于加密运算中的初始密钥,后边40个字分为10组,每组4个字(128比特)分别用于10轮加密运算中的轮密钥加,如下图:

    aes4.png

    上图中,设K = “abcdefghijklmnop”,则K0 = a, K15 = p, W[0] = K0 K1 K2 K3 = “abcd”。

    AES的整体结构如下图所示,其中的W[0,3]是指W[0]、W[1]、W[2]和W[3]串联组成的128位密钥。加密的第1轮到第9轮的轮函数一样,包括4个操作:字节代换、行位移、列混合和轮密钥加。最后一轮迭代不执行列混合。另外,在第一轮迭代之前,先将明文和原始密钥进行一次异或加密操作。

    aes5.png

    上图为AES加密过程和解密过程,解密过程也为10轮,每一轮操作都是之前加密操作的逆操作。由于AES的4个轮操作都是可逆的,因此,解密的操作的第一轮就是顺序执行逆位移位,逆字节代换,轮密钥加和逆列混合。

    AES*算法流程

    AES加密算法主要有4个操作:

    • 字节替代(SubBytes)

    • 行位移(ShiftRows)

    • 列混淆(MixColumns)

    • 轮密钥加(AddRoundKey)

    通过之前的介绍,我们也可以了解到,加密和解密是一个相互可逆的过程。而且所有的顺序刚好是相反的。

    字节替代

    AES的字节替代实际上就是一个简单的查表操作,AES定义了一个S盒和一个逆S盒。

    正向字节替代

    S盒 :

    行/列 0 1 2 3 4 5 6 7 8 9 A B C D E F
    0 0x63 0x7c 0x77 0x7b 0xf2 0x6b 0x6f 0xc5 0x30 0x01 0x67 0x2b 0xfe 0xd7 0xab 0x76
    1 0xca 0x82 0xc9 0x7d 0xfa 0x59 0x47 0xf0 0xad 0xd4 0xa2 0xaf 0x9c 0xa4 0x72 0xc0
    2 0xb7 0xfd 0x93 0x26 0x36 0x3f 0xf7 0xcc 0x34 0xa5 0xe5 0xf1 0x71 0xd8 0x31 0x15
    3 0x04 0xc7 0x23 0xc3 0x18 0x96 0x05 0x9a 0x07 0x12 0x80 0xe2 0xeb 0x27 0xb2 0x75
    4 0x09 0x83 0x2c 0x1a 0x1b 0x6e 0x5a 0xa0 0x52 0x3b 0xd6
    0xb3 0x29 0xe3 0x2f 0x84
    5 0x53 0xd1 0x00 0xed 0x20 0xfc 0xb1 0x5b 0x6a 0xcb 0xbe 0x39 0x4a 0x4c 0x58 0xcf
    6 0xd0 0xef 0xaa 0xfb 0x43 0x4d 0x33 0x85 0x45 0xf9 0x02 0x7f 0x50 0x3c 0x9f 0xa8
    7 0x51 0xa3 0x40 0x8f 0x92 0x9d 0x38 0xf5 0xbc 0xb6 0xda 0x21 0x10 0xff 0xf3 0xd2
    8 0xcd 0x0c 0x13 0xec 0x5f 0x97 0x44 0x17 0xc4 0xa7 0x7e 0x3d 0x64 0x5d 0x19 0x73
    9 0x60 0x81 0x4f 0xdc 0x22 0x2a 0x90 0x88 0x46 0xee 0xb8 0x14 0xde 0x5e 0x0b 0xdb
    A 0xe0 0x32 0x3a 0x0a 0x49 0x06 0x24 0x5c 0xc2 0xd3 0xac 0x62 0x91 0x95 0xe4 0x79
    B 0xe7 0xc8 0x37 0x6d 0x8d 0xd5 0x4e 0xa9 0x6c 0x56 0xf4 0xea 0x65 0x7a 0xae 0x08
    C 0xba 0x78 0x25 0x2e 0x1c 0xa6 0xb4 0xc6 0xe8 0xdd 0x74 0x1f 0x4b 0xbd 0x8b 0x8a
    D 0x70 0x3e 0xb5 0x6 0x48 0x03 0xf6 0x0e 0x61 0x35 0x57 0xb9 0x86 0xc1 0x1d 0x9e
    E 0xe1 0xf8 0x98 0x11 0x69 0xd9 0x8e 0x94 0x9b 0x1e 0x87 0xe9 0xce 0x55 0x28 0xdf
    F 0x8c 0xa1 0x89 0x0d 0xbf 0xe6 0x42 0x68 0x41 0x99 0x2d 0x0f 0xb0 0x54 0xbb 0x16

    状态矩阵中的元素按照下面的方式映射为一个新的字节:把该字节的高4位作为行值,低4位作为列值,取出S盒或者逆S盒中对应的行的元素作为输出。例如,加密时,输出的字节S1为0x12,则查S盒的第0x01行和0x02列,得到值0xc9,然后替换S1原有的0x12为0xc9。状态矩阵经字节代换后的图如下:

    aes6.png

    逆向字节替代

    逆向字节替代也就是查询逆S盒来变换,逆S盒如下:

    行/列 0 1 2 3 4 5 6 7 8 9 A B C D E F
    0 0x52 0x09 0x6a 0xd5 0x30 0x36 0xa5 0x38 0xbf 0x40 0xa3 0x9e 0x81 0xf3 0xd7 0xfb
    1 0x7c 0xe3 0x39 0x82 0x9b 0x2f 0xff 0x87 0x34 0x8e 0x43 0x44 0xc4 0xde 0xe9 0xcb
    2 0x54 0x7b 0x94 0x32 0xa6 0xc2 0x23 0x3d 0xee 0x4c 0x95 0x0b 0x42 0xfa 0xc3 0x4e
    3 0x08 0x2e 0xa1 0x66 0x28 0xd9 0x24 0xb2 0x76 0x5b 0xa2 0x49 0x6d 0x8b 0xd1 0x25
    4 0x72 0xf8 0xf6 0x64 0x86 0x68 0x98 0x16 0xd4 0xa4 0x5c 0xcc 0x5d 0x65 0xb6 0x92
    5 0x6c 0x70 0x48 0x50 0xfd 0xed 0xb9 0xda 0x5e 0x15 0x46 0x57 0xa7 0x8d 0x9d 0x84
    6 0x90 0xd8 0xab 0x00 0x8c 0xbc 0xd3 0x0a 0xf7 0xe4 0x58 0x05 0xb8 0xb3 0x45 0x06
    7 0xd0 0x2c 0x1e 0x8f 0xca 0x3f 0x0f 0x02 0xc1 0xaf 0xbd 0x03 0x01 0x13 0x8a 0x6b
    8 0x3a 0x91 0x11 0x41 0x4f 0x67 0xdc 0xea 0x97 0xf2 0xcf 0xce 0xf0 0xb4 0xe6 0x73
    9 0x96 0xac 0x74 0x22 0xe7 0xad 0x35 0x85 0xe2 0xf9 0x37 0xe8 0x1c 0x75 0xdf 0x6e
    A 0x47 0xf1 0x1a 0x71 0x1d 0x29 0xc5 0x89 0x6f 0xb7 0x62 0x0e 0xaa 0x18 0xbe 0x1b
    B 0xfc 0x56 0x3e 0x4b 0xc6 0xd2 0x79 0x20 0x9a 0xdb 0xc0 0xfe 0x78 0xcd 0x5a 0xf4
    C 0x1f 0xdd 0xa8 0x33 0x88 0x07 0xc7 0x31 0xb1 0x12 0x10 0x59 0x27 0x80 0xec 0x5f
    D 0x60 0x51 0x7f 0xa9 0x19 0xb5 0x4a 0x0d 0x2d 0xe5 0x7a 0x9f 0x93 0xc9 0x9c 0xef
    E 0xa0 0xe0 0x3b 0x4d 0xae 0x2a 0xf5 0xb0 0xc8 0xeb 0xbb 0x3c 0x83 0x53 0x99 0x61
    F 0x17 0x2b 0x04 0x7e 0xba 0x77 0xd6 0x26 0xe1 0x69 0x14 0x63 0x55 0x21 0x0c 0x7d

    行位移

    正向行位移

    行移位是一个简单的左循环移位操作。当密钥长度为128比特时,状态矩阵的第0行左移0字节,第1行左移1字节,第2行左移2字节,第3行左移3字节,如下图所示:

    aes7.png

    逆向行位移

    行移位的逆变换是将状态矩阵中的每一行执行相反的移位操作,例如AES-128中,状态矩阵的第0行右移0字节,第1行右移1字节,第2行右移2字节,第3行右移3字节。

    列混淆

    正向列混淆

    列混合变换是通过矩阵相乘来实现的,经行移位后的状态矩阵与固定的矩阵相乘,得到混淆后的状态矩阵,如下图的公式所示:

    aes8.png

    状态矩阵中的第j列(0 ≤j≤3)的列混合可以表示为下图所示:

    aes9.png

    其中,矩阵元素的乘法和加法都是定义在基于GF(2^8)上的二元运算,并不是通常意义上的乘法和加法。这里涉及到一些信息安全上的数学知识,不过不懂这些知识也行。其实这种二元运算的加法等价于两个字节的异或,乘法则复杂一点。对于一个8位的二进制数来说,使用域上的乘法乘以(00000010)等价于左移1位(低位补0)后,再根据情况同(00011011)进行异或运算,设S1 = (a7 a6 a5 a4 a3 a2 a1 a0),刚0x02 * S1如下图所示:

    aes10.png

    也就是说,如果a7为1,则进行异或运算,否则不进行。

    类似地,乘以(00000100)可以拆分成两次乘以(00000010)的运算:

    aes11.png

    乘以(0000 0011)可以拆分成先分别乘以(0000 0001)和(0000 0010),再将两个乘积异或:

    aes12.png

    逆向列混淆

    逆向列混合变换可由下图的矩阵乘法定义:

    aes13.png

    可以验证,逆变换矩阵同正变换矩阵的乘积恰好为单位矩阵。

    轮密钥加

    轮密钥加是将128位轮密钥Ki同状态矩阵中的数据进行逐位异或操作,如下图所示。其中,密钥Ki中每个字W[4i],W[4i+1],W[4i+2],W[4i+3]为32位比特字,包含4个字节,他们的生成算法下面在下面介绍。轮密钥加过程可以看成是字逐位异或的结果,也可以看成字节级别或者位级别的操作。也就是说,可以看成S0 S1 S2 S3 组成的32位字与W[4i]的异或运算。

    aes14.png

    轮密钥加的逆运算同正向的轮密钥加运算完全一致,这是因为异或的逆操作是其自身。轮密钥加非常简单,但却能够影响S数组中的每一位。

    iOS中的*AES

    在iOS中使用AES进行加密解密的一个关键的函数就是:

    
    CCCryptorStatus CCCrypt(
    
     CCOperation op,  /* kCCEncrypt, etc. */
    
     CCAlgorithm alg, /* kCCAlgorithmAES128, etc. */
    
     CCOptions options, /* kCCOptionPKCS7Padding, etc. */
    
     const void *key,
    
     size_t keyLength,
    
     const void *iv,  /* optional initialization vector */
    
     const void *dataIn,  /* optional per op and alg */
    
     size_t dataInLength,
    
     void *dataOut, /* data RETURNED here */
    
     size_t dataOutAvailable,
    
     size_t *dataOutMoved)
    
     __OSX_AVAILABLE_STARTING(__MAC_10_4, __IPHONE_2_0);
    
    

    我们首先来介绍一下这么一大堆参数都是什么用:

    • CCOperation op:用来代表加密或者解密,kCCEncrypt = 加密,kCCDecrypt = 解密;

    • CCAlgorithm alg:用来代表加密算法,有kCCAlgorithmAES128...具体的可以cmd+ctl点击进去看;

    • CCOptions options:填充模式,iOS中只提供了kCCOptionPKCS7Padding和kCCOptionECBMode两种,这个在于后台和安卓交互时要注意一点。

    • const void *key:密钥长度,一般使用char keyPtr[kCCKeySizeAES256+1];

    • const void *dataIn:加密信息的比特数

    • size_t dataInLength:加密信息的长度

    • void *dataOut:用来输出加密结果

    • size_t dataOutAvailable:输出的大小

    了解了这个函数大概的工作方式之后,我们就可以开始撸起袖子写代码了。

    这里我们主要对NSData和NSString进行加密解密。其实实际上NSString也是转换为了NSData,然后再进行加密解密的。

    引入框架

    第一步,重中之重的一步就是引入框架了

    
    #import <CommonCrypto/CommonCrypto.h>
    
    #import <CommonCrypto/CommonDigest.h>
    
    

    NSData 加密

    首先我们先写一下对NSData的加密,因为之前介绍过了主要的函数,这里就直接上代码了。

    
    //对NSData 进行加密
    
    - (NSData *)encryptDataWithData:(NSData *)data Key:(NSString *)key
    
    {
    
     char keyPtr[kCCKeySizeAES128 + 1];
    
     bzero(keyPtr, sizeof(keyPtr));
    
     [key getCString:keyPtr maxLength:sizeof(key) encoding:NSUTF8StringEncoding];
    
     NSUInteger dataLength = [data length];
    
     size_t bufferSize = dataLength + kCCBlockSizeAES128;
    
     void *buffer = malloc(bufferSize);
    
     size_t numBytesEncrypted = 0;
    
     CCCryptorStatus cryptStatus = CCCrypt(kCCEncrypt,
    
     kCCAlgorithmAES128,
    
     kCCOptionPKCS7Padding | kCCOptionECBMode,
    
     keyPtr,
    
     kCCBlockSizeAES128,
    
     NULL,
    
     [data bytes],
    
     dataLength,
    
     buffer,
    
     bufferSize,
    
     &numBytesEncrypted);
    
     if(cryptStatus == kCCSuccess)
    
     {
    
     return [NSData dataWithBytesNoCopy:buffer length:numBytesEncrypted];
    
     }
    
     free(buffer);
    
     return nil;
    
    }
    
    

    NSData 解密

    
    // 解密
    
    - (NSData *)decryptDataWithData:(NSData *)data andKey:(NSString *)key
    
    {
    
     char keyPtr[kCCKeySizeAES128 + 1];
    
     bzero(keyPtr, sizeof(keyPtr));
    
     [key getCString:keyPtr maxLength:sizeof(keyPtr) encoding:NSUTF8StringEncoding];
    
     NSUInteger dataLength = [data length];
    
     size_t bufferSize = dataLength + kCCBlockSizeAES128;
    
     void *buffer = malloc(bufferSize);
    
     size_t numBytesDecrypted = 0;
    
     CCCryptorStatus cryptStatus = CCCrypt(kCCDecrypt, kCCAlgorithmAES128, kCCOptionPKCS7Padding | kCCOptionECBMode, keyPtr, kCCBlockSizeAES128, NULL, [data bytes], dataLength, buffer, bufferSize, &numBytesDecrypted);
    
     if(cryptStatus == kCCSuccess)
    
     {
    
     return [NSData dataWithBytesNoCopy:buffer length:numBytesDecrypted];
    
     }
    
     free(buffer);
    
     return nil;
    
    }
    
    

    NSString 加密

    对NSString加密的时候需要注意一点,当计算中文字符串长度时,需要使用char字符长度计算,而不要使用[NSString length]计算。

    
    - (NSString *)encryptStringWithString:(NSString *)string andKey:(NSString *)key
    
    {
    
     const char *cStr = [string cStringUsingEncoding:NSUTF8StringEncoding];
    
     NSData *data = [NSData dataWithBytes:cStr length:[string length]];
    
     //对数据进行加密
    
     NSData *result = [self encryptDataWithData:data Key:key];
    
     //转换为2进制字符串
    
     if(result && result.length > 0)
    
     {
    
     Byte *datas = (Byte *)[result bytes];
    
     NSMutableString *outPut = [NSMutableString stringWithCapacity:result.length];
    
     for(int i = 0 ; i < result.length ; i++)
    
     {
    
     [outPut appendFormat:@"%02x",datas[i]];
    
     }
    
     return outPut;
    
     }
    
     return nil;
    
    }
    
    

    NSString 解密

    
    - (NSString *)decryptStringWithString:(NSString *)string andKey:(NSString *)key
    
    {
    
     NSMutableData *data = [NSMutableData dataWithCapacity:string.length/2.0];
    
     unsigned char whole_bytes;
    
     char byte_chars[3] = {'\0','\0','\0'};
    
     int i;
    
     for(i = 0 ; i < [string length]/2 ; i++)
    
     {
    
     byte_chars[0] = [string characterAtIndex:i * 2];
    
     byte_chars[1] = [string characterAtIndex:i * 2 + 1];
    
     whole_bytes = strtol(byte_chars, NULL, 16);
    
     [data appendBytes:&whole_bytes length:1];
    
     }
    
     NSData *result = [self decryptDataWithData:data andKey:key];
    
     if(result && result.length > 0)
    
     {
    
     return [[NSString alloc] initWithData:result encoding:NSUTF8StringEncoding];
    
     }
    
     return nil;
    
    }
    
    

    结束

    以上就是AES加密的简单原理以及iOS中对AES加密解密的简单使用,其中的图片和大部分内容都是来自一位大神博客中的内容。

    本文章也仅限个人学习使用,如果有哪里不对的地方请大佬们多多指正。

    参考文档

    AES加密算法的详细介绍与实现

    密码算法详解——AES

    iOS开发_AES加密和解密算法的实现

    iOS开发之Objective-c的AES加密和解密算法的实现

    相关文章

      网友评论

        本文标题:iOS开发-AES加密

        本文链接:https://www.haomeiwen.com/subject/tkoyuftx.html