AES是開(kāi)發(fā)中常用的加密算法之一箱玷。然而由于前后端開(kāi)發(fā)環(huán)境差異吮龄,導(dǎo)致出現(xiàn)前端加密而后端不能解密的情況出現(xiàn)拷窜。然而無(wú)論什么環(huán)境开皿,AES的算法總是相同的, 因此導(dǎo)致結(jié)果不一致的原因在于加密配置的參數(shù)不一致 装黑。于是先來(lái)看看在兩個(gè)平臺(tái)使用AES加密時(shí)需要統(tǒng)一的幾個(gè)參數(shù)副瀑。
密鑰長(zhǎng)度(Key Size)
加密模式(Cipher Mode)
填充方式(Padding)
初始向量(Initialization Vector)
1.密鑰長(zhǎng)度
AES算法下,key的長(zhǎng)度有三種:128恋谭、192和256 bits糠睡。由于歷史原因,JDK默認(rèn)只支持不大于128 bits的密鑰疚颊,而128 bits的key已能夠滿足商用安全需求狈孔。因此本例先使用AES-128。(Java使用大于128 bits的key方法在文末提及)
2.加密模式
AES屬于分組加密(Block Cipher)材义,塊加密中有CBC均抽、ECB、CTR其掂、OFB油挥、CFB等幾種工作模式。本例統(tǒng)一使用CBC模式款熬。
3.填充方式
由于分組加密只能對(duì)特定長(zhǎng)度的數(shù)據(jù)塊進(jìn)行加密深寥,因此CBC、ECB模式需要在最后一數(shù)據(jù)塊加密前進(jìn)行數(shù)據(jù)填充贤牛。(CFB惋鹅,OFB和CTR模式由于與key進(jìn)行加密操作的是上一塊加密后的密文,因此不需要對(duì)最后一段明文進(jìn)行填充)
在iOS SDK中提供了PKCS7Padding殉簸,而JDK則提供了PKCS5Padding闰集。原則上PKCS5Padding限制了填充的Block Size為8 bytes,而Java實(shí)際上當(dāng)塊大于該值時(shí)般卑,其PKCS5Padding與PKCS7Padding是相等的:每需要填充χ個(gè)字節(jié)武鲁,填充的值就是χ。
4.初始向量
使用除ECB以外的其他加密模式均需要傳入一個(gè)初始向量蝠检,其大小與Block Size相等(AES的Block Size為128 bits)洞坑,而兩個(gè)平臺(tái)的API文檔均指明當(dāng)不傳入初始向量時(shí),系統(tǒng)將默認(rèn)使用一個(gè)全0的初始向量蝇率。
有了上述的基礎(chǔ)之后迟杂,可以開(kāi)始分別在兩個(gè)平臺(tái)進(jìn)行實(shí)現(xiàn)了刽沾。
iOS實(shí)現(xiàn)
先定義一個(gè)初始向量的值。
NSString *const kInitVector = @"16-Bytes--String";// 16個(gè)字節(jié)的初始化向量
確定密鑰長(zhǎng)度排拷,這里選擇 AES-128侧漓。
size_t const kKeySize = kCCKeySizeAES128;
加密方法
+ (NSString *)encryptAES:(NSString *)content key:(NSString *)key {
NSData *contentData = [content dataUsingEncoding:NSUTF8StringEncoding];
NSUInteger dataLength = contentData.length;
// 為結(jié)束符'\\0' +1
char keyPtr[kKeySize + 1];
memset(keyPtr, 0, sizeof(keyPtr));
[key getCString:keyPtr maxLength:sizeof(keyPtr) encoding:NSUTF8StringEncoding];
// 密文長(zhǎng)度 <= 明文長(zhǎng)度 + 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, // 系統(tǒng)默認(rèn)使用 CBC,然后指明使用 PKCS7Padding
keyPtr,
kKeySize,
initVector.bytes,
contentData.bytes,
dataLength,
encryptedBytes,
encryptSize,
&actualOutSize);
if (cryptStatus == kCCSuccess) {
// 對(duì)加密后的數(shù)據(jù)進(jìn)行 base64 編碼
return [[NSData dataWithBytesNoCopy:encryptedBytes length:actualOutSize] base64EncodedStringWithOptions:NSDataBase64EncodingEndLineWithLineFeed];
}
free(encryptedBytes);
return nil;
}
解密方法
+ (NSString *)decryptAES:(NSString *)content key:(NSString *)key {
// 把 base64 String 轉(zhuǎn)換成 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;
}
Java實(shí)現(xiàn)
同理先在類中定義一個(gè)初始向量监氢,需要與iOS端的統(tǒng)一布蔗。
private static final String IV_STRING = "16-Bytes--String";
另 Java 不需手動(dòng)設(shè)置密鑰大小,系統(tǒng)會(huì)自動(dòng)根據(jù)傳入的 Key 進(jìn)行判斷浪腐。
加密方法
public static String encryptAES(String content, String key)
throws InvalidKeyException, NoSuchAlgorithmException,
NoSuchPaddingException, UnsupportedEncodingException,
InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException {
byte[] byteContent = content.getBytes("UTF-8");
// 注意纵揍,為了能與 iOS 統(tǒng)一
// 這里的 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);
// 同樣對(duì)加密后數(shù)據(jù)進(jìn)行 base64 編碼
Encoder encoder = Base64.getEncoder();
return encoder.encodeToString(encryptedBytes);
}
解密方法
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 同步實(shí)現(xiàn)完成特漩。
注意以上實(shí)現(xiàn)的是 AES-128吧雹,因此方法傳入的 key 需為長(zhǎng)度為 16 的字符串。
關(guān)于Java使用大于128 bits的key
到Oracle官網(wǎng)下載對(duì)應(yīng)Java版本的 JCE 涂身,解壓后放到 JAVA_HOME/jre/lib/security/ 雄卷,然后修改 iOS 端的 kKeySize 和兩端對(duì)應(yīng)的 key 即可。