来自: http://www.henishuo.com/ios-aes128-ecb-nopadding/
前言 谈谈aes加密,网上有很多的版本,当我没有真正在加密安全问题前,总以为百度出来某个aes加密算法就可以直接使用,实际上当我真正要做加密时,遇到了很多的坑,原来不是拿过来就能用的。写下本篇文章,记录下曾经遇到的坑,严防以后再出现同样的坑。
aes规则 原输入数据不够16字节的整数位时,就要补齐。因此就会有padding,若使用不同的padding,那么加密出来的结果也会不一样。
aes加密算法 苹果提供给我们的api只有这一个函数用来加密或者解密:
cccryptorstatus cccrypt( ccoperation op, /* kccencrypt, etc. */ ccalgorithm alg, /* kccalgorithmaes128, etc. */ ccoptions options, /* kccoptionpkcs7padding, etc. */ const void *key, size_tkeylength, const void *iv, /* optional initialization vector */ const void *datain, /* optional per op and alg */ size_tdatainlength, void *dataout, /* data returned here */ size_tdataoutavailable, size_t*dataoutmoved) __osx_available_starting(__mac_10_4, __iphone_2_0);
其中第一个 ccoperation 只有两个值,要么是 kccencrypt 表示加密,要么是 kccdecrypt 表示解密。 第二个参数表示加密的算法,它只有以下向种类型: enum { kccalgorithmaes128 = 0, kccalgorithmaes = 0, kccalgorithmdes, kccalgorithm3des, kccalgorithmcast, kccalgorithmrc4, kccalgorithmrc2, kccalgorithmblowfish };typedef uint32_tccalgorithm;
我们这里使用的是 kccalgorithmaes128 表示使用aes128位加密。
第三个参数表示选项,这里使用的是 kccoptionecbmode ,表示ecb: enum { /* options for block ciphers */ kccoptionpkcs7padding = 0x0001, kccoptionecbmode = 0x0002 /* stream ciphers currently have no options */};typedef uint32_tccoptions;
第四个参数表示加密/解密的密钥。 第五个参数keylength表示密钥的长度。 第六个参数iv是个固定值,通过直接使用密钥即可。大家一定要注视这个参数,如果安卓、服务端和ios端不统一,那么加密结果就会不一样,解密可能能解出来,但是解密后在末尾会出现一些\0、\t之类的。 第七个参数datain表示要加密/解密的数据。 第八个参数datainlength表示要加密/解密的数据的长度。 第九个参数dataout用于接收加密后/解密后的结果。 第十个参数dataoutavailable表示加密后/解密后的数据的长度。 第十一个参数dataoutmoved表示实际加密/解密的数据的长度。(因为有补齐) 加密算法 依赖于第三方库: gtmbase64 ,这个库已经几年没有维护了,现在还是mrc版本,要使用请到github查看使用教程,那里有arc接入说明:
+ (nsstring *)hyb_aesencrypt:(nsstring *)plaintextpassword:(nsstring *)key { if (key == nil || (key.length != 16 && key.length != 32)) { return nil; } char keyptr[kcckeysizeaes128+1]; memset(keyptr, 0, sizeof(keyptr)); [keygetcstring:keyptrmaxlength:sizeof(keyptr)encoding:nsutf8stringencoding]; char ivptr[kccblocksizeaes128+1]; memset(ivptr, 0, sizeof(ivptr)); [keygetcstring:ivptrmaxlength:sizeof(ivptr)encoding:nsutf8stringencoding]; nsdata* data = [plaintextdatausingencoding:nsutf8stringencoding]; nsuinteger datalength = [datalength]; int diff = kcckeysizeaes128 - (datalength % kcckeysizeaes128); unsigned long newsize = 0; if(diff > 0) { newsize = datalength + diff; } char dataptr[newsize]; memcpy(dataptr, [databytes], [datalength]); for(int i = 0; i < diff; i++) { // 这里是关键,这里是使用nopadding的 dataptr[i + datalength] = 0x0000; } size_tbuffersize = newsize + kccblocksizeaes128; void *buffer = malloc(buffersize); memset(buffer, 0, buffersize); size_tnumbytescrypted = 0; cccryptorstatus cryptstatus = cccrypt(kccencrypt, kccalgorithmaes128, kccoptionecbmode, [keyutf8string], kcckeysizeaes128, ivptr, dataptr, sizeof(dataptr), buffer, buffersize, &numbytescrypted); if (cryptstatus == kccsuccess) { nsdata *resultdata = [nsdatadatawithbytesnocopy:bufferlength:numbytescrypted]; return [gtmbase64stringbyencodingdata:resultdata]; } free(buffer); return nil;}
对于加密算法,大家一定要注意,保证ios、安卓、服务端的加密规则是一定的,建议统一使用no padding的,这里使用no padding是这样的:
for(int i = 0; i < diff; i++) { // 这里是关键,这里是使用nopadding的 dataptr[i + datalength] = 0x0000;}
其实所谓padding就是指在位数不够需要补齐时,使用什么来填充,而no padding就是使用16个0,对应0x0000.如果三端不统一,加密出来就算能解密,也会出现一些奇怪的字符,甚至会有部分乱码。
另外,这里使用的是 kccoptionecbmode ,也就是ecb。在安卓端和php端,也得使用ecb。在调试过程中,发现php使用cbc解密不了ios端的。于是改成了使用ecb。
解密算法 依赖于第三方库: gtmbase64 ,这个库已经几年没有维护了,现在还是mrc版本,要使用请到github查看使用教程,那里有arc接入说明:
+ (nsstring *)hyb_aesdecrypt:(nsstring *)encrypttextpassword:(nsstring *)key { if (key == nil || (key.length != 16 && key.length != 32)) { return nil; } char keyptr[kcckeysizeaes128 + 1]; memset(keyptr, 0, sizeof(keyptr)); [keygetcstring:keyptrmaxlength:sizeof(keyptr)encoding:nsutf8stringencoding]; char ivptr[kccblocksizeaes128 + 1]; memset(ivptr, 0, sizeof(ivptr)); [keygetcstring:ivptrmaxlength:sizeof(ivptr)encoding:nsutf8stringencoding]; nsdata *data = [gtmbase64decodedata:[encrypttextdatausingencoding:nsutf8stringencoding]]; nsuinteger datalength = [datalength]; size_tbuffersize = datalength + kccblocksizeaes128; void *buffer = malloc(buffersize); size_tnumbytescrypted = 0; cccryptorstatus cryptstatus = cccrypt(kccdecrypt, kccalgorithmaes128, kccoptionecbmode, [keyutf8string], kccblocksizeaes128, ivptr, [databytes], datalength, buffer, buffersize, &numbytescrypted); if (cryptstatus == kccsuccess) { nsdata *resultdata = [nsdatadatawithbytesnocopy:bufferlength:numbytescrypted]; nsstring *decoded=[[nsstring alloc]initwithdata:resultdataencoding:nsutf8stringencoding]; return decoded; } free(buffer); return nil;}
解密时也得跟加密一样指定为ecb,否则解出来会出现乱码,或者末尾会出现\0、\t之类的符号。
写在最后 开发中总会遇到各种坑,网上查了很多的资料,但是终究没有说明解决的办法,而是只将自己的代码放出来。对于刚接触这方面知识的开发人员来说,是很懵懂的。甚至很多新手会觉得系统就是这样的,我也没办法。其实总会有解决办法的,关键在于与其他各端统一连调。
关注我 swift/objc技术群一:324400294(已满) swift/objc技术群二:494669518 objc/swift高级群:461252383(注明年限,新手勿扰) 关注微信公众号: iosdevshares
关注新浪微博账号:标哥jacky
标哥的github地址: coderjackyhuang
支持并捐助 如果您觉得文章对您很有帮忙,希望得到您的支持。您的捐肋将会给予我最大的鼓励,感谢您的支持!
支付宝捐助 微信捐助