最近项目中用到数据加密解密的功能,由于Android、iOS以及服务器端都需要用到这个功能。而不同平台上加密出来的密文是不一样的,这样导致互相之间密文无法使用。于是决定使用C/C++完成加密解密,其他平台调用的方式进行处理。
加密解密实现 AES加密的具体实现过程本文暂不讨论,实现代码是直接从openssl源码中抽出来。
加密解密调用以下两个方法:
int aes_encrypt(char * in, char* key, char * out) 加密//明文,密钥,密文
int aes_decrypt(char * in, char* key, char * out) 解密//密文,密钥,明文 明文需要从外部获取;一般情况下key需要自定义,所以也是外部获取;out是加密或者解密的结果,需要返回给调用者。
所以接下来需要做的就是获取外部数据,交给加密解密方法处理,返回结果到外部。
编写java本地方法 java调用c/c++代码是通过JNI来实现,在java中需要声明native方法。
AESUtil.java public class AESUtil{ public native String encrypt(String plainText, String key); public native String decrypt(String cipherText, String key); } javac编译AESUtil.java生成class文件AESUtil.class。
javah AESUtil生成AESUtil.h,该文件将会作为头文件包含到c项目中去。 打开AESUtil.h,代码如下:
复制代码 /* DO NOT EDIT THIS FILE - it is machine generated / #include <jni.h> / Header for class AESUtil */
#ifndef _Included_AESUtil #define _Included_AESUtil #ifdef __cplusplus extern "C" { #endif /*
- Class: AESUtil
- Method: encrypt
- Signature: (Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String; */ JNIEXPORT jstring JNICALL Java_AESUtil_encrypt (JNIEnv *, jobject, jstring, jstring);
/*
- Class: AESUtil
- Method: decrypt
- Signature: (Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String; */ JNIEXPORT jstring JNICALL Java_AESUtil_decrypt (JNIEnv *, jobject, jstring, jstring);
#ifdef __cplusplus } #endif #endif 复制代码 头文件中声明了两个方法Java_AESUtil_encrypt和Java_AESUtil_decrypt,分别对应java中的两个native方法encrypt和decrypt。而两个native方法中传的两个String类型的参数,在头文件中被转化为jstring类型。通过该类型我们可以实现java和c之间字符串的转换。
在c++中实现java中调用的方法 引入头文件后就需要实现这两个方法。新建AESUtil.cpp,引入AESUtil.h,实现声明的加密解密方法:
复制代码 #define LEN 512 JNIEXPORT jstring JNICALL Java_AESUtil_encrypt (JNIEnv env, jobject obj, jstring s, jstring k){ //将需要加密的字符串转化为const char类型 const char* str = env->GetStringUTFChars(s, 0); //密钥字符串转化成char* char* key = (char *)env->GetStringUTFChars(k,0);
int i; char source[LEN]; char dst[LEN]; memset((char*)source, 0 ,LEN); memset((char*)dst, 0 ,LEN); strcpy(source, str); if(!aes_encrypt(source,key,dst))//(in,key,out)//加密 { printf("encrypt error\n"); } char t[3]; string tempStr; int realLen=LEN; for(i=LEN-1;!dst[i];i--){// 加密结果中可能包含‘\0’,而‘\0’是C++中字符串的结尾标志,所以为了保证‘\0’之后的密文可以被取出,从数组尾部开始往前,第一个不是‘\0’的元素就是我们要取的最后一个值 realLen = i; } for(i= 0;i<=realLen-1;i+=1){//将加密结果转化为十六进制,拼接成字符串输出 sprintf(t, "%x", (unsigned char)dst[i]); if((unsigned char)dst[i]<=0x0f){ tempStr = tempStr+"0"+t; }else{ tempStr = tempStr+t; } } char *data=(char *)tempStr.data(); return env->NewStringUTF(data);//通过JNI提供的转化方法将char*转化为jstring作为结果返回
}
JNIEXPORT jstring JNICALL Java_AESUtil_decrypt (JNIEnv env, jobject obj, jstring s, jstring k){ const char str = env->GetStringUTFChars(s, 0); char* key = (char )env->GetStringUTFChars(k,0); int i; char source[LEN]; char dst[LEN]; memset((char)source, 0 ,LEN); memset((char*)dst, 0 ,LEN); strcpy(dst,str); char data[LEN]; int j = 0; memset((char*)data, 0 ,LEN); int len=strlen(dst); for(i=0;dst[i];i++){
if((i+1)%2==0){//加密结果中字符串两两分隔组成十六进制转化为具体值存入数组以供解密 data[j] = ascii2hex(&((char)dst[i-1]),1)*16+ascii2hex(&((char)dst[i]),1); j++; } } if(!aes_decrypt(data,key,source)) { printf("decrypt error\n"); } return env->NewStringUTF(source);}
int aes_encrypt(char* in, char* key, char* out) { AES_KEY aes; int len = strlen(in), en_len = 0; if (!in || !key || !out) return 0;
if (AES_set_encrypt_key((unsigned char*)key, 128, &aes) < 0) { return 0; }
while (en_len < len) { AES_encrypt((unsigned char*)in, (unsigned char*)out, &aes); in += AES_BLOCK_SIZE; out += AES_BLOCK_SIZE; en_len += AES_BLOCK_SIZE; } return 1; }
int aes_decrypt(char* in, char* key, char* out) { AES_KEY aes; int len = MSG_LEN, en_len = 0; for (size_t i = MSG_LEN - 1; !in[i]; i--)//修改计算in长度方式,以防出现加密内容包含'\0'的情况 { len = i; } if (!in || !key || !out) return 0;
if (AES_set_decrypt_key((unsigned char*)key, 128, &aes) < 0) { return 0; }
while (en_len < len) { AES_decrypt((unsigned char*)in, (unsigned char*)out, &aes); in += AES_BLOCK_SIZE; out += AES_BLOCK_SIZE; en_len += AES_BLOCK_SIZE; } return 1; }
复制代码 可以看到生成的头文件中引入了jni.h,我们在进行java和c++之间字符串转换时使用的方法就是来自于该文件。jni.h可以从jdk安装目录\include下找到,同时引入jni_mod.h,可以从jdk安装目录\include\win32下找到。
java调用c++需要通过调用dll来完成,所以我们需要将c++生成dll,我这里是直接在vs2010中新建dll项目生成的,具体可以google。 生成dll后将其加入classpath,我是直接放入了jdk根目录\bin下面。也可以将dll所在目录加入环境变量。
调用java本地方法 dll生成了,java方法有了,下面就是调用方法进行测试啦~~捧腹大笑
复制代码 public class StringUtils {
public static void main(String[] args) { System.loadLibrary("AESCPP");//加载dll,不需要包含.dll后缀名 AESUtil s = new AESUtil();//AESUtil中包含了native方法 String plain = "testdata[@#](https://my.oschina.net/u/2839266)&*99HUIWB1=-";//明文 System.out.println(plain); String key = "1234567890123456afhiu$^&682036490";//密钥 String str = s.encrypt(plain,key);//加密 System.out.println("\nhahaha:\n "+str);//打印结果 System.out.println("length: "+str.length()); System.out.println("@@@@@@@@@@@"); String ss = s.decrypt(str,key);//解密 System.out.println("----->-------->"); System.out.println(ss);//解密结果}
} 复制代码 跑一下~ QQ截图20150128170813 可以发现加密解密结果一致。
在c++代码中我们将得到的结果存储在dst[]数组中,我们所得到的密文正是数组中的值转化为十六进制输出后拼成的字符串。解密的时候我们需要将这些字符串两两拆开(62,f8,a8…)重新存入数组中进行解密。
windows下大概就是这样。
linux下由于无法调用dll文件,需要生成.so文件。另外引入jni.h和jni_mod.h的时候需要的是linux下jdk目录中的这两个文件。具体操作可以google,和windows下类似。
Android中调用 之前在linux下生成.so是因为android中无法使用dll,但是linux下生成成功后放入android发现还是不可以使用,对android和linux也不是太了解,经过查阅发现linux是x86_64平台,而测试的Android手机是arm平台。。
这样有发现了个新玩意:NDK~~ 具体NDK环境搭建就不赘述,我是按这个链接来搭建的http://my.oschina.net/lifj/blog/176916 环境搭建完毕,首先在eclipse下新建一个Android项目AESTest。 右击项目名选择Android Tools->Add Native Support. 打开jni目录下的Android.mk文件,可以看到如下代码:(没有可以自己加入)
复制代码 LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := AESUtil LOCAL_SRC_FILES := AESUtil.cpp
include $(BUILD_SHARED_LIBRARY) 复制代码 其中LOCAL_MODULE所对应的值就是加载库时所要用的字符串(System.loadLibrary("XXX");)
LOCAL_SRC_FILES所对应的值则代表了实现方法所在的文件。 jni目录下新建文件Application.mk,加入
APP_STL:=stlport_static 不加这个文件之前涉及到c++的方法都会报错,具体原因尚未探究,只是搜报错信息从stackoverflow上找到了这个方法。
在jni目录中加入我们之前生成dll时用到的头文件和实现文件. 打开Cygwin,进入项目根目录,输入$NDK/ndk-build就可以生成so文件了
QQ截图20150128180408
可以看到so文件生成在libs/armeabi文件夹下,在android代码中调用该文件编译运行就可以了。
另外,还有一篇C# AES加解密文章: AES简介 AES(The Advanced Encryption Standard)是美国国家标准与技术研究所用于加密电子数据的规范。它被预期能成为人们公认的加密包括金融、电信和政府数字信息的方法。
AES 是一个新的可以用于保护电子数据的加密算法。明确地说,AES 是一个迭代的、对称密钥分组的密码,它可以使用128、192 和 256 位密钥,并且用 128 位(16字节)分组加密和解密数据。与公共密钥密码使用密钥对不同,对称密钥密码使用相同的密钥加密和解密数据。通过分组密码返回的加密数据 的位数与输入数据相同。迭代加密使用一个循环结构,在该循环中重复置换(permutations )和替换(substitutions)输入数据。Figure 1 显示了 AES 用192位密钥对一个16位字节数据块进行加密和解密的情形。
AES加密函数 复制代码 public static string Encrypt(string toEncrypt) { byte[] keyArray = UTF8Encoding.UTF8.GetBytes("12345678901234567890123456789012"); byte[] toEncryptArray = UTF8Encoding.UTF8.GetBytes(toEncrypt);
RijndaelManaged rDel = new RijndaelManaged();rDel.Key = keyArray;rDel.Mode = CipherMode.ECB;rDel.Padding = PaddingMode.PKCS7;ICryptoTransform cTransform = rDel.CreateEncryptor();byte[] resultArray = cTransform.TransformFinalBlock(toEncryptArray, 0, toEncryptArray.Length);return Convert.ToBase64String(resultArray, 0, resultArray.Length);
}
复制代码 AES解密函数 复制代码 public static string Decrypt(string toDecrypt) { byte[] keyArray = UTF8Encoding.UTF8.GetBytes("12345678901234567890123456789012"); byte[] toEncryptArray = Convert.FromBase64String(toDecrypt);
RijndaelManaged rDel = new RijndaelManaged();rDel.Key = keyArray;rDel.Mode = CipherMode.ECB;rDel.Padding = PaddingMode.PKCS7;ICryptoTransform cTransform = rDel.CreateDecryptor();byte[] resultArray = cTransform.TransformFinalBlock(toEncryptArray, 0, toEncryptArray.Length);return UTF8Encoding.UTF8.GetString(resultArray);
}
复制代码 注:以上代码实现的是256位的AES算法
原文地址: