从 JNI 调用 getWritableDatabase(SQLCipher java 方法)

Calling getWritableDatabase (SQLCipher java method) from JNI

我有一个 java 代码来打开 SqlCipher 数据库。我想从 JNI 调用 getWritableDatabase 函数来隐藏密码。

Java_com_company_pos_DbHelper_getWritableSqlite中从SQLiteOpenHelperclass调用getWritableDatabas方法时发生错误 方法 (sqlauth.cpp),我的代码有什么问题?

DbHelper.java

public class DbHelper extends SQLiteOpenHelper {
    //private final static String PASSWORD= "MyPassword"
    private final static String TAG = "DbHelper";
    private static SQLiteDatabase db = null;

    /* This method works only password is not secure.
    public void open() {
        try {
            //The password isn't hidden
            db = getWritableDatabase(PASSWORD);
        } catch (SQLiteException e) {
            Log.e(TAG, e.getMessage());
        }
    }
    */

    static {
        System.loadLibrary("sqlauth");
    }

    private native SQLiteDatabase getWritableSqlite();

    public void open() {
        try {
            //The password is hidden in jNI
            db = getWritableSqlite();
        } catch (SQLiteException e) {
            Log.e(TAG, e.getMessage());
        }
    }

}

JNI

sqlauth.h

#include <jni.h>
#include <string.h>
#include <iostream>

#ifndef SQLAUTH_H
    #define SQLAUTH_H
#endif

static char *CLS_SQLITEOPENHELPER = (char *) "net/sqlcipher/database/SQLiteOpenHelper";
static char *MID_GETWRITABLEDATABASE = (char *) "getWritableDatabase";
static char *SIG_GETWRITABLEDATABASE = (char *) "(Ljava/lang/String;)Lnet/sqlcipher/database/SQLiteDatabase;";

#ifdef __cplusplus
    extern "C" {
#endif

JavaVM *_jvm;
jclass _clsSQLiteOpenHelper;
jobject _objGetWritableDatabase;
jmethodID _midGetWritableDatabase;

JNIEnv *getEnv();
jboolean tryCatch();
JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *jvm, void *reserved);
JNIEXPORT void JNICALL JNI_OnUnload(JavaVM *jvm, void *reserved);
JNIEXPORT jobject JNICALL Java_com_company_pos_DbHelper_getWritableSqlite(JNIEnv *e, jobject obj);

#ifdef __cplusplus
    }
#endif

sqlauth.cpp

#include "sqlauth.h"

#ifdef __cplusplus
    extern "C" {
#endif

JNIEnv *getEnv() {
    JNIEnv *env;
    _jvm->GetEnv((void **) &env, JNI_VERSION_1_6);

    return env;
}

jboolean tryCatch() {
    JNIEnv *env = getEnv();
    if (env == NULL) {
        return JNI_TRUE;
    }

    jthrowable ex = env->ExceptionOccurred();
    if (ex) {
        env->ExceptionClear();
        return JNI_TRUE;
    }
    return JNI_FALSE;
}

JNIEXPORT jint JNICALL
JNI_OnLoad(JavaVM *jvm, void *reserved) {
    JNIEnv *env;
    jclass clsDbHelper, clsSqliteDatabase, clsSQLiteOpenHelper;

    _jvm = jvm;
    if (jvm->GetEnv((void **) &env, JNI_VERSION_1_6)) {
        return JNI_ERR;
    }

    // SQLiteOpenHelper.java
    clsSQLiteOpenHelper = env->FindClass(CLS_SQLITEOPENHELPER);
    if (clsSQLiteOpenHelper == NULL) {
        return JNI_ERR;
    }
    _clsSQLiteOpenHelper = (jclass) env->NewWeakGlobalRef(clsSQLiteOpenHelper);
    if (_clsSQLiteOpenHelper == NULL) {
        return JNI_ERR;
    }
    _midGetWritableDatabase = env->GetMethodID(_clsSQLiteOpenHelper, MID_GETWRITABLEDATABASE, SIG_GETWRITABLEDATABASE);
    if (_midGetWritableDatabase == NULL) {
        return JNI_ERR;
    }
    return JNI_VERSION_1_6;
}

JNIEXPORT void JNICALL
JNI_OnUnload(JavaVM *jvm, void *reserved) {
    JNIEnv *env;

    if (jvm->GetEnv((void **) &env, JNI_VERSION_1_6)) {
        return;
    }
    env->DeleteWeakGlobalRef(_clsSQLiteOpenHelper);
    return;
}

JNIEXPORT jobject JNICALL
Java_com_company_pos_DbHelper_getWritableSqlite(JNIEnv *e, jobject obj) {
    JNIEnv *env = getEnv();
    if (env == NULL || obj == NULL) {
        return NULL;
    }

    // Error in here when calling getWritableDatabase 
    // from net/sqlcipher/database/SQLiteOpenHelper
    _objGetWritableDatabase = env->CallObjectMethod(_clsSQLiteOpenHelper, _midGetWritableDatabase, env->NewStringUTF("MyPassword"));  
    if (_objGetWritableDatabase == NULL || tryCatch()) {
        return NULL; // Program stop in here.
    }
    return _objGetWritableDatabase;
}

#ifdef __cplusplus
    }
#endif

SQLiteOpenHelper.class

public synchronized SQLiteDatabase getWritableDatabase(String password) {
    return this.getWritableDatabase(password == null?null:password.toCharArray());
}

根据pskink的指导,我可以通过如下更改代码来解决上述问题:

JNIEXPORT jobject JNICALL
Java_com_company_pos_DbHelper_getWritableSqlite(JNIEnv *e, jobject obj) {
    JNIEnv *env = getEnv();
    if (env == NULL || obj == NULL) {
        return NULL;
    }

    // Error in here when calling getWritableDatabase from net/sqlcipher/database/SQLiteOpenHelper
    _objGetWritableDatabase = env->CallObjectMethod(_clsSQLiteOpenHelper, _midGetWritableDatabase, env->NewStringUTF("MyPassword"));  
    if (_objGetWritableDatabase == NULL || tryCatch()) {
        return NULL; // Program stop in here.
    }
    return _objGetWritableDatabase;
}

JNIEXPORT jobject JNICALL
Java_com_company_pos_DbHelper_getWritableSqlite(JNIEnv *e, jobject obj) {
    JNIEnv *env = getEnv();
    if (env == NULL || obj == NULL) {
        return NULL;
    }

    _objGetWritableDatabase = env->CallObjectMethod(obj, _midGetWritableDatabase, env->NewStringUTF("MyPassword"));  
    if (_objGetWritableDatabase == NULL || tryCatch()) {
        return NULL;
    }
    return _objGetWritableDatabase;
}

密码不安全

You can change the MyPassword with hex string values (base 16). But store password in the form of string is not secured, we can view the pasword by opening the .so file with notepad++

static char *MyPassword = (char *) "93CEFC75923EA0370B6B04CECA4E7A99"

安全密码

以字节数组的形式存储加密后的密码,比较难读。读取时解密密码。像这样的例子:

const size_t BIT_LENGTH = 16;
static unsigned char bit_pwd[] = {0x13, 0x4E, 0x83, 0xF4, 0xEC, 0xBC, 0xDD, 0xB4, 0x77, 0xEF, 0x7F, 0x4B, 0xB0, 0xC8, 0x03, 0x1E};
static unsigned char chr_pwd[BIT_LENGTH * 2];

char *getPwd() {
    unsigned char c1 = 0x80;
    unsigned char c2 = c1;
    unsigned char *p = chr_pwd;

    memset(chr_pwd, '[=13=]', BIT_LENGTH * 2);
    for (jint i = 0; i < BIT_LENGTH; i++) {
        if (i % 2 != 0) {
            bit_pwd[i] ^= c1++;
        }
        else {
            bit_pwd[i] ^= c2--;
        }
        p += sprintf((char *) p, "%02X", bit_pwd[i]);
    }
    chr_pwd[BIT_LENGTH * 2] = '[=13=]';
    return (char *) chr_pwd;
}

// You will get real password ("93CEFC75923EA0370B6B04CECA4E7A99") from getPwd method.
_objGetWritableDatabase = env->CallObjectMethod(obj, _midGetWritableDatabase, env->NewStringUTF(getPwd()));