iOS与PHP/Android AES128 ECB NoPadding加密

谈谈AES加密,网上有很多的版本,当我没有真正在加密安全问题前,总以为百度出来某个AES加密算法就可以直接使用,实际上当我真正要做加密时,遇到了很多的坑,原来不是拿过来就能用的。写下本篇文章,记录下曾经遇到的坑,严防以后再出现同样的坑。

AES规则

原输入数据不够16字节的整数位时,就要补齐。因此就会有padding,若使用不同的padding,那么加密出来的结果也会不一样。

AES加密算法

苹果提供给我们的API只有这一个函数用来加密或者解密:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

(

/* kCCEncrypt, etc. */

/* kCCAlgorithmAES128, etc. */

/* kCCOptionPKCS7Padding, etc. */

,

,

/* optional initialization vector */

/* optional per op and alg */

,

/* data RETURNED here */

,

)

;

  • 其中第一个CCOperation只有两个值,要么是kCCEncrypt表示加密,要么是kCCDecrypt表示解密。
  • 第二个参数表示加密的算法,它只有以下向种类型:

1

2

3

4

5

6

7

8

9

10

11

12

13

{

,

,

,

,

;

;

我们这里使用的是kCCAlgorithmAES128表示使用AES128位加密。

  • 第三个参数表示选项,这里使用的是kCCOptionECBMode,表示ECB:

1

2

3

4

5

6

7

8

9

{

/* options for block ciphers */

,

0x0002

/* stream ciphers currently have no options */

;

;

  • 第四个参数表示加密/解密的密钥。
  • 第五个参数keyLength表示密钥的长度。
  • 第六个参数iv是个固定值,通过直接使用密钥即可。大家一定要注视这个参数,如果安卓、服务端和iOS端不统一,那么加密结果就会不一样,解密可能能解出来,但是解密后在末尾会出现一些\0、\t之类的。
  • 第七个参数dataIn表示要加密/解密的数据。
  • 第八个参数dataInLength表示要加密/解密的数据的长度。
  • 第九个参数dataOut用于接收加密后/解密后的结果。
  • 第十个参数dataOutAvailable表示加密后/解密后的数据的长度。
  • 第十一个参数dataOutMoved表示实际加密/解密的数据的长度。(因为有补齐)

加密算法

依赖于第三方库:GTMBase64,这个库已经几年没有维护了,现在还是MRC版本,要使用请到GITHUB查看使用教程,那里有ARC接入说明:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

{

{

;

}

;

;

;

;

;

;

;

;

;

;

{

;

}

;

;

{

// 这里是关键,这里是使用NoPadding的

;

}

;

;

;

;

,

,

,

,

,

,

,

,

,

,

;

{

;

;

}

;

;

}

对于加密算法,大家一定要注意,保证iOS、安卓、服务端的加密规则是一定的,建议统一使用No Padding的,这里使用No Padding是这样的:

1

2

3

4

5

6

{

// 这里是关键,这里是使用NoPadding的

;

}

其实所谓Padding就是指在位数不够需要补齐时,使用什么来填充,而No Padding就是使用16个0,对应0x0000.如果三端不统一,加密出来就算能解密,也会出现一些奇怪的字符,甚至会有部分乱码。

另外,这里使用的是kCCOptionECBMode,也就是ECB。在安卓端和PHP端,也得使用ECB。在调试过程中,发现PHP使用CBC解密不了IOS端的。于是改成了使用ECB。

解密算法

依赖于第三方库:GTMBase64,这个库已经几年没有维护了,现在还是MRC版本,要使用请到GITHUB查看使用教程,那里有ARC接入说明:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

{

{

;

}

;

;

;

;

;

;

;

;

;

;

;

,

,

,

,

,

,

,

,

,

,

;

{

;

;

;

}

;

;

}

解密时也得跟加密一样指定为ECB,否则解出来会出现乱码,或者末尾会出现\0、\t之类的符号。

写在最后

开发中总会遇到各种坑,网上查了很多的资料,但是终究没有说明解决的办法,而是只将自己的代码放出来。对于刚接触这方面知识的开发人员来说,是很懵懂的。甚至很多新手会觉得系统就是这样的,我也没办法。其实总会有解决办法的,关键在于与其他各端统一连调。

  • 一行代码实现加密

更新:MD5加密是单向的,只能加密不能解密(破解除外)。标题可能会引起读者误解,已经改正,感谢Li_Cheng同学的提醒,另外笔者发现Li_Cheng同学有篇MD5加密更为详尽的文章,推荐阅读:iOS开发 关于MD5加密的相关使用

加密的Demo,欢迎下载

java端的加密解密,读者可以看我同事的这篇文章http://www.jianshu.com/p/98569e81cc0b

最近做了一个移动项目,是有服务器和客户端类型的项目,客户端是要登录才行的,服务器也会返回数据,服务器是用Java开发的,客户端要同时支持多平台(Android、iOS),在处理iOS的数据加密的时候遇到了一些问题。起初采取的方案是DES加密,老大说DES加密是对称的,网络抓包加上反编译可能会被破解,故采取RSA方式加密。RSA加密时需要公钥和私钥,客户端保存公钥加密数据,服务器保存私钥解密数据。(iOS端公钥加密私钥解密、java端公钥加密私钥解密,java端私钥加密公钥解密都容易做到,iOS不能私钥加密公钥解密,只能用于验签)。

问题

问题1:iOS端公钥加密的数据用Java端私钥解密。

iOS无论使用系统自带的sdk函数,用mac产生的或者使用java的jdk产生的公钥和私钥,进行加密解密自己都可以使用。不过ios加密,java解密,或者反过来就不能用了。要么是无法创建报告个-9809或-50的错误,要么解出来是乱码。ios系统函数种只有用公钥加密,私钥解密的方式。而公钥加密每次结果都不同。

MAC上生成公钥、私钥的方法,及使用

  • 1.打开终端,切换到自己想输出的文件夹下
  • 2.输入指令:openssl(openssl是生成各种秘钥的工具,mac已经嵌入
  • 3.输入指令:genrsa -out rsa_private_key.pem 1024 (生成私钥,java端使用的)
  • 4.输入指令:rsa -in rsa_private_key.pem -out rsa_public_key.pem -pubout (生成公钥)
  • 5.输入指令:pkcs8 -topk8 -in rsa_private_key.pem -out pkcs8_rsa_private_key.pem -nocrypt(私钥转格式,在ios端使用私钥解密时用这个私钥)

    注意:在MAC上生成三个.pem格式的文件,一个公钥,两个私钥,都可以在终端通过指令vim xxx.pem 打开,里面是字符串,第三步生成的私钥是java端用来解密数据的,第五步转换格式的私钥iOS端可以用来调试公钥、私钥解密(因为私钥不留在客户端)

    详细步骤

问题2:服务器返回数据也要加密,老大打算用java私钥加密,ios用公钥解密(由于iOS做不到用私钥加密公钥解密,只能私钥加密公钥验签),所以这种方案也有问题。

通过看一些大牛的介绍,了解了iOS常用的加密方式

  • 1 通过简单的URLENCODE + BASE64编码防止数据明文传输
  • 2 对普通请求、返回数据,生成MD5校验(MD5中加入动态密钥),进行数据完整性(简单防篡改,安全性较低,优点:快速)校验
  • 3 对于重要数据,使用RSA进行数字签名,起到防篡改作
  • 4 对于比较敏感的数据,如用户信息(登陆、注册等),客户端发送使用RSA加密,服务器返回使用DES(AES)加密

    原因:客户端发送之所以使用RSA加密,是因为RSA解密需要知道服务器私钥,而服务器私钥一般盗取难度较大;如果使用DES的话,可以通过破解客户端获取密钥,安全性较低。而服务器返回之所以使用DES,是因为不管使用DES还是RSA,密钥(或私钥)都存储在客户端,都存在被破解的风险,因此,需要采用动态密钥,而RSA的密钥生成比较复杂,不太适合动态密钥,并且RSA速度相对较慢,所以选用DES)

    所以此次加密,我们选择了第四种加密方式

加密方式

ios端进行DES加密、解密时非常方便

1、引入头文件 #import "DES3Util.h"
2、加密时调用类方法  +(NSString *) encryptUseDES:(NSString *)plainText key:(NSString *)key;
3、解密时调用类方法  +(NSString *)decryptUseDES:(NSString *)cipherText key:(NSString *)key;

ios端进行RSA加密、解密时非常方便

1、引入头文件 #import "RSAUtil.h"
2、公钥加密时调用类方法:
+ (NSString *)encryptString:(NSString *)str publicKey:(NSString *)pubKey;
+ (NSData *)encryptData:(NSData *)data publicKey:(NSString *)pubKey;
3、私钥解密时调用类方法 
+ (NSString *)decryptString:(NSString *)str privateKey:(NSString *)privKey;
+ (NSData *)decryptData:(NSData *)data privateKey:(NSString *)privKey;

ios端进行MD5加密、解密时非常方便

1、引入头文件 #import "MD5Util"
2、加密时调用方法:- (NSString *)md5:(NSString *)str;

ios端进行AES加密、解密时非常方便

1、引入头文件 #import "AES.h"
2、加密时调用方法
+ (NSString *)encrypt:(NSString *)message password:(NSString *)password;
2、解密时调用的方法
+ (NSString *)decrypt:(NSString *)base64EncodedString password:(NSString *)password;

有关RSA、MD5、AES加密的原理介绍

  • MD5加密

iOS中提供了很多种加密算法,对于存储密码,可以使用不可逆的MD5加密。

使用MD5加密需要导入头文件:

''#import <CommonCrypto/CommonDigest.h>

##### 简单的MD5加密
+ ( NSString *)md5String:( NSString *)str

{

const char *myPasswd = [str UTF8String ];

unsigned char mdc[ 16 ];

CC_MD5 (myPasswd, ( CC_LONG ) strlen (myPasswd), mdc);

NSMutableString *md5String = [ NSMutableString string ];

for ( int i = 0 ; i< 16 ; i++) {

[md5String appendFormat : @"%02x" ,mdc[i]];

}

return md5String;

}

##### 复杂一些的MD5加密
+ ( NSString *)md5String:( NSString *)str

 {

 const char *myPasswd = [str UTF8String ];

 unsigned char mdc[ 16 ];

 CC_MD5 (myPasswd, ( CC_LONG ) strlen (myPasswd), mdc);

 NSMutableString *md5String = [ NSMutableString string ];

 [md5String appendFormat : @"%02x" ,mdc[ 0 ]];

 for ( int i = 1 ; i< 16 ; i++) {

 [md5String appendFormat : @"%02x" ,mdc[i]^mdc[ 0 ]];
  • AES加密

高级加密标准(Advanced Encryption Standard,AES),又称Rijndael加密法。 以下实现代码中分别为NSData和NSString增加了一个Category。使用时直接调用即可。

需要注意的是,AES并不能作为HASH算法,加密并解密后的结果,并不一定与原文相同,使用时请注意进行结果验算。例如解密原文的长度,格式规则等。 NG实例

原文:170987350
密码:170

Objective-c的AES加密和解密算法的具体实现代码如下: 1.拓展NSData,增加AES256加密方法

//
//NSData+AES256.h
//
 
#import <Foundation/Foundation.h>
#import <CommonCrypto/CommonDigest.h>
#import <CommonCrypto/CommonCryptor.h>
 
@interface NSData(AES256)
-(NSData *) aes256_encrypt:(NSString *)key;
-(NSData *) aes256_decrypt:(NSString *)key;
@end
 
 
//
//NSData+AES256.m
//
#import "NSData+AES256.h"
 
@implementation NSData(AES256)
 
- (NSData *)aes256_encrypt:(NSString *)key   //加密
{
  char keyPtr[kCCKeySizeAES256+1];
  bzero(keyPtr, sizeof(keyPtr));
  [key getCString:keyPtr maxLength:sizeof(keyPtr) encoding:NSUTF8StringEncoding];
  NSUInteger dataLength = [self length];
  size_t bufferSize = dataLength + kCCBlockSizeAES128;
  void *buffer = malloc(bufferSize);
  size_t numBytesEncrypted = 0;
  CCCryptorStatus cryptStatus = CCCrypt(kCCEncrypt, kCCAlgorithmAES128,
                      kCCOptionPKCS7Padding | kCCOptionECBMode,
                      keyPtr, kCCBlockSizeAES128,
                      NULL,
                      [self bytes], dataLength,
                      buffer, bufferSize,
                      &numBytesEncrypted);
  if (cryptStatus == kCCSuccess) {
    return [NSData dataWithBytesNoCopy:buffer length:numBytesEncrypted];
  }
  free(buffer);
  return nil;
}
 
 
- (NSData *)aes256_decrypt:(NSString *)key   //解密
{
  char keyPtr[kCCKeySizeAES256+1];
  bzero(keyPtr, sizeof(keyPtr));
  [key getCString:keyPtr maxLength:sizeof(keyPtr) encoding:NSUTF8StringEncoding];
  NSUInteger dataLength = [self length];
  size_t bufferSize = dataLength + kCCBlockSizeAES128;
  void *buffer = malloc(bufferSize);
  size_t numBytesDecrypted = 0;
  CCCryptorStatus cryptStatus = CCCrypt(kCCDecrypt, kCCAlgorithmAES128,
                      kCCOptionPKCS7Padding | kCCOptionECBMode,
                      keyPtr, kCCBlockSizeAES128,
                      NULL,
                      [self bytes], dataLength,
                      buffer, bufferSize,
                      &numBytesDecrypted);
  if (cryptStatus == kCCSuccess) {
    return [NSData dataWithBytesNoCopy:buffer length:numBytesDecrypted];
 
  }
  free(buffer);
  return nil;
}
@end

2.拓展NSString,增加AES256加密方法,需要导入NSData+AES256.h

//
//NSString +AES256.h
//
 
#import <Foundation/Foundation.h>
#import <CommonCrypto/CommonDigest.h>
#import <CommonCrypto/CommonCryptor.h>
 
#import "NSData+AES256.h"
 
@interface NSString(AES256)
 
-(NSString *) aes256_encrypt:(NSString *)key;
-(NSString *) aes256_decrypt:(NSString *)key;
 
@end
 
 
//
//NSString +AES256.h
//
 
@implementation NSString(AES256)
 
-(NSString *) aes256_encrypt:(NSString *)key
{
  const char *cstr = [self cStringUsingEncoding:NSUTF8StringEncoding];
  NSData *data = [NSData dataWithBytes:cstr length:self.length];
  //对数据进行加密
  NSData *result = [data aes256_encrypt:key];
 
  //转换为2进制字符串
  if (result && result.length > 0) {
 
    Byte *datas = (Byte*)[result bytes];
    NSMutableString *output = [NSMutableString stringWithCapacity:result.length * 2];
    for(int i = 0; i < result.length; i++){
      [output appendFormat:@"%02x", datas[i]];
    }
    return output;
  }
  return nil;
}
 
-(NSString *) aes256_decrypt:(NSString *)key
{   
  //转换为2进制Data
  NSMutableData *data = [NSMutableData dataWithCapacity:self.length / 2];
  unsigned char whole_byte;
  char byte_chars[3] = {'\0','\0','\0'};
  int i;
  for (i=0; i < [self length] / 2; i++) {
    byte_chars[0] = [self characterAtIndex:i*2];
    byte_chars[1] = [self characterAtIndex:i*2+1];
    whole_byte = strtol(byte_chars, NULL, 16);
    [data appendBytes:&whole_byte length:1];
  }
 
  //对数据进行解密
  NSData* result = [data aes256_decrypt:key];
  if (result && result.length > 0) {
    return [[NSString alloc] initWithData:result encoding:NSUTF8StringEncoding]autorelease];
  }
  return nil;
}
@end