Android使用JNI实现本地数据加解密

why

1、Apk包很容易被破解(应该有共识)

2、Java语言不安全,容易被反编译从而看到源码

3、C语言相对安全,因为编译后是二进制文件

4、秘钥不应该在java代码中保存,容易被反编译看到,所以秘钥必须放在C中

5、只把秘钥放在C中也不安全,因为如果反编译看到java调用so包方法并且拿到so包也是可以拿到秘钥的,所以没有意义。因此需要把整个加解密过程都放在C端。

6、如果旧的版本使用java加密,而新的版本使用Jni加密,那么就得考虑兼容性的问题,即Java和JNI可以互相加解密

how

JNI使用配置过程我就不说了网上很多,直接从代码开始。

demo目录 如图:
TimLine图片20181123152201.png

JniUtil就是负责本地数据的加解密,代码如下:

package com.example.xingchang.jni_aes256_demo;

public class JniUtil {
    static {
        System.loadLibrary("JniUtil");
    }
    public native String encrypt(String plainText);
    public native String decrypt(String cipherText);

}

真正进行加解密操作的是JniUtil.c文件,代码如下:

//
// Created by xing.chang on 2018/10/24.
//

#include "com_example_xingchang_jni_aes256_demo_JniUtil.h"
#include "base64.h"
#include <stdlib.h>
#include <aes256.h>
#include<android/log.h>
const char *DES_KEY = "b5e52765b81d101510dc0afdc52b1d64";
const char *VIPARA = "1982051319810208";

#define TAG "myDemo-jni" // 这个是自定义的LOG的标识
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG,TAG ,__VA_ARGS__) // 定义LOGD类型
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO,TAG ,__VA_ARGS__) // 定义LOGI类型
#define LOGW(...) __android_log_print(ANDROID_LOG_WARN,TAG ,__VA_ARGS__) // 定义LOGW类型
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR,TAG ,__VA_ARGS__) // 定义LOGE类型
#define LOGF(...) __android_log_print(ANDROID_LOG_FATAL,TAG ,__VA_ARGS__) // 定义LOGF类型

jstring charToJstring(JNIEnv* envPtr, char *src) {
    JNIEnv env = *envPtr;

    jsize len = strlen(src);
    jclass clsstring = env->FindClass(envPtr, "java/lang/String");
    jstring strencode = env->NewStringUTF(envPtr, "UTF-8");
    jmethodID mid = env->GetMethodID(envPtr, clsstring, "<init>", "([BLjava/lang/String;)V");
    jbyteArray barr = env->NewByteArray(envPtr, len);
    env->SetByteArrayRegion(envPtr, barr, 0, len, (jbyte*) src);

    return (jstring) env->NewObject(envPtr, clsstring, mid, barr, strencode);
}

jstring getImportInfo(JNIEnv* envPtr, jstring mingwen) {
    JNIEnv env = *envPtr;

    //b5e52765b81d101510dc0afdc52b1d64 秘钥
     unsigned char key[32] = {0x62,0x35,0x65,0x35,0x32,0x37,0x36,0x35,
                                  0x62,0x38,0x31,0x64,0x31,0x30,0x31,0x35,
                                  0x31,0x30,0x64,0x63,0x30,0x61,0x66,0x64,
                                  0x63,0x35,0x32,0x62,0x31,0x64,0x36,0x34};

    //****************************************开始加密******************************************************
    //1.初始化数据
    //初始化向量
    uint8_t iv[16] = { 0x31,0x39,0x38,0x32,0x30,0x35,0x31,0x33,
                       0x31,0x39,0x38,0x31,0x30,0x32,0x30,0x38 };

    //初始化加密参数
    aes256_context ctx;
    aes256_init(&ctx, key);

    //2.将jstring转为char
    const char *mwChar = env->GetStringUTFChars(envPtr, mingwen, JNI_FALSE);

    //3.分组填充加密
    int i;
    int mwSize = strlen(mwChar);
    int remainder = mwSize % 16;
    jstring entryptString;
    if (mwSize < 16) {  //小于16字节,填充16字节,后面填充几个几 比方说10个字节 就要补齐6个6 11个字节就补齐5个5
        uint8_t input[16];
        for (i = 0; i < 16; i++) {
            if (i < mwSize) {
                input[i] = (unsigned char) mwChar[i];
            } else {
                input[i] = (unsigned char) (16 - mwSize);
            }
        }
        //加密
        uint8_t output[16];
        aes256_encrypt_cbc(&ctx, input, iv, output);
        //base64加密后然后jstring格式输出
        char *enc = base64_encode((const char *) output, sizeof(output));
        entryptString = charToJstring(envPtr, enc);

        free(enc);
    } else {    //如果是16的倍数,填充16字节,后面填充0x10
        int group = mwSize / 16;
        int size = 16 * (group + 1);
        uint8_t input[size];
        for (i = 0; i < size; i++) {
            if (i < mwSize) {
                input[i] = (unsigned char) mwChar[i];
            } else {
                if (remainder == 0) {
                    input[i] = 0x10;
                } else {    //如果不足16位 少多少位就补几个几  如:少4为就补4个4 以此类推
                    int dif = size - mwSize;
                    input[i] = (unsigned char) dif;
                }
            }
        }
        //加密
        uint8_t output[size];
        aes256_encrypt_cbc(&ctx, input, iv, output);
        //base64加密后然后jstring格式输出
        LOGD("encrypt output size=%d",size);
        char *enc = base64_encode((const char *) output, sizeof(output));
        LOGD("encrypt enc=%s",enc);
        entryptString = charToJstring(envPtr, enc);

        free(enc);
    }

    //释放mwChar
    env->ReleaseStringUTFChars(envPtr, mingwen, mwChar);

    return entryptString;
}

JNIEXPORT jstring JNICALL Java_com_example_xingchang_jni_1aes256_1demo_JniUtil_encrypt
  (JNIEnv *env, jobject instance, jstring jstr){
    if (jstr == NULL) {
        return NULL;
    }
    return getImportInfo(env,jstr);
  }

jstring doecrypt(JNIEnv* env, jstring miwen) {
    jstring result;

    //b5e52765b81d101510dc0afdc52b1d64 秘钥
    unsigned char key[32] = {0x62,0x35,0x65,0x35,0x32,0x37,0x36,0x35,
                             0x62,0x38,0x31,0x64,0x31,0x30,0x31,0x35,
                             0x31,0x30,0x64,0x63,0x30,0x61,0x66,0x64,
                             0x63,0x35,0x32,0x62,0x31,0x64,0x36,0x34};

    //1.初始化数据
    //初始化向量
    uint8_t iv[16] = { 0x31,0x39,0x38,0x32,0x30,0x35,0x31,0x33,
                       0x31,0x39,0x38,0x31,0x30,0x32,0x30,0x38 };
    aes256_context ctx;
    aes256_init(&ctx, key);

    //2.将jstring转为char
    const char *mwChar = (*env)->GetStringUTFChars(env, miwen, JNI_FALSE);
    char *enc = base64_decode(mwChar, strlen(mwChar));
    uint8_t output[4096];
    aes256_decrypt_cbc(&ctx, (unsigned char *) enc, iv, output);
    int size = strlen((const char *) output);
    LOGD("output size=%d",size);
    int i;
    for(i=0;i<size;i++){
        LOGD("cha %d = %c",i,output[i]);
        if(output[i]>=1&&output[i]<=16){
            output[i] = 0;
        }
    }
    result = charToJstring(env, (char *) output);
    LOGD("result=%s",(char *) output);
    free(enc);
    //释放mwChar
    (*env)->ReleaseStringUTFChars(env, miwen, mwChar);
    aes256_done(&ctx);
    return result;
}

JNIEXPORT jstring JNICALL Java_com_example_xingchang_jni_1aes256_1demo_JniUtil_decrypt
  (JNIEnv *env, jobject instance, jstring jstr){
    if (jstr == NULL) {
        return NULL;
    }
    return doecrypt(env,jstr);
  }


getImportInfo方法是加密,步骤是
1、准备秘钥和向量
2、按照PKCS5Padding方式进行填充
3、调用aes256_encrypt_cbc进行cbc加密
4、然后再用base64_encode进行加密
5、最后输出密文

doecrypt方法是解密,步骤是:
1、准备秘钥和向量
2、调用base64_decode解密
3、调用aes256_decrypt_cbc解密
4、去掉填充
5、输出明文

在main中调用jni和java加解密:

package com.example.xingchang.jni_aes256_demo

import android.support.v7.app.AppCompatActivity
import android.os.Bundle
import android.util.Log

class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        //jni加解密
        val jniUtil = JniUtil()
        val str= "abcdefg1234567"
        val encryptResult= jniUtil.encrypt(str)
        Log.i("jniUtil","JNI encryptResult=$encryptResult")
        val decryptResult= jniUtil.decrypt(encryptResult)
        Log.i("jniUtil","JNI decryptResult=$decryptResult")

        //java加解密
        val key_md5 ="b5e52765b81d101510dc0afdc52b1d64"
        val javaEncrypt= AesUtils.aesEncrypt(str,key_md5)
        Log.i("jniUtil","Java encryptResult=$javaEncrypt")
        val javaDecrypt = AesUtils.aesDecrypt(javaEncrypt,key_md5)
        Log.i("jniUtil","Java decryptResult=$javaDecrypt")
    }
}

执行结果如下:

JNI encryptResult=/IeeaSmRmkAGCzmZYvNOKw==
JNI decryptResult=abcdefg1234567
Java encryptResult=/IeeaSmRmkAGCzmZYvNOKw==
Java decryptResult=abcdefg1234567

源码Demo

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,590评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 86,808评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,151评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,779评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,773评论 5 367
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,656评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,022评论 3 398
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,678评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,038评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,659评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,756评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,411评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,005评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,973评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,203评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,053评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,495评论 2 343

推荐阅读更多精彩内容

  • 用两张图告诉你,为什么你的 App 会卡顿? - Android - 掘金 Cover 有什么料? 从这篇文章中你...
    hw1212阅读 12,691评论 2 59
  • 本文主要介绍移动端的加解密算法的分类、其优缺点特性及应用,帮助读者由浅入深地了解和选择加解密算法。文中会包含算法的...
    苹果粉阅读 11,454评论 5 29
  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,442评论 25 707
  • 雨后,青苔生密的石板路上,少年迎面走来。 一瞥黑发及耳鬓,两眼青衣宽身,三低头小心石板下的水洼,四目对视匆匆擦肩而...
    Miss_kiwi阅读 677评论 4 5
  • 前言: 其实,在看到这个题目的时候我就已经开始浮想联翩了。 陌生,到底是怎样的一种感觉呢? 又或者是一种气质? 陌...
    穆勒书信时光阅读 413评论 4 2