目录
- 数据加密概述
- 对称密钥加密概述
- AES(Rijndael)概述
- iOS中的AES(
iOS SDK中的Security.framework库
,非openssl库
)
# 数据加密简述
数据加密(Encryption)是指将明文信息(Plaintext)采取
数学方法进行函数转换
成密文(Ciphertext),只有特定接受方才能将其解密(Decryption)还原成明文的过程。
构成:
- 明文(Plaintext):加密前的原始信息
- 密文(Ciphertext):明文被加密后的信息
- 密钥(Key):控制加密算法和解密算法得以实现的关键信息,分为
加密密钥和解密密钥
(必然,密钥不同,由明文生成的密文的结果也不同) - 加密(Encryption):将明文通过数学算法转换成密文的过程
- 解密(Decryption):将密文还原成明文的过程
## 密码系统的一般模型
- 如果不论截取者获得了多少密文,但在密文中都没有足够的信息来唯一地确定出对应的明文,则这一密码体制称为
无条件安全
的,或称为理论上是不可破
的(这种方法是不太容易获得的,因此在现实生活中,更多是追求计算上安全即可。) - 如果密码体制中的密码不能被可使用的计算资源 破译,则这一密码体制称为在
计算上是安全的
(利用已有的最好方法破译某个密码系统所需要的代价超出了破译者的能力(如时间、空间、资金等资源))
# 对称密钥加密(单钥加密)
常用的对称加密算法:
- DES/3DES(3重DES)
- IDEA
- RC5
- AES(Rijndael)
DES早期用的很多,但是由于相对比较简单,加密的安全性偏低,所以现在一般都使用3DES或 AES来替代。
对称加密的优缺点:
优点:
- 算法公开(往往是标准算法,是可以公开的)
- 计算量小
- 加密速度快(核心是替换和移位,可以用硬件来实现)
- 加密效率高。
缺点:
- 交易双方都使用同样钥匙,安全性得不到保证。
- 每对用户每次使用对称加密算法时,都需要使用其他人不知道的唯一钥匙,使得发收双方所拥有的钥匙数量呈几何级数增长,密钥管理成为用户负担。
- 密钥分发比较困难,尤其网络环境中安全难以保障,易成为瓶颈。
# AES(Rijndael)
下面摘自 一篇写的非常全面的博客
AES, Advanced Encryption Standard,其实是一套标准:FIPS 197,而我们所说的AES算法其实是Rijndael算法。
Rijndael算法是基于代换-置换网络(SPN,Substitution-permutation network)的迭代算法。明文数据经过多轮次的转换后方能生成密文,每个轮次的转换操作由轮函数定义。轮函数任务就是根据密钥编排序列(即轮密码)对数据进行不同的代换及置换等操作。
补充:代换-置换网络SPN
是一系列被应用于分组密码中相关的数学运算,高级加密标准(AES)、3-Way、Kuznyechik、PRESENT、SAFER、SHARK、Square都有涉用。这种加密网络使用明文块和密钥块作为输入,并通过交错的若干“轮”(或“层”)代换操作和置换操作产生密文块。代换(Substitution)和置换(Permutation)分别被称作S盒(替换盒/S-boxes)和P盒(排列盒/P-boxes)
。由于其实施于硬件的高效性,SPN的应用十分广泛。
# iOS中的AES
部分摘自 简书博客1,加了一些个人理解,有兴趣可以直接移步原文
AES是开发中常用的加密算法之一。然而由于前后端开发使用的语言不统一,导致经常出现前端加密而后端不能解密的情况出现。然而无论什么语言系统,AES的算法总是相同的, 因此导致结果不一致的原因在于 加密设置的参数不一致 。于是先来看看在两个平台使用AES加密时需要统一的几个参数。
- 密钥长度(Key Size)
- 加密模式(Cipher Mode)
- 填充方式(Padding)
- 初始向量(Initialization Vector)
密钥长度
AES算法标准
下,key的长度有三种:128、192和256 bits。由于历史原因,JDK默认只支持不大于128 bits的密钥,而128 bits的key已能够满足商用安全需求。因此本例先使用AES-128。(Java使用大于128 bits的key方法在文末提及)
加密模式
AES属于块加密(Block Cipher),块加密中有ECB、CBC、CFB、OFB、CTR、CCM、GCM等几种工作模式。本例统一使用CBC模式。
填充方式
由于块加密只能对特定长度的数据块进行加密,因此CBC、ECB模式需要在最后一数据块加密前进行数据填充,解密后删除掉填充的数据
。(CFB,OFB和CTR模式由于与key进行加密操作的是上一块加密后的密文,因此不需要对最后一段明文进行填充)
-
NoPadding
顾名思义,不填充,自己对长度不足block size的部分进行填充 -
ZeroPadding
数据长度不对齐时使用0填充,否则不填充(当原数据尾部也存在0时,在unpadding时可能会存在问题)。 -
PKCS7Padding
如果数据长度需要填充n(n>0)个字节才对齐,那么填充n个字节,每个字节都是n;
如果数据本身就已经对齐了,则填充一块长度为块大小的数据,每个字节都是块大小。 -
PKCS5Padding
PKCS7Padding的子集,块大小固定为8字节,其它一致(即PKCS5Padding是限制块大小的PKCS7Padding
)。 -
PKCS1Padding
与RSA算法一起使用,这里不再赘述
附上文档链接:
PKCS #7: Cryptographic Message Syntax 10.3节中讲到了上面提到的填充算法, 对Block Size并没有做规定
PKCS #5: Password-Based Cryptography Specification 在6.1.1 中对 填充做了说明,该标准只讨论了 8字节(64位) 块的加密, 对其他块大小没有做说明,其填充算法跟 PKCS7是一样的
使用PKCS7Padding/PKCS5Padding填充时,最后一个字节肯定为填充数据的长度,所以在解密后,取最后一位,就可以准确删除填充的数据。
在iOS SDK中提供了PKCS7Padding,而JDK则提供了PKCS5Padding(限制Block Size为8 bytes),但AES等算法,后来都把BlockSize扩充到了16字节或更大,Java中,采用PKCS5实质上就是采用PKCS7
(PKCS5Padding与PKCS7Padding填充结果是相等的)。
初始向量
使用除ECB以外的其他加密模式均需要传入一个初始向量,其大小(即串的长度)与Block Size相等
(AES的Block Size为128 bits),而两个平台的API文档均指明当不传入初始向量时,系统将默认使用一个全0的初始向量。(在区块加密中,使用了初始化向量的加密模式被称为区块加密模式
)
以CBC为例:IV是长度为分组大小的一组随机,通常情况下不用保密,不过在大多数情况下,针对同一密钥不应多次使用同一组IV。 CBC要求第一个分组的明文在加密运算前先与IV进行异或
;从第二组开始,所有的明文先与前一分组加密后的密文进行异或。
## iOS实现
//先定义一个初始向量IV的值。ECB模式不需要
NSString *const kInitVector = @"16-Bytes--String";
//确定密钥长度,这里选择 AES-128。即"密钥是个16位字符串
size_t const kKeySize = kCCKeySizeAES128;
+ (NSString *)encryptAES:(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];
// 密文长度 <= 明文长度 + BlockSize
size_t encryptSize = dataLength + kCCBlockSizeAES128;
void *encryptedBytes = malloc(encryptSize);
size_t actualOutSize = 0;
NSData *initVector = [kInitVector dataUsingEncoding:NSUTF8StringEncoding];
/*
第三个参数:先查看下枚举说明,可以发现里面只有两个枚举变量,并在kCCOptionECBMode的旁边,写着Default is CBC.
kCCOptionPKCS7Padding:表示函数运用CBC加密模式,并且使用PKCS7Padding的填充模式进行加密
kCCOptionPKCS7Padding | kCCOptionECBMode:就表示函数运用ECB加密模式,并且使用PKCS7Padding的填充模式进行加密
如果要设置NoPadding,可以填入0x0000
*/
CCCryptorStatus cryptStatus = CCCrypt(kCCEncrypt, //加密/解密
kCCAlgorithmAES, //选用的加密算法
kCCOptionPKCS7Padding, //设置工作模式+填充
keyPtr, //key
kKeySize, // key length
initVector.bytes, // 初始向量IV的长度,如果不需要IV,设置为nil(不可以为@"")
contentData.bytes,
dataLength,
encryptedBytes,
encryptSize,
&actualOutSize);
if (cryptStatus == kCCSuccess) {
// 对加密后的数据进行 base64 编码
return [[NSData dataWithBytesNoCopy:encryptedBytes length:actualOutSize] base64EncodedStringWithOptions:NSDataBase64EncodingEndLineWithLineFeed];
}
free(encryptedBytes);
return nil;
}
## Java实现
//同理先在类中定义一个初始向量,需要与iOS端的统一。
private static final String IV_STRING = "16-Bytes--String";
//另 Java 不需手动设置密钥大小,系统会自动根据传入的 Key 进行判断。
public static String encryptAES(String content, String key)
throws InvalidKeyException, NoSuchAlgorithmException,
NoSuchPaddingException, UnsupportedEncodingException,
InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException {
byte[] byteContent = content.getBytes("UTF-8");
// 注意,为了能与 iOS 统一
// 这里的 key 不可以使用 KeyGenerator、SecureRandom、SecretKey 生成
byte[] enCodeFormat = key.getBytes();
SecretKeySpec secretKeySpec = new SecretKeySpec(enCodeFormat, "AES");
byte[] initParam = IV_STRING.getBytes();
IvParameterSpec ivParameterSpec = new IvParameterSpec(initParam);
// 指定加密的算法、工作模式和填充方式
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec, ivParameterSpec);
byte[] encryptedBytes = cipher.doFinal(byteContent);
// 同样对加密后数据进行 base64 编码
Encoder encoder = Base64.getEncoder();
return encoder.encodeToString(encryptedBytes);
}
关于Java使用大于128 bits的key
到Oracle官网下载对应Java版本的 JCE ,解压后放到 JAVA_HOME/jre/lib/security/ ,然后修改 iOS 端的 kKeySize 和两端对应的 key 即可。