背景
- 将AES加密算法的秘钥放java源码层容易被反编译获取
- 在C++层封装AES加/解密过程供java层调用
- 目前支持AES加密模式CBC,填充方式:zeropadding,数据块128位,输出base64/hex
流程
新建Native C++工程
目的是为了获取as自动生成的CMakeLists.txt,不用我们手动生成
新建一个Library模块mylibrary
- 将app模块的main目录下的cpp文件夹拷贝到mylibrary模块的main目录下
- 修改mylibrary模块的build.gradle
android {
...
defaultConfig {
...
externalNativeBuild {
cmake {
// 设置标志来支持C++编译器的格式化宏常量
cppFlags ""
}
}
}
externalNativeBuild {
cmake {
//Gradle与原生库关联
path "src/main/cpp/CMakeLists.txt"
version "3.10.2"
}
}
}
- 在cpp文件夹下新建aes-library文件夹,用于存放AES加解密算法c++源码
- 将加解密算法c++源码AES.cpp,AES.h文件放进第3步新建的文件夹
- 修改CMakeLists.txt文件
# 该命令会查找指定目录下的所有源文件,然后将结果存进指定变量名
aux_source_directory(aes-library AES_LIBRARY_SRC)
# 添加头文件搜索目录
include_directories(aes-library)
# Creates and names a library, sets it as either STATIC
# or SHARED, and provides the relative paths to its source code.
# You can define multiple libraries, and CMake builds them for you.
# Gradle automatically packages shared libraries with your APK.
add_library( # Sets the name of the library.
native-lib
# Sets the library as a shared library.
SHARED
# Provides a relative path to your source file(s).
native-lib.cpp ${
AES_LIBRARY_SRC})
- 在java文件夹下新建native方法所在的类文件MyEncryptInterface
在该类中添加两个AES加/解密的native方法,outputType(0/1)控制加密输出为base64或者hex
按下Alt+Enter可快速生成C++接口
package com.xxx.xxx;
public class MyEncryptInterface{
static {
//加载so库
System.loadLibrary("native-lib");
}
public static native String aesEncrypt(String plainText, int outputType);
public static native String aesDecrypt(String encryptText, int outputType);
}
- 修改native-lib.cpp
将java的native方法转换成C++函数声明的规则是这样的:Java_{package_and_classname}_{function_name}(JNI arguments)。包名中的点换成单下划线。需要说明的是生成函数中的两个参数:
- JNIEnv *:这是一个指向JNI运行环境的指针,后面我们会看到,我们通过这个指针访问JNI函数
- jobject与jclass通常作为JNI函数的第二个参数,当所声明Native方法是静态方法时,对应参数jclass,因为静态方法不依赖对象实例,而依赖于类,所以参数中传递的是一个jclass类型。相反,如果声明的Native方法时非静态方法时,那么对应参数是jobject 。
#include <jni.h>
#include <string>
#include "aes-library/AES.h"
#include "aes-library/Base64.h"
#include<android/log.h>
#define TAG "xxx" // 这个是自定义的LOG的Tag
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG,TAG ,__VA_ARGS__) // 定义LOGD类型
char const hex[16] = {
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'};
std::string byte_2_hex(char* bytes, int size) {
std::string str;
for (int i = 0; i < size; ++i) {
const char ch = bytes[i];
str.append(&hex[(ch & 0xF0) >> 4], 1);
str.append(&hex[ch & 0xF], 1);
}
return str;
}
int hexCharToInt(char c){
if(c >= '0' && c <= '9') return (c - '0');
if(c >= 'A' && c <= 'F') return (c - 'A'+10);
if(c >= 'a' && c <= 'f') return (c - 'a'+10);
return 0;
}
std::string hex_2_byte(std::string s){
int sz = (int) s.length();
char *ret = new char[sz/2];
for (int i = 0; i < sz; i+=2) {
ret[i/2] = (char)((hexCharToInt(s.at(i)) << 4)|hexCharToInt(s.at(i+1)));
}
return std::string(ret);
}
const char g_key[17] = "xxxx"; //AES加密key
const char g_iv[17] = "xxx"; //AES加密CBC模式偏移量
JNIEXPORT jstring JNICALL
Java_com_xxx_xxx_MyEncryptInterface_aesEncrypt(JNIEnv *env, jclass clazz,
jstring plain_text,
jint output_type) {
// TODO: implement aesEncrypt()
const std::string strSrc = std::string(env->GetStringUTFChars(plain_text, 0));
size_t length = strSrc.length();
int block_num = length / BLOCK_SIZE + 1;
//明文
char *szDataIn = new char[block_num * BLOCK_SIZE + 1];
memset(szDataIn, 0x00, block_num * BLOCK_SIZE + 1);
strcpy(szDataIn, strSrc.c_str());
//进行PKCS7Padding填充。
// int k = length % BLOCK_SIZE;
// int j = length / BLOCK_SIZE;
// int padding = BLOCK_SIZE - k;
// for (int i = 0; i < padding; i++)
// {
// szDataIn[j * BLOCK_SIZE + k + i] = padding;
// }
// szDataIn[block_num * BLOCK_SIZE] = '\0';
//
// //加密后的密文
char *szDataOut = new char[block_num * BLOCK_SIZE + 1];
memset(szDataOut, 0, block_num * BLOCK_SIZE + 1);
//进行PKCS5Padding填充。
// int nRaw_size = strParams.size();
int i = 0, j = length / 8 + 1, k = length % 8;
int nPidding_size = 8 - k;
// char* szArray = (char *)malloc(length + nPidding_size);
// memcpy(szArray, strSrc.c_str(), length);
for (int i1 = length; i1 < (length + nPidding_size); i1++) {
// PKCS5Padding 算法:
// szDataIn[i1] = nPidding_size;
// zeroPadding 算法:
szDataIn[i1] = 0;
}
//进行进行AES的CBC模式加密
AES aes;
aes.MakeKey(g_key, g_iv, 16, 16);
aes.Encrypt(szDataIn, szDataOut, block_num * BLOCK_SIZE, AES::CBC);
string str;
if (output_type == 0) {
LOGD("##########encrypt输出base64");
str = base64_encode((unsigned char *) szDataOut,
block_num * BLOCK_SIZE);
} else {
LOGD("##########encrypt输出hex");
str = byte_2_hex(szDataOut, block_num * BLOCK_SIZE);
}
delete[] szDataIn;
delete[] szDataOut;
return env->NewStringUTF(str.c_str());
}
JNIEXPORT jstring JNICALL
Java_com_com_xxx_xxx_MyEncryptInterface_aesDecrypt(JNIEnv *env, jclass clazz,
jstring encrypt_text,
jint output_type) {
// TODO: implement aesDecrypt()
const std::string strSrc = std::string(env->GetStringUTFChars(encrypt_text, 0));
string strData;
if (output_type == 0) {
LOGD("##########decrypt输出base64");
strData = base64_decode(strSrc);
} else {
LOGD("##########decrypt输出hex");
strData = hex_2_byte(strSrc);
}
size_t length = strData.length();
//密文
char *szDataIn = new char[length + 1];
memcpy(szDataIn, strData.c_str(), length + 1);
//明文
char *szDataOut = new char[length + 1];
memcpy(szDataOut, strData.c_str(), length + 1);
//进行AES的CBC模式解密
AES aes;
aes.MakeKey(g_key, g_iv, 16, 16);
aes.Decrypt(szDataIn, szDataOut, length, AES::CBC);
//去掉PKCS7Padding/zeroPadding填充
if (0x00 < szDataOut[length - 1] <= 0x16) {
int tmp = szDataOut[length - 1];
for (int i = length - 1; i >= length - tmp; i--) {
if (szDataOut[i] != tmp) {
memset(szDataOut, 0, length);
break;
} else
szDataOut[i] = 0;
}
}
string strDest(szDataOut);
delete[] szDataIn;
delete[] szDataOut;
return env->NewStringUTF(strDest.c_str());
}
}
- 编写一个对外暴露的sdk接口类
package com.xxx.xxx;
public class MyEncryptTool {
private static MyEncryptTool delegate;
private MyEncryptTool (){
}
public static MyEncryptTool getDelegate() {
if (delegate == null) {
delegate = new MyEncryptTool();
}
return delegate;
}
public String aesEncrypt(String plainText, int type){
return MyEncryptInterface.aesEncrypt(plainText, type);
}
public String aesDecrypt(String encryptText, int type){
return MyEncryptInterface.aesDecrypt(encryptText, type);
}
}
- build一下mylibrary模块生成aar,app模块引入aar后即可调用MyEncryptTool.getDelegate().aesEncrypt加密字符串