iOS实现对称加密多种填充方式(ANSIX923、ISO10126、Zero)

777 阅读5分钟

本人已参与「新人创作礼」活动,一起开启掘金创作之路。

前言

    前段时间调研了iOS有关加密的框架CommonCrypto.在查阅资料的过程中,有一篇《iOS加密解密:AES,DES,3DES,BLOWFISH(含有多种模式和算法说明)》 的文章。 在文章中作者对需要加密的数据实现PKCS7Padding填充,并在调用CCCryptorCreateWithMode()函数时将padding参数赋值为ccNoPadding(不填充)。在作者的基础上我增添了ANSIX923,ISO10126以及Zero填充方式,以及解密之后的去除数据的填充。

    在对称加密中,可以概分为两种模式加密,流加密以及块加密,当我们使用块加密(也就是分组加密)的时候,例如AES、DES,每次是对固定大小的分组数据进行处理。但是大多数需要加密的数据并不是固定大小的倍数长度。例如AES数据块为128位,也就是16字节长度,而需要加密的长度可能为15、26等等。为了解决这个问题,我们就需要对数据进行填补操作,将数据补齐至对应块长度。

补码原理

接下来呢讲一下关于数据填充ANSIX923ISO10126PKCS7以及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)