PHP与iOS之间的AES加解密

前言

在项目开发过程中,为了保证传输数据的安全性,我们经常要对传输的内容进行加密处理,以增加别人破解的成本。常用的加密算法有很多,今天我们先围绕AES加密算法进行一个使用总结

AES算法介绍

AES是高级加密标准(Advanced Encryption Standard)的缩写,在密码学中又被称为Rijndael加密法,如果想对AES的背景有更多的了解可以移步到维基百科-高级加密标准

AES加密时需要统一四个参数:

  • 密钥长度 (Key Size)
  • 加密模式 (Cipher Mode)
  • 填充方式 (Padding)
  • 初始向量 (Initialization Vector)

由于前后端开发所使用的语言不统一,导致经常出现前后端之间互相不能解密的情况出现,其实,无论什么语言系统,AES的算法总是相同的,导致结果不一致的原因在于上述的四个参数不一致,下面就来了解一下这四个参数的含义

密钥长度

AES算法下,key的长度有三种:128、192、256 bits,三种不同密钥长度就需要我们传入的key传入不同长度的字符串,例如我们选择AES-128,那我们定的key需要是长度为16的字符串

加密模式

AES属于块加密,块加密中有CBC、ECB、CTR、OFB、CFB等几种工作模式,为了保持前后端统一,我们选择ECB模式

填充方式

由于块加密只能对特定长度的数据块进行加密,因此CBC、ECB模式需要在最后一数据块加密前进行数据填充

初始向量

使用除ECB以外的其他加密模式均需要传入一个初始向量,其大小与Block Size相等

代码实现

PHP端代码实现

<?php
/*
 * 定义类cryptAES 专用于AES加解密
 * 初始化时传入密钥长度、加密Key、初始向量、加密模式四个字段
 */
class cryptAES
{
    public $iv = null;
    public $key = null;
    public $bit = 128;
    private $cipher;

    public function __construct($bit, $key, $iv, $mode)
    {
        if(empty($bit) || empty($key) || empty($iv) || empty($mode))
        {
            return NULL;
        }

        $this->bit = $bit;
        $this->key = $key;
        $this->iv = $iv;
        $this->mode = $mode;

        switch($this->bit)
        {
            case 192 : $this->cipher = MCRYPT_RIJNDAEL_192; break;
            case 256 : $this->cipher = MCRYPT_RIJNDAEL_256; break;
            default : $this->cipher = MCRYPT_RIJNDAEL_128;
        }
        switch($this->mode)
        {
            case 'ecb' : $this->mode = MCRYPT_MODE_ECB; break;
            case 'cfb' : $this->mode = MCRYPT_MODE_CFB; break;
            case 'ofb' : $this->mode = MCRYPT_MODE_OFB; break;
            case 'nofb' : $this->mode = MCRYPT_MODE_NOFB; break;
            default : $this->mode = MCRYPT_MODE_CBC;
        }
    }

        /*
         * 加密数据并返回
         */
    public function encrypt($data)
    {
        $data = base64_encode(mcrypt_encrypt($this->cipher, $this->key, $data, $this->mode, $this->iv));
        return $data;
    }

        /*
         * 解密数据并返回
         */
    public function decrypt($data)
    {
        $data = mcrypt_decrypt($this->cipher, $this->key, base64_decode($data), $this->mode, $this->iv);
        $data = rtrim(rtrim($data), "\x00..\x1F");
        return $data;
    }
}

iOS端代码实现

// NSString+AESSecurity.h
@interface NSString (AESSecurity)

+ (NSString *)encrypyAES:(NSString *)content key:(NSString *)key;

+ (NSString *)descryptAES:(NSString *)content key:(NSString *)key;

@end

// NSString+AESSecurity.m
#import "NSString+AESSecurity.h"

#import <CommonCrypto/CommonCrypto.h>

// 初始向量
NSString *const kInitVector = @"0123456789";

// 密钥长度
size_t const kKeySize = kCCKeySizeAES128;

@implementation NSString (AESSecurity)

+ (NSString *)encrypyAES:(NSString *)content key:(NSString *)key {
    
    NSData *contentData = [content dataUsingEncoding:NSUTF8StringEncoding];
    NSUInteger dataLength = contentData.length;
    
    // 为结束符'\\0' +1
    char keyPtr[kKeySize + 1];
    memset(keyPtr, 0, sizeof(keyPtr));
    [key getCString:keyPtr maxLength:sizeof(keyPtr) encoding:NSUTF8StringEncoding];
    
    size_t encryptSize = dataLength + kCCBlockSizeAES128;
    void *encryptedBytes = malloc(encryptSize);
    size_t actualOutSize = 0;
    
    NSData *initVector = [kInitVector dataUsingEncoding:NSUTF8StringEncoding];
    
    CCCryptorStatus cryptStatus = CCCrypt(kCCEncrypt,
                                          kCCAlgorithmAES128,
                                          kCCOptionPKCS7Padding | kCCOptionECBMode, // 加密模式
                                          keyPtr,
                                          kKeySize,
                                          initVector.bytes,
                                          contentData.bytes,
                                          dataLength,
                                          encryptedBytes,
                                          encryptSize,
                                          &actualOutSize);
    
    if (cryptStatus == kCCSuccess) {
        // 对加密后的数据进行 base64 编码
        return [[NSData dataWithBytesNoCopy:encryptedBytes length:actualOutSize] base64EncodedStringWithOptions:NSDataBase64EncodingEndLineWithLineFeed];
    }
    free(encryptedBytes);
    return nil;
}

+ (NSString *)descryptAES:(NSString *)content key:(NSString *)key {//
    // 把 base64 String 转换成 NSData
    NSData *contentData = [[NSData alloc] initWithBase64EncodedString:content options:NSDataBase64DecodingIgnoreUnknownCharacters];
    NSUInteger dataLength = contentData.length;
    
    char keyPtr[kKeySize + 1];
    memset(keyPtr, 0, sizeof(keyPtr));
    [key getCString:keyPtr maxLength:sizeof(keyPtr) encoding:NSUTF8StringEncoding];
    
    size_t decryptSize = dataLength + kCCBlockSizeAES128;
    void *decryptedBytes = malloc(decryptSize);
    size_t actualOutSize = 0;
    
    NSData *initVector = [kInitVector dataUsingEncoding:NSUTF8StringEncoding];
    
    CCCryptorStatus cryptStatus = CCCrypt(kCCDecrypt,
                                          kCCAlgorithmAES,
                                          kCCOptionPKCS7Padding | kCCOptionECBMode, // 加密模式
                                          keyPtr,
                                          kKeySize,
                                          initVector.bytes,
                                          contentData.bytes,
                                          dataLength,
                                          decryptedBytes,
                                          decryptSize,
                                          &actualOutSize);
    
    if (cryptStatus == kCCSuccess) {
        return [[NSString alloc] initWithData:[NSData dataWithBytesNoCopy:decryptedBytes length:actualOutSize] encoding:NSUTF8StringEncoding];
    }
    
    free(decryptedBytes);
    return nil;
}

@end

注意点

在iOS上,字符串经过加解密后可能会在数据中添加一些操作符这会导致我们想进一步处理解密后的字符串时会处理失败,例如,当我们想将解密后的json字符串转成字典时,可能会抛出Garbage at End的错误,解决方案如下:

  1. 将字符串中的所有控制符替换成空字符
NSString *newStr = [oldStr stringByTrimmingCharactersInSet:[NSCharacterSet controlCharacterSet]];
  1. 将处理后的字符串进行json序列化操作
NSError *err = nil;
            
NSData *jsondata = [str dataUsingEncoding:NSUTF8StringEncoding];
            
NSArray *arr = [NSJSONSerialization JSONObjectWithData:jsondata options:NSJSONReadingMutableLeaves error:&err];

附上相关模块的代码

AESSecurity-PHP

AESSecurity-iOS

欢迎star和fork~


欢迎访问我的个人博客

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,185评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,445评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,684评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,564评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,681评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,874评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,025评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,761评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,217评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,545评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,694评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,351评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,988评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,778评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,007评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,427评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,580评论 2 349

推荐阅读更多精彩内容