前言
????前段時間調研了iOS有關加密的框架CommonCrypto
.在查閱資料的過程中,有一篇《iOS加密解密:AES,DES,3DES,BLOWFISH(含有多種模式和算法說明)》
的文章。
在文章中作者對需要加密的數據實現PKCS7Padding填充,并在調用CCCryptorCreateWithMode()
函數時將padding參數賦值為ccNoPadding(不填充)
。在作者的基礎上我增添了ANSIX923
,ISO10126
以及Zero
填充方式,以及解密之后的去除數據的填充。
????在對稱加密中,可以概分為兩種模式加密,流加密以及塊加密,當我們使用塊加密(也就是分組加密)的時候,例如AES、DES,每次是對固定大小的分組數據進行處理。但是大多數需要加密的數據并不是固定大小的倍數長度。例如AES數據塊為128位,也就是16字節長度,而需要加密的長度可能為15、26等等。為了解決這個問題,我們就需要對數據進行填補操作,將數據補齊至對應塊長度。
補碼原理
接下來呢講一下關于數據填充ANSIX923
、ISO10126
、PKCS7
以及Zero
具體的補碼原理。
注,補碼原理來自于文章Padding (cryptography),讀中文的小伙伴們可以查看這篇 關于PKCS5Padding與PKCS7Padding的區別.
-
ANSIX923 填充方式
ANSIX923 在填充時首先獲取需要填充的字節長度 = (塊長度 - (數據長度 % 塊長度)), 在填充字節序列中最后一個字節填充為
需要填充的字節長度值
, 填充字節中其余字節均填充數字零.
例:假定塊長度為8 ,數據長度為 10,則填充字節數等于 6,數據等于 FF FF FF FF FF FF FF FF FF DD: 數據: FF FF FF FF FF FF FF FF FF X923 填充后: FF FF FF FF FF FF FF FF | FF DD 00 00 00 00 00 06
-
ISO10126 填充方式
ISO10126 在填充時首先獲取需要填充的字節長度 = (塊長度 - (數據長度 % 塊長度)), 在填充字節序列中最后一個字節填充為
需要填充的字節長度值
, 填充字節中其余字節均填充隨機數值.
例:假定塊長度為 16,數據長度為 9,則填充字節數等于 7,數據等于 FF FF FF FF FF FF FF FF FF : | FF FF FF FF FF FF FF FF FF 73 68 C4 81 A6 23 07 |
-
PKCS7 填充方式
PKCS7 在填充時首先獲取需要填充的字節長度 = (塊長度 - (數據長度 % 塊長度)), 在填充字節序列中所有字節填充為
需要填充的字節長度值
例:假定塊長度為 8,數據長度為 3,則填充字節數等于 5,數據等于 FF FF FF : | FF FF FF 05 05 05 05 05 |
-
Zero 填充方式
Zero 在填充時首先獲取需要填充的字節長度 = (塊長度 - (數據長度 % 塊長度)), 在填充字節序列中所有字節填充為
0x00
,零填充在數據最后字節為零的時候可能不可逆
例:假定塊長度為 8,數據長度為 2,則填充字節數等于 6,數據等于 FF FF : | FF FF 00 00 00 00 00 00 |
特定的,為了使算法可以逆向去除多余的填充字符,所以當數據長度恰好等于塊長度的時候,需要補足塊長度的字節.例如塊長度為8,數據長度為8,則填充字節數等于8.
代碼部分
為了可以完成自己的填充算法方便調用,并同時省去PKCS7部分的填充操作,先實現一個枚舉并保持系統枚舉CCPadding
的枚舉值也在其中,代碼實例如下
typedef enum : NSUInteger {
CcCryptorNoPadding = 0, //No Padding to source Data
CcCryptorPKCS7Padding = 1, // PKCS_7 | Each byte fills in the length of the sequence of the bytes . ***This Padding Mode use the system method.***
CcCryptorZeroPadding = 2, // 0x00 Padding | Each byte fills 0x00
CcCryptorANSIX923, // The last byte fills the length of the byte sequence, and the remaining bytes are filled with 0x00.
CcCryptorISO10126 // The last byte fills the length of the byte sequence and the remaining bytes fill the random data.
}CcCryptorPadding;
并在調用CCCryptorCreateWithMode
函數之前,判斷填充模式CCPadding paddingMode = ((padding == ccPKCS7Padding) ? ccPKCS7Padding:ccNoPadding) ;
,使得我們可以正常使用系統的填充模式之外使用其他自定義填充模式。
下面是iOS部分如何實現對數據進行填充,此函數需要在CCCryptorCreateWithMode
函數之前對數據進行處理.
// Fill in the bytes that need to be encrypted.
static NSData * bitPadding(CCOperation operation, CCAlgorithm algorithm ,CcCryptorPadding padding, NSData *data)
{
//當模式為PKCS7填充時,我們選擇使用系統PKCS7填充,返回原始數據,避免二次填充
if (padding == CcCryptorPKCS7Padding) {
return data;
}
// 當前處于加密模式,并且不是流加密的情況下.
if (operation == kCCEncrypt && (algorithm != CcCryptoAlgorithmRC4) ) {
//獲取一份可操作的數據對象
NSMutableData *sourceData = data.mutableCopy;
//設置默認塊大小為8.
int blockSize = 8;
switch (algorithm) {
// 當AES加密時,塊大小為16.
case kCCAlgorithmAES:
blockSize = kCCBlockSizeAES128;
break;
case kCCAlgorithmDES:
case kCCAlgorithm3DES:
case kCCAlgorithmCAST:
case kCCAlgorithmBlowfish:
case kCCAlgorithmRC2:
default:
blockSize = 8;
break;
}
// 根據選定填充模式實現不同填充
switch (padding) {
// 零填充
case CcCryptorZeroPadding:
{
int pad = 0x00;
int diff = blockSize - (sourceData.length % blockSize);
for (int i = 0; i < diff; i++) {
[sourceData appendBytes:&pad length:1];
}
}
break;
// ANSIX923
case CcCryptorANSIX923:
{
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;
//ISO10126
case CcCryptorISO10126:
{
int diff = blockSize - (sourceData.length % blockSize);
for (int i = 0; i < diff - 1; i++) {
int pad = arc4random() % 254 + 1;
[sourceData appendBytes:&pad length:1];
}
[sourceData appendBytes:&diff length:1];
}
break;
default:
break;
}
return sourceData;
}
return data;
}
當我們對數據解密過后,如果不去除填充字節,會發現在解密后的字符串末尾發現填充字節造成的字符
因此,我們需要在解密過后對數據進行去除可能存在的填充字節的處理。代碼示例如下:
static NSData * removeBitPadding(CCOperation operation, CCAlgorithm algorithm ,CcCryptorPadding padding, NSData *sourceData)
{
//若為PKCS7,則交由系統函數處理.
if (padding == CcCryptorPKCS7Padding) {
return sourceData;
}
//若為解密,并且為塊加密
if (operation == kCCDecrypt && (algorithm != CcCryptoAlgorithmRC4) ) {
// 設置需要非填充字節長度為0
int correctLength = 0;
// 設置塊長度默認值為8
int blockSize = 8;
判斷塊長度
switch (algorithm) {
case kCCAlgorithmAES:
blockSize = kCCBlockSizeAES128;
break;
case kCCAlgorithmDES:
case kCCAlgorithm3DES:
case kCCAlgorithmCAST:
case kCCAlgorithmBlowfish:
default:
blockSize = 8;
break;
}
// 將data -> byte
Byte *testByte = (Byte *)[sourceData bytes];
// 獲取最后一位字節.
char end = testByte[sourceData.length - 1];
// 根據選定填充模式不同去除可能存在的填充字符
if (padding == CcCryptorZeroPadding && end == 0) {
for (int i = (short)sourceData.length - 1; i > 0 ; i--) {
//若不相等為零字節,則停止
if (testByte[i] != end) {
// 非填充字節的長度為i
correctLength = i + 1;
break;
}
}
}
else if ((padding == CcCryptorANSIX923 || padding == CcCryptorISO10126) && (end > 0 && end < blockSize + 1)){
if (padding == CcCryptorISO10126 || ( testByte[sourceData.length - 2] == 0 && testByte[sourceData.length - end] == 0)) {
//若填充模式為ANSIX923 或者ISO10126 則根據最后一位字節的大小判斷填充字節的長度以及非填充字節的長度
correctLength = (short)sourceData.length - end;
}
}
// 直接根據獲取的非填充字節的長度以及Byte 生成新的僅有正確字節的data數據.
NSData *data = [NSData dataWithBytes:testByte length:correctLength];
return data;
}
return sourceData;
}
想看源碼的可以去Github.
有小伙伴們對CommonCrypto
、對稱加密感興趣的可以看看這篇文章iOS 關于CommonCrypto加密 ②(CommonCryptor AES|DES|3DES|CAST|BlowFish)