Android NDK实践——开发AES加/解密工具包

背景

  • 将AES加密算法的秘钥放java源码层容易被反编译获取
  • 在C++层封装AES加/解密过程供java层调用
  • 目前支持AES加密模式CBC,填充方式:zeropadding,数据块128位,输出base64/hex

流程

新建Native C++工程

目的是为了获取as自动生成的CMakeLists.txt,不用我们手动生成
工程

新建一个Library模块mylibrary
  1. 将app模块的main目录下的cpp文件夹拷贝到mylibrary模块的main目录下
  2. 修改mylibrary模块的build.gradle
android {
    
    
    ...

    defaultConfig {
    
    
        ...
        externalNativeBuild {
    
    
            cmake {
    
    
            	// 设置标志来支持C++编译器的格式化宏常量
                cppFlags ""
            }
        }
    }

    externalNativeBuild {
    
    
        cmake {
    
    
        	//Gradle与原生库关联
            path "src/main/cpp/CMakeLists.txt"
            version "3.10.2"
        }
    }
}
  1. 在cpp文件夹下新建aes-library文件夹,用于存放AES加解密算法c++源码
  2. 将加解密算法c++源码AES.cpp,AES.h文件放进第3步新建的文件夹
  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})
  1. 在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);
}
  1. 修改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());
}
}
  1. 编写一个对外暴露的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);
    }
}
  1. build一下mylibrary模块生成aar,app模块引入aar后即可调用MyEncryptTool.getDelegate().aesEncrypt加密字符串
    在这里插入图片描述

在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/weixin_40855673/article/details/119189852