赞
踩
最近和服务器同学对接口进行数据加解密时用到了AES加密。原本以为AES就一种加密形式,对接过程中才学习到AES不同模式、不同填充方式下,结果都不相同。因此去学习了一下AES加密的基本概念、实现原理,以及各种模式下的区别与实现。
AES加密是对称加密的一种,全称是Advanced Encryption Standard(高级加密标准)。常用于网络传输中的数据加解密。
这是一个AES在线加密工具。通过网站上的内容可以可以看出,加解密除了需要秘钥(Key)之外,AES还有多种模式,不同的模式加密的方式和结果都不相同。同时还有秘钥长度、初始向量、填充方式等参数,结果也是不尽相同。下面简单介绍一下AES加密的一些概念和参数:
在每一个明文块加密前,会让明文块和一个值先做异或操作。IV作为初始化变量,参与第一个明文块的异或,后续的每一个明文块和它前一个明文块所加密出的密文块相异或,从而保证加密出的密文块都不同。
最后一块
做额外处理,在加密前进行数据填充。常用的模式有PKCS5, PKCS7等。填充方式 | 说明 | 示例(假定块长度为8,数据长度为9) |
---|---|---|
None | 不填充 | |
PKCS7 | 填充字符串由一个字节序列组成,每个字节填充该字节序列的长度。 | 填充用八位字节数,等于7:数据: FF FF FF FF FF FF FF FF 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 FFISO10126 填充: FF FF FF FF FF FF FF FF FF 7D 2A 75 EF F8 EF 07 |
Zeros | 填充字符串由设置为零的字节组成 |
AES加密函数中,会执行一个轮函数,并且执行10次这个轮函数
,这个轮函数的前9次执行的操作是一样的,只有第10次有所不同。也就是说,一个明文分组会被加密10轮。
AES的处理单位是字节,128位的输入明文分组P被分成16个字节。
假设明文分组为P = abcdefghijklmnop。明文分组用字节为单位的正方形矩阵描述,称为状态矩阵。在每一轮加密中,状态矩阵的内容不断发生变化,最后的结果作为密文输出。该矩阵中字节的排列顺序为从上到下、从左至右依次排列,生成状态矩阵图的过程如下图所示:
上图中,0x61为字符a的十六进制表示,其他同理。
明文经过AES加密后,已经面目全非。
而这10轮加密到底做了什么呢?主要包括4个操作:字节代换、行位移、列混合和轮密钥加。最后一轮迭代不执行列混合。另外,在第一轮迭代之前,先将明文和原始密钥进行一次异或加密操作。
同样,AES解密过程仍为10轮,每一轮的操作是加密操作的逆操作。同加密操作类似,最后一轮不执行逆列混合,在第1轮解密之前,要执行1次密钥加操作。
AES加密的具体操作,可以在文章 AES加密算法的详细介绍 找到详细的阐述。这里只简单介绍,不展开说明。
一般情况下,iOS开发者若没有详细接触过AES加密,当后端同事告诉你客户端需要AES加解密时,下意识去网上直接找代码copy。现在网上最常见、也是大家copy使用最多的,实际上是 AES128(即秘钥长度为128)、ECB模式、PKCS7填充 的加密方式。
而ECB模式却是AES加密中最不推荐的加密模式!
下图是ECB模式的分组密码算法加密过程:
上图可以看出,明文中重复的排列会反映在密文中(即明文分组是什么顺序,密文分组就是什么顺序)。
当密文被篡改时,解密后对应的明文分组也会出错,且解密者察觉不到密文被篡改了。也就是说,ECB不能提供对密文的完整性校验。因此,在任何情况下都不推荐使用ECB模式。
iOS开发中,官方的CommonCrypto.framework提供了常用的加密方式的实现,其中就包括了AES加密算法(除此之外还有DES、blowfish等)。
对于AES加密来说,苹果官方有提供了三种函数接口,它们分别是CCCryptorcreate()、CCCryptorCreateFromData()、以及CCCryptorCreateWithMode()。下面使用CCCryptorCreateWithMode()来实现AES加密的4种常用模式:ECB、CBC、CFB、OFB。
(1)支持的模式
因为框架中有个CCMode的宏,里面就包含了ECB、CBC、CFB、OFB这4种模式,而这个宏只有在CCCryptorCreateWithMode()中才有参数。而为了对比加密数据的正确性,我使用 在线AES加密解密 的结果来对比,网站里只有ECB、CBC、CFB、OFB这4种模式,所以我代码也暂时只实现这4种模式。
(2)支持的秘钥长度
系统默认对128、192、256三种长度都支持。
(3)支持的填充方式
系统只提供了PKCS7Pading和NoPading(不填充)。这里借鉴大佬的博客 aescfb加密_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_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
需要注意的是,ISO10126填充标准, 每次是随机填充的。所以除了最后一个分段长度是15bits以外(因为15bits长度只需要填充一个bit,而这个bit内容是固定的,即长度01)的情况,其他情况每次加密结果是不一样的。因为最后一个块的解密是先把填充删除了再解密的,所以不影响解密。
其他没什么好解释的,代码里有注释。加解密的详细过程不用实现,CCCryptorCreateWithMode()内都实现好了。
Demo源码:
码云:gitee.com/ztfiso/MIUA…
Github:github.com/Ztfiso/MIUA…
AES作为业内最常见的对称加密模式,我们在使用的过程中,不仅仅是要会用,对其不同模式、参数区别,要有一个大概的了解。当与后端进行对接时,能根据后端制定的规则来编写客户端的代码。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。