文档章节

关于AES256算法java端加密,ios端解密出现无法解密问题的解决方案

Sun1009
 Sun1009
发布于 2012/12/13 16:14
字数 1991
阅读 28645
收藏 32

      我想关于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编码

@end
EncryptAndDecrypt.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

      终于写完了。。。第一次写博文,虽然有点费时,但感觉很好!如有转载,请注明出处,谢谢大家!

© 著作权归作者所有

共有 人打赏支持
Sun1009
粉丝 8
博文 4
码字总数 4484
作品 0
浦东
程序员
加载中

评论(32)

xuxubaobao
xuxubaobao
谢谢你!加密成功了!
a
anybyb
楼主!按照这个方式不得行啊!java端自己可以加密解密,ios端也是。但是ios和java无法相互加密解密
我们用的128位的!ios加密的可以再AES的在线加解密上面解开,但是我java的就解不出来。怎么做啊!
张志合
解密的时候传递的参数是 data为byte[] 类型

但我们接口收到的一般是加密后的字符窜,如果是字符窜,昨解密,有没例子??
张志合
我们是 api走接口,后端取后的值是 加密后的字符窜
但您这里没有提交字符窜 转 byte[]
因 为你解密的方法里的参数是byte[]
如果接口收到的 String加密后的窜,昨解决???
淡如水6268
解密怎么解啊。不会。。。。新手 求指教
longdidi
longdidi
java和ios加密后的密文只有前30位相同。后边的不同,求解啊楼主
zhuguoqiang
zhuguoqiang
楼主,根据你的例子,java,ios都可以分别加密解密,但是,我现在将java加密后的二进制数据转为base64成功传递到了ios端,但是在ios端将base64数据转换为nsdata的时候,数据出现丢失,转换出的nsdata只有32byte,能给点意见吗??
Sun1009
Sun1009

引用来自“lyx_luck”的评论

引用来自“Nic_Sun”的评论

引用来自“chuanning”的评论

楼之,我用的是iOS 128 的加密,怎么修改iOS的让iOS和android一致,而不是修改android 的,+ (NSData *)AES128EncryptWithKey:(NSString *)key withData:(NSData*)_data
{
char keyPtr[kCCKeySizeAES128 + 1]; // room for terminator (unused)
bzero( keyPtr, sizeof( keyPtr ) ); // fill with zeroes (for padding)
[key getCString:keyPtr maxLength:sizeof( keyPtr ) encoding:NSUTF8StringEncoding];
NSUInteger dataLength = [_data length];
size_t bufferSize = dataLength + kCCBlockSizeAES128;
void *buffer = malloc( bufferSize );

size_t numBytesEncrypted = 0;
CCCryptorStatus cryptStatus = CCCrypt( kCCEncrypt, kCCAlgorithmAES128, kCCOptionECBMode | kCCOptionPKCS7Padding,keyPtr, kCCKeySizeAES128,NULL [_data bytes], dataLength, buffer, bufferSize, &numBytesEncrypted );

尝试着根据文中的方法让ios端跟java端的填充模式保持一致,希望能帮到你。

感谢楼主的贡献,我的加密算法已经实现,现在没有采用AES,最后采用了DES算法,和AES差不多,总之还是很感谢你,世界非常需要像楼主这样的人!

言重了。。互相学习
lyx_luck
lyx_luck

引用来自“Nic_Sun”的评论

引用来自“chuanning”的评论

楼之,我用的是iOS 128 的加密,怎么修改iOS的让iOS和android一致,而不是修改android 的,+ (NSData *)AES128EncryptWithKey:(NSString *)key withData:(NSData*)_data
{
char keyPtr[kCCKeySizeAES128 + 1]; // room for terminator (unused)
bzero( keyPtr, sizeof( keyPtr ) ); // fill with zeroes (for padding)
[key getCString:keyPtr maxLength:sizeof( keyPtr ) encoding:NSUTF8StringEncoding];
NSUInteger dataLength = [_data length];
size_t bufferSize = dataLength + kCCBlockSizeAES128;
void *buffer = malloc( bufferSize );

size_t numBytesEncrypted = 0;
CCCryptorStatus cryptStatus = CCCrypt( kCCEncrypt, kCCAlgorithmAES128, kCCOptionECBMode | kCCOptionPKCS7Padding,keyPtr, kCCKeySizeAES128,NULL [_data bytes], dataLength, buffer, bufferSize, &numBytesEncrypted );

尝试着根据文中的方法让ios端跟java端的填充模式保持一致,希望能帮到你。

感谢楼主的贡献,我的加密算法已经实现,现在没有采用AES,最后采用了DES算法,和AES差不多,总之还是很感谢你,世界非常需要像楼主这样的人!
Sun1009
Sun1009

引用来自“chuanning”的评论

楼之,我用的是iOS 128 的加密,怎么修改iOS的让iOS和android一致,而不是修改android 的,+ (NSData *)AES128EncryptWithKey:(NSString *)key withData:(NSData*)_data
{
char keyPtr[kCCKeySizeAES128 + 1]; // room for terminator (unused)
bzero( keyPtr, sizeof( keyPtr ) ); // fill with zeroes (for padding)
[key getCString:keyPtr maxLength:sizeof( keyPtr ) encoding:NSUTF8StringEncoding];
NSUInteger dataLength = [_data length];
size_t bufferSize = dataLength + kCCBlockSizeAES128;
void *buffer = malloc( bufferSize );

size_t numBytesEncrypted = 0;
CCCryptorStatus cryptStatus = CCCrypt( kCCEncrypt, kCCAlgorithmAES128, kCCOptionECBMode | kCCOptionPKCS7Padding,keyPtr, kCCKeySizeAES128,NULL [_data bytes], dataLength, buffer, bufferSize, &numBytesEncrypted );

尝试着根据文中的方法让ios端跟java端的填充模式保持一致,希望能帮到你。
Objective-C 和 Java 下 DES加解密保持一致的方式

最近做了一个移动项目,是有服务器和客户端类型的项目,客户端是要登录才行的,登录的密码要用DES加密,服务器是用Java开发的,客户端要同时支持多平台(Android、iOS),在处理iOS的DES加密...

山哥
2012/04/19
0
5
实现ios上传加密nodejs后台解密

今天在做项目的时候遇到一个问题,我需要在ios端把上传数据加密,防止中间代理捕获信息内容并修改数据库的信息。把数据传到后台在解码,实现数据安全。 下面介绍我实现的在nodejs的加密和解密...

90后爱国
2014/08/21
0
0
iOS中DES与MD5加密方案

MD5算法和DES算法是常见的两种加密算法。 MD5:MD5是一种不可逆的加密算法,按我的理解,所谓不可逆,就是不能解密,那么它有什么用的,它的用处大了,大多数的登录功能都会使用到这种算法。...

珲少
2015/04/03
0
0
JavaScript(React Native、Node.js等)移动、服务端通吃的全栈语言

作者:李宁老师 东北大学计算机专业硕士。曾任沈阳东软股份项目经理。51CTO学院签约讲师。从事软件研究和开发超过20年。长久以来一直从事Java、Android、iOS、C++、Swift、Objective-C以及跨...

androidguy
06/29
0
0
Android、iPhone和Java三个平台一致的加密工具

移动开发中遇到的最让人纠结的要属Java、Android和iPhone三个平台加解密不一致的问题。因为手机端后台通常是用JAVA开发的Web Service,Android和iPhone客户端调用同样的Web Service接口,为了...

科技创造
2014/12/19
0
0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

nginx模块学习六 add_header 跨域访问

语法 Syntax: add_header name value [always];Default: --Context:http,server,location,if in location 例:/etc/nginx/conf.d/default.conf server {    listen       80; ......

Romanceling
今天
0
0
SpringBoot初探

#SpringBoot初探 三种创建SpringBoot项目的方式: 第一种:使用IDEA创建maven项目,选择maven-archetype-quickstart; 第二种:使用IDEA创建Spring Initializer,选择web组件; 第三种:使用...

向码而生
今天
2
0
IO

JAVA中IO技术:BIO、NIO、AIO 1、同步异步、阻塞非阻塞概念 同步和异步是针对应用程序和内核的交互而言的。 阻塞和非阻塞是针对于进程在访问数据的时候,根据IO操作的就绪状态来采取的不同方...

DemonsI
今天
0
0
org.apache.commons 常用工具类

一. org.apache.commons.io.IOUtils closeQuietly 关闭一个IO流、socket、或者selector且不抛出异常。通常放在finally块。 toString 转换IO流、 Uri、 byte[]为String。 copy IO流数据复制,...

sprouting
今天
0
0
linux使用Inotify监控目录或者文件状态变更

基本概念: Inotify 是一个 Linux特性,它监控文件系统操作,比如读取、写入和创建。Inotify 反应灵敏,用法非常简单,并且比 cron 任务的繁忙轮询高效得多。 需求: 1.有一个文件采集进程,...

mickelfeng
今天
0
1

没有更多内容

加载失败,请刷新页面

加载更多

下一页

返回顶部
顶部