AES是开发中常用的加密算法之一。然而由于前后端开发使用的语言不统一,导致经常出现前端加密而后端不能解密的情况出现。然而无论什么语言系统,AES的算法总是相同的, 因此导致结果不一致的原因在于 加密设置的参数不一致 。于是先来看看在两个平台使用AES加密时需要统一的几个参数。
AES算法下,key的长度有三种:128、192和256 bits。由于历史原因,JDK默认只支持不大于128 bits的密钥,而128 bits的key已能够满足商用安全需求。因此本例先使用AES-128。(Java使用大于128 bits的key方法在文末提及)
AES属于块加密(Block Cipher),块加密中有CBC、ECB、CTR、OFB、CFB等几种工作模式。本例统一使用CBC模式。
由于块加密只能对特定长度的数据块进行加密,因此CBC、ECB模式需要在最后一数据块加密前进行数据填充。(CFB,OFB和CTR模式由于与key进行加密操作的是上一块加密后的密文,因此不需要对最后一段明文进行填充)
在iOS SDK中提供了PKCS7Padding,而JDK则提供了PKCS5Padding。原则上PKCS5Padding限制了填充的Block Size为8 bytes,而Java实际上当块大于该值时,其PKCS5Padding与PKCS7Padding是相等的:每需要填充χ个字节,填充的值就是χ。
使用除ECB以外的其他加密模式均需要传入一个初始向量,其大小与Block Size相等(AES的Block Size为128 bits),而两个平台的API文档均指明当不传入初始向量时,系统将默认使用一个全0的初始向量。
有了上述的基础之后,可以开始分别在两个平台进行实现了。
先定义一个初始向量的值。
1
|
NSString
*const
kInitVector
=
@"16-Bytes--String"
;
|
确定密钥长度,这里选择 AES-128。
1
|
size_t
const
kKeySize
=
kCCKeySizeAES128
;
|
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
|
+
(
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
]
;
CCCryptorStatus
cryptStatus
=
CCCrypt
(
kCCEncrypt
,
kCCAlgorithmAES
,
kCCOptionPKCS7Padding
,
// 系统默认使用 CBC,然后指明使用 PKCS7Padding
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
;
}
|
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
|
+
(
NSString
*
)
decryptAES
:
(
NSString
*
)
content
key
:
(
NSString
*
)
key
{
// 把 base64 String 转换成 Data
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
,
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
;
}
|
同理先在类中定义一个初始向量,需要与iOS端的统一。
1
|
private
static
final
String
IV_STRING
=
"16-Bytes--String"
;
|
另 Java 不需手动设置密钥大小,系统会自动根据传入的 Key 进行判断。
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
|
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
)
;
}
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
public
static
String
decryptAES
(
String
content
,
String
key
)
throws
InvalidKeyException
,
NoSuchAlgorithmException
,
NoSuchPaddingException
,
InvalidAlgorithmParameterException
,
IllegalBlockSizeException
,
BadPaddingException
,
UnsupportedEncodingException
{
// base64 解码
Decoder
decoder
=
Base64
.
getDecoder
(
)
;
byte
[
]
encryptedBytes
=
decoder
.
decode
(
content
)
;
byte
[
]
enCodeFormat
=
key
.
getBytes
(
)
;
SecretKeySpec
secretKey
=
new
SecretKeySpec
(
enCodeFormat
,
"AES"
)
;
byte
[
]
initParam
=
IV_STRING
.
getBytes
(
)
;
IvParameterSpec
ivParameterSpec
=
new
IvParameterSpec
(
initParam
)
;
Cipher
cipher
=
Cipher
.
getInstance
(
"AES/CBC/PKCS5Padding"
)
;
cipher
.
init
(
Cipher
.
DECRYPT_MODE
,
secretKey
,
ivParameterSpec
)
;
byte
[
]
result
=
cipher
.
doFinal
(
encryptedBytes
)
;
return
new
String
(
result
,
"UTF-8"
)
;
}
|
至此,AES 在 iOS 与 Java 同步实现完成。
注意以上实现的是 AES-128,因此方法传入的 key 需为长度为 16 的字符串。
到Oracle官网下载对应Java版本的 JCE ,解压后放到 JAVA_HOME/jre/lib/security/ ,然后修改 iOS 端的 kKeySize 和两端对应的 key 即可。
以上。
AESCipher-iOS
AESCipher-Java
可直接使用,欢迎各种star和fork~