我想关于AES算法大家应该都已经了解了,我就不多介绍了。这是本人第一次写技术博文,如果有不对之处欢迎大家指正,共同讨论,一起学习!
之前在项目上用到AES256加密解密算法,刚开始在java端加密解密都没有问题,在iOS端加密解密也没有问题。但是奇怪的是在java端加密后的文件在iOS端无法正确解密打开,然后简单测试了一下,发现在java端和iOS端采用相同明文,相同密钥加密后的密文不一样!上网查了资料后发现iOS中AES加密算法采用的填充是PKCS7Padding,而java不支持PKCS7Padding,只支持PKCS5Padding。我们知道加密算法由算法+模式+填充组成,所以这两者不同的填充算法导致相同明文相同密钥加密后出现密文不一致的情况。那么我们需要在java中用PKCS7Padding来填充,这样就可以和iOS端填充算法一致了。
要实现在java端用PKCS7Padding填充,需要用到bouncycastle组件来实现,下面我会提供该包的下载。啰嗦了一大堆,下面是一个简单的测试,上代码!
package com.encrypt.file; import java.io.UnsupportedEncodingException; import java.security.Key; import java.security.Security; import javax.crypto.Cipher; import javax.crypto.SecretKey; import javax.crypto.spec.SecretKeySpec; public class AES256Encryption{ /** * 密钥算法 * java6支持56位密钥,bouncycastle支持64位 * */ public static final String KEY_ALGORITHM="AES"; /** * 加密/解密算法/工作模式/填充方式 * * JAVA6 支持PKCS5PADDING填充方式 * Bouncy castle支持PKCS7Padding填充方式 * */ public static final String CIPHER_ALGORITHM="AES/ECB/PKCS7Padding"; /** * * 生成密钥,java6只支持56位密钥,bouncycastle支持64位密钥 * @return byte[] 二进制密钥 * */ public static byte[] initkey() throws Exception{ // //实例化密钥生成器 // Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider()); // KeyGenerator kg=KeyGenerator.getInstance(KEY_ALGORITHM, "BC"); // //初始化密钥生成器,AES要求密钥长度为128位、192位、256位 //// kg.init(256); // kg.init(128); // //生成密钥 // SecretKey secretKey=kg.generateKey(); // //获取二进制密钥编码形式 // return secretKey.getEncoded(); //为了便于测试,这里我把key写死了,如果大家需要自动生成,可用上面注释掉的代码 return new byte[] { 0x08, 0x08, 0x04, 0x0b, 0x02, 0x0f, 0x0b, 0x0c, 0x01, 0x03, 0x09, 0x07, 0x0c, 0x03, 0x07, 0x0a, 0x04, 0x0f, 0x06, 0x0f, 0x0e, 0x09, 0x05, 0x01, 0x0a, 0x0a, 0x01, 0x09, 0x06, 0x07, 0x09, 0x0d }; } /** * 转换密钥 * @param key 二进制密钥 * @return Key 密钥 * */ public static Key toKey(byte[] key) throws Exception{ //实例化DES密钥 //生成密钥 SecretKey secretKey=new SecretKeySpec(key,KEY_ALGORITHM); return secretKey; } /** * 加密数据 * @param data 待加密数据 * @param key 密钥 * @return byte[] 加密后的数据 * */ public static byte[] encrypt(byte[] data,byte[] key) throws Exception{ //还原密钥 Key k=toKey(key); /** * 实例化 * 使用 PKCS7PADDING 填充方式,按如下方式实现,就是调用bouncycastle组件实现 * Cipher.getInstance(CIPHER_ALGORITHM,"BC") */ Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider()); Cipher cipher=Cipher.getInstance(CIPHER_ALGORITHM, "BC"); //初始化,设置为加密模式 cipher.init(Cipher.ENCRYPT_MODE, k); //执行操作 return cipher.doFinal(data); } /** * 解密数据 * @param data 待解密数据 * @param key 密钥 * @return byte[] 解密后的数据 * */ public static byte[] decrypt(byte[] data,byte[] key) throws Exception{ //欢迎密钥 Key k =toKey(key); /** * 实例化 * 使用 PKCS7PADDING 填充方式,按如下方式实现,就是调用bouncycastle组件实现 * Cipher.getInstance(CIPHER_ALGORITHM,"BC") */ Cipher cipher=Cipher.getInstance(CIPHER_ALGORITHM); //初始化,设置为解密模式 cipher.init(Cipher.DECRYPT_MODE, k); //执行操作 return cipher.doFinal(data); } /** * @param args * @throws UnsupportedEncodingException * @throws Exception */ public static void main(String[] args) throws UnsupportedEncodingException{ String str="AES"; System.out.println("原文:"+str); //初始化密钥 byte[] key; try { key = AES256Encryption.initkey(); System.out.print("密钥:"); for(int i = 0;i<key.length;i++){ System.out.printf("%x", key[i]); } System.out.print("\n"); //加密数据 byte[] data=AES256Encryption.encrypt(str.getBytes(), key); System.out.print("加密后:"); for(int i = 0;i<data.length;i++){ System.out.printf("%x", data[i]); } System.out.print("\n"); //解密数据 data=AES256Encryption.decrypt(data, key); System.out.println("解密后:"+new String(data)); } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } } }
运行程序后的结果截图:
上图可以看到密钥和密文,好了,我们来看看iOS端实现AES256加密解密的方法,有点复杂,大家耐心点看就好。
EncryptAndDecrypt.h文件
// // EncryptAndDecrypt.h // AES256EncryptionDemo // // Created by rich sun on 12-12-13. // Copyright (c) 2012年 rich sun. All rights reserved. // #import <Foundation/Foundation.h> @class NSString; @interface NSData (Encryption) - (NSData *)AES256EncryptWithKey:(NSData *)key; //加密 - (NSData *)AES256DecryptWithKey:(NSData *)key; //解密 - (NSString *)newStringInBase64FromData; //追加64编码 + (NSString*)base64encode:(NSString*)str; //同上64编码 @endEncryptAndDecrypt.m文件
// // EncryptAndDecrypt.m // AES256EncryptionDemo // // Created by rich sun on 12-12-13. // Copyright (c) 2012年 rich sun. All rights reserved. // #import "EncryptAndDecrypt.h" #import <CommonCrypto/CommonCrypto.h> static char base64[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; @implementation NSData (Encryption) - (NSData *)AES256EncryptWithKey:(NSData *)key //加密 { //AES256加密,密钥应该是32位的 const void * keyPtr2 = [key bytes]; char (*keyPtr)[32] = keyPtr2; //对于块加密算法,输出大小总是等于或小于输入大小加上一个块的大小 //所以在下边需要再加上一个块的大小 NSUInteger dataLength = [self length]; size_t bufferSize = dataLength + kCCBlockSizeAES128; void *buffer = malloc(bufferSize); size_t numBytesEncrypted = 0; CCCryptorStatus cryptStatus = CCCrypt(kCCEncrypt, kCCAlgorithmAES128, kCCOptionPKCS7Padding/*这里就是刚才说到的PKCS7Padding填充了*/ | kCCOptionECBMode, [key bytes], kCCKeySizeAES256, NULL,/* 初始化向量(可选) */ [self bytes], dataLength,/*输入*/ buffer, bufferSize,/* 输出 */ &numBytesEncrypted); if (cryptStatus == kCCSuccess) { return [NSData dataWithBytesNoCopy:buffer length:numBytesEncrypted]; } free(buffer);//释放buffer return nil; } - (NSData *)AES256DecryptWithKey:(NSData *)key //解密 { //同理,解密中,密钥也是32位的 const void * keyPtr2 = [key bytes]; char (*keyPtr)[32] = keyPtr2; //对于块加密算法,输出大小总是等于或小于输入大小加上一个块的大小 //所以在下边需要再加上一个块的大小 NSUInteger dataLength = [self length]; size_t bufferSize = dataLength + kCCBlockSizeAES128; void *buffer = malloc(bufferSize); size_t numBytesDecrypted = 0; CCCryptorStatus cryptStatus = CCCrypt(kCCDecrypt, kCCAlgorithmAES128, kCCOptionPKCS7Padding/*这里就是刚才说到的PKCS7Padding填充了*/ | kCCOptionECBMode, keyPtr, kCCKeySizeAES256, NULL,/* 初始化向量(可选) */ [self bytes], dataLength,/* 输入 */ buffer, bufferSize,/* 输出 */ &numBytesDecrypted); if (cryptStatus == kCCSuccess) { return [NSData dataWithBytesNoCopy:buffer length:numBytesDecrypted]; } free(buffer); return nil; } - (NSString *)newStringInBase64FromData //追加64编码 { NSMutableString *dest = [[NSMutableString alloc] initWithString:@""]; unsigned char * working = (unsigned char *)[self bytes]; int srcLen = [self length]; for (int i=0; i<srcLen; i += 3) { for (int nib=0; nib<4; nib++) { int byt = (nib == 0)?0:nib-1; int ix = (nib+1)*2; if (i+byt >= srcLen) break; unsigned char curr = ((working[i+byt] << (8-ix)) & 0x3F); if (i+nib < srcLen) curr |= ((working[i+nib] >> ix) & 0x3F); [dest appendFormat:@"%c", base64[curr]]; } } return dest; } + (NSString*)base64encode:(NSString*)str { if ([str length] == 0) return @""; const char *source = [str UTF8String]; int strlength = strlen(source); char *characters = malloc(((strlength + 2) / 3) * 4); if (characters == NULL) return nil; NSUInteger length = 0; NSUInteger i = 0; while (i < strlength) { char buffer[3] = {0,0,0}; short bufferLength = 0; while (bufferLength < 3 && i < strlength) buffer[bufferLength++] = source[i++]; characters[length++] = base64[(buffer[0] & 0xFC) >> 2]; characters[length++] = base64[((buffer[0] & 0x03) << 4) | ((buffer[1] & 0xF0) >> 4)]; if (bufferLength > 1) characters[length++] = base64[((buffer[1] & 0x0F) << 2) | ((buffer[2] & 0xC0) >> 6)]; else characters[length++] = '='; if (bufferLength > 2) characters[length++] = base64[buffer[2] & 0x3F]; else characters[length++] = '='; } NSString *g = [[NSString alloc] initWithBytesNoCopy:characters length:length encoding:NSASCIIStringEncoding freeWhenDone:YES]; return g; } @end
ViewController.m文件
// // ViewController.m // AES256EncryptionDemo // // Created by 孙 裔 on 12-12-13. // Copyright (c) 2012年 rich sun. All rights reserved. // #import "ViewController.h" #import "EncryptAndDecrypt.h" @interface ViewController () @end @implementation ViewController @synthesize plainTextField; - (void)viewDidLoad { [super viewDidLoad]; // Do any additional setup after loading the view, typically from a nib. } - (void)didReceiveMemoryWarning { [super didReceiveMemoryWarning]; // Dispose of any resources that can be recreated. } //这个函数实现了用户输入完后点击视图背景,关闭键盘 - (IBAction)backgroundTap:(id)sender{ [plainTextField resignFirstResponder]; } - (IBAction)encrypt:(id)sender { NSString *plainText = plainTextField.text;//明文 NSData *plainTextData = [plainText dataUsingEncoding:NSUTF8StringEncoding]; //为了测试,这里先把密钥写死 Byte keyByte[] = {0x08,0x08,0x04,0x0b,0x02,0x0f,0x0b,0x0c,0x01,0x03,0x09,0x07,0x0c,0x03, 0x07,0x0a,0x04,0x0f,0x06,0x0f,0x0e,0x09,0x05,0x01,0x0a,0x0a,0x01,0x09, 0x06,0x07,0x09,0x0d}; //byte转换为NSData类型,以便下边加密方法的调用 NSData *keyData = [[NSData alloc] initWithBytes:keyByte length:32]; // NSData *cipherTextData = [plainTextData AES256EncryptWithKey:keyData]; Byte *plainTextByte = (Byte *)[cipherTextData bytes]; for(int i=0;i<[cipherTextData length];i++){ printf("%x",plainTextByte[i]); } } @end
运行程序,这里需要自己创建一个简单的应用程序,简单的布局:
加密后的密文:
大家可以看到这里的密文和java端的密文是一致的,这样我们就成功完成了。只要密文和密钥是一致的,那么解密应该就不会有什么问题了后面如果有需要解密的可以自己调用解密的那个方法就可以了。
如有什么不明之处欢迎大家留言,互相学习!很遗憾这里无法上传附件,代码中需要的包只能以链接的方式让各位去下了:
jce_policy-6.zip 下载链接:http://www.oracle.com/technetwork/java/javase/downloads/jce-6-download-429243.html
下载解压后将里边的两个jar包(local_policy.jar,US_export_policy.jar)替换掉jdk安装路径下security文件夹中的两个包。
bcprov-jdk16-139.jar 下载链接:http://www.bouncycastle.org
终于写完了。。。第一次写博文,虽然有点费时,但感觉很好!如有转载,请注明出处,谢谢大家!