最近項目中用到數據加密解密的功能,因爲Android、iOS以及服務器端都須要用到這個功能。而不一樣平臺上加密出來的密文是不同的,這樣致使互相之間密文沒法使用。因而決定使用C/C++完成加密解密,其餘平臺調用的方式進行處理。java
加密解密實現 AES加密的具體實現過程本文暫不討論,實現代碼是直接從openssl源碼中抽出來。linux
加密解密調用如下兩個方法:
int aes_encrypt(char * in, char* key, char * out) 加密//明文,密鑰,密文android
int aes_decrypt(char * in, char* key, char * out) 解密//密文,密鑰,明文 明文須要從外部獲取;通常狀況下key須要自定義,因此也是外部獲取;out是加密或者解密的結果,須要返回給調用者。c++
因此接下來須要作的就是獲取外部數據,交給加密解密方法處理,返回結果到外部。
編寫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。windows
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 /*服務器
/*eclipse
#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 是一個迭代的、對稱密鑰分組的密碼,它可使用12八、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算法
原文地址:http://www.overset.com/2008/04/17/simple-aes-rijndael-c-encrypt-decrypt-functions/