iOS-AES暗号化および復号化モード(ECB、CBC、CFB、OFB)の実装

序文

最近、サーバークラスメートとのインターフェイスでデータを暗号化および復号化するときに、AES 暗号化を使用しました。私は当初、AES は単なる暗号化の一種だと考えていましたが、ドッキング プロセス中に、AES モードとパディング方法が異なると結果も異なることが分かりました。そこで、AES 暗号化の基本概念と実装原理、さまざまなモードの違いと実装について学びに行きました。

1.コンセプト

AES 暗号化は対称暗号化の一種で、正式名は Advanced Encryption Standard です。ネットワーク送信におけるデータの暗号化と復号化に一般的に使用されます。

これは、AES オンライン暗号化ツールです。Web サイトの内容を見ると、AES には暗号化と復号化に秘密鍵 (Key) が必要であることに加えて、複数のモードがあり、モードが異なれば暗号化方法と結果も異なります。鍵の長さ、初期ベクトル、充填方法などのパラメータもあり、結果も異なります。ここでは、AES 暗号化のいくつかの概念とパラメータを簡単に紹介します。

  • グループ化 (またはブロック)  : AES はブロック暗号化テクノロジであり、平文をグループに分割し、各グループは同じ長さで、平文全体が暗号化されるまで一度に 1 つのデータ グループを暗号化します。AES 標準仕様では、パケット長は 128 ビットのみです。つまり、各パケットは 16 バイトです (16 バイト = 128 ビット / 8)。
  • キーの長さ: AES でサポートされるキーの長さは、128 ビット、192 ビット、または 256 ビットです。次の表に示すように、キーの長さが異なり、推奨される暗号化ラウンド数も異なります。

  • 暗号化モード: ブロック暗号化では固定長のブロックのみを暗号化でき、暗号化が必要な実際の平文はブロック長を超える可能性があるため、平文全体の暗号化を完了するにはブロック暗号アルゴリズムを反復する必要があります。暗号化モード。これには多くの種類があり、一般的な動作モードは次のとおりです。

  • 初期化ベクトル (IV、初期化ベクトル)  : 目的は、同じ平文ブロックが常に同じ暗号文ブロックに暗号化されるのを防ぐことです。例として CBC モードを取り上げます。

各平文ブロックが暗号化される前に、平文ブロックは値と XOR 演算されます。IV は初期化変数として機能し、最初の平文ブロックの XOR に参加します。後続の各平文ブロックは、前の平文ブロックによって暗号化された暗号文ブロックと XOR 演算され、それによって暗号化された暗号文ブロックが異なることが保証されます。

  • パディング : キーは特定の長さのデータ ブロックのみを処理でき、データの長さは通常は可変であるため、最后一块暗号化前にデータをパディングするには追加の処理が必要です。一般的に使用されるモードには、PKCS5、PKCS7 などが含まれます。
充填方法 説明する 例(ブロック長8、データ長9の場合)
なし パディングなし
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 ではブロックのサイズが 8 ビットであると明確に定義されているのに対し、PKCS7 では明確に定義されていないことです。
ANSIX923 パディング文字列はバイト シーケンスで構成され、その最後のバイトはバイト シーケンスの長さでパディングされ、残りのバイトは数値ゼロでパディングされます。 データ:FF FF FF FF FF FF FF FF FFX923 フィル: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
ゼロ パディング文字列はゼロに設定されたバイトで構成されます

2. 原理の簡単な説明

AES暗号化関数ではラウンド関数が実行されますが、执行10次这个轮函数このラウンド関数の最初の9回の演算は同じで、10回目だけが異なります。つまり、平文パケットは 10 ラウンド暗号化されます。

AESの処理単位はバイトであり、入力された128ビットの平文パケットPは16バイトに分割される。

平文のグループ化が P = abcdefghijklmnop であると仮定します。平文パケットは、状態行列と呼ばれるバイト単位の正方行列で記述されます。暗号化の各ラウンドで、状態マトリックスの内容は変化し続け、最終結果が暗号文として出力されます。この行列のバイトの順序は上から下、左から右です。状態マトリックス図を生成するプロセスを次の図に示します。

上図において、0x61 は文字 a を 16 進数で表現したもので、他は同じです。

平文は AES によって暗号化された後、認識できないほど変更されています。

そして、これら 10 回の暗号化は正確に何をしたのでしょうか? これには主に、バイト置換、行置換、列混合、ラウンド キー加算の 4 つの操作が含まれます。最後の反復では列ブレンドは実行されません。さらに、最初の反復の前に、平文と元のキーに対して XOR 暗号化操作が実行されます。

同様に、AES 復号化プロセスも 10 ラウンドであり、各ラウンドの操作は暗号化操作の逆の操作です。暗号化処理と同様に、最後の逆列混合処理は実行されず、最初の復号化処理の前に鍵追加処理が実行されます。

AES 暗号化の具体的な操作については、「AES 暗号化アルゴリズムの詳細な紹介」の記事で詳しく説明されています。これは簡単な紹介に過ぎず、詳しい説明はありません。

3. iOS でのコードの実装

1. ECB モードは推奨されません

通常の状況では、iOS 開発者が AES 暗号化について詳しく知らなかった場合、バックエンドの同僚からクライアントに AES 暗号化と復号化が必要だと告げられると、その開発者は無意識のうちにオンラインにアクセスしてコードを見つけて直接コピーするでしょう。 。現在インターネット上で最も一般的であり、誰もが最もよく使用している暗号化方式は、実際には、AES128 (つまり、キーの長さは 128)、ECB モード、および PKCS7 パディングの暗号化方式です。

ECB モードは、AES 暗号化の中で最も推奨されない暗号化モードです。

次の図は、ECB モードでのブロック暗号アルゴリズムの暗号化プロセスを示しています。

上図からわかるように、平文内の繰り返しの配置が暗号文に反映されます(つまり、平文のグループ化の順序が暗号文のグループ化の順序になります)。

暗号文が改ざんされると、復号化後の対応する平文のグループ化も正しくなくなり、復号者は暗号文が改ざんされたことに気付かなくなります。言い換えれば、ECB は暗号文の完全性検証を行うことができません。したがって、ECB モードはいかなる状況でも推奨されません。

2. iOS はさまざまなモードで AES 暗号化と復号化を実装します

iOS 開発では、公式 CommonCrypto.framework は、(DES、blowfish などに加えて) AES 暗号化アルゴリズムを含む、一般的に使用される暗号化方式の実装を提供します。

AES 暗号化の場合、Apple は CCCryptorcreate()、CCCryptorCreateFromData()、および CCCryptorCreateWithMode() という 3 つの関数インターフェイスを公式に提供しています。CCCryptorCreateWithMode() は、AES 暗号化の 4 つの一般的なモード (ECB、CBC、CFB、OFB) を実装するために以下で使用されます。

(1) 対応モード

フレームワークには CCMode マクロがあり、ECB、CBC、CFB、OFB の 4 つのモードが含まれており、このマクロには CCCryptorCreateWithMode() のパラメーターのみがあるためです。暗号化されたデータの正しさを比較するために、オンラインの AES 暗号化と復号化の結果を使用して比較しました。Web サイトには ECB、CBC、CFB、OFB の 4 つのモードしかないため、コードではこれら 4 つのモードのみを実装しています。当面。

(2) サポートされる鍵の長さ

デフォルトでは、システムは 128、192、256 の 3 つの長さをサポートします。

(3) サポートされている充填方法

システムは PKCS7Pading と NoPading (パディングなし) のみを提供します。ここでは、上司のブログ aescfb 暗号化_iOS AES 暗号化 (主に CFB モードを使用) から学び、PKCS7Padding、ZeroPadding、ANSIX923、ISO10126 の 4 つの充填方法を実装します。

コードを直接表示:

(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 充填規格に従って、毎回ランダムに充填されることに注意してください。したがって、最後のセグメント長が 15 ビットの場合を除いて (15 ビット長には 1 ビットを埋めればよく、このビットの内容は固定、つまり長さが 01 であるため)、暗号化結果は次のようになります。他の場合は毎回異なります。最後のブロックの復号化では、最初にパディングが削除されてから復号化されるため、復号化には影響しません。

他に説明することはありません。コード内にコメントがあります。暗号化と復号化の詳細なプロセスを実装する必要はありません。すでに CCCryptorCreateWithMode() に実装されています。

デモのソースコード:

コードクラウド: gitee.com/ztfiso/MIUA…

Github: github.com/Ztfiso/MIUA…

要約する

AES は業界で最も一般的な対称暗号化モードであり、これを使用する場合は、それを使用できるだけでなく、さまざまなモードやパラメータの違いについて一般的に理解する必要があります。バックエンドに接続する場合、バックエンドによって設定されたルールに従ってクライアント コードを記述することができます。

おすすめ

転載: blog.csdn.net/u013712343/article/details/132472634