加密ArcGIS離線地圖及其在Android上的實現

來源:互聯網
上載者:User

 

加密ArcGIS離線地圖的思路

目前,使用Compact格式的切片檔案是離線地圖的一個很好的方案,但是,如果我們可能會希望限制離線地圖不被第三方程式使用;或者,希望限制離線地圖只被經過授權的裝置使用。在這樣的需求下,我們必須保護好部署在智慧型裝置上的離線地圖資料,因此,需要對離線地圖資料進行加密。

在這裡,我使用了這樣的一個思路,其中包含以下主要環節:

1. 經授權的裝置序號+保密的標識符再經過MD5產生校正值。

2. MD5校正值與加密的離線資料一起分發,由於第三方程式無法得知保密的標識符,因此無法產生正確的校正值。

3. 離線資料的加密通過加密索引檔案實現,加密通過位元組交換實現,這樣可以基本不影響效能。

4. 讀取加密檔案的演算法封裝在動態串連庫中,確保第三方無法通過反編譯手段獲得演算法。

下面詳細敘述各個環節的實現。

裝置唯一身份的確認

裝置的唯一序號可能在不同種類的系統上都有不同擷取的方法,通過CPU序號、IMEI編號、MAC地址等多種途徑的組合可以產生每個裝置都不同的標識符,比如在Android中,可以以IMEI和IMSI的組合產生一個序號:

TelephonyManager tm = (TelephonyManager) this

.getSystemService(Context.TELEPHONY_SERVICE);

String imei = tm.getDeviceId();

String imsi = tm.getSubscriberId();

deviceId = String.format("%s-%s", imei, imsi);

比如我這裡得到一個裝置標識“000000000000000-310260000000000”,下面根據不同情況,對上述的裝置標識附加一個保密的標識符,再計算其MD5校正值:

String id = String.format("%s-%s", deviceId, "wuyf_qwert");

MessageDigest md = MessageDigest.getInstance("MD5");

byte[] bytes = md.digest(id.getBytes());

result = StringUtil.bytesToHexString(bytes);

這裡的“wuyf_qwert”就是自己定義的保密標識符,這個保密標識符只有資料的發行者才知道,因此第三方無法通過裝置標識符自行產生校正值。最後,可以將校正值儲存在一個以裝置序號命名的檔案中,和資料一起發布(多個裝置使用多個校正檔案,增加刪除都很方便)。

圖 1 與資料一同部署的校正檔案

離線地圖的加密

考慮效能的影響,對離線地圖的加密需要使用儘可能簡單的加密方法,因此,這裡使用對離線地圖的索引檔案進行加密的方法。對於原始的索引資料檔案,我們只需要對若干位元組進行交換即可,使用者可以根據需要改變自己的加密方法,同樣,這個加密方法只有資料的發行者才知道:

static public void encrypt(String inPath, String outPath) {

FileInputStream in = null;

FileOutputStream out = null;

try {

File inFile = new File(inPath);

File outFile = new File(outPath);

in = new FileInputStream(inFile);

out = new FileOutputStream(outFile, false);

int read;

read = in.read();

int count = 0;

while (read != -1) {

byte b = (byte) read;

// 此處可以增加置換對數以增加檔案的複雜度

if(read==3){

b = (byte)37;

count++;

}else if(read==37){

b = (byte)3;

count++;

}

out.write(b);

read = in.read();

}

out.flush();

System.out.println(count);

} catch (Exception ex) {

ex.printStackTrace();

} finally {

try {

in.close();

out.close();

} catch (Exception ex) {}

}

}

在裝置上讀取加密的離線地圖

裝置上讀取加密的離線地圖分為2步:校正裝置的身份和擷取加密的資料,這兩步都必須封裝在動態串連庫中確保演算法的保密。在Android上,需要通過JNI實現,我們可以把這兩步都封裝在一個C函數中:

JNIEXPORT jbyteArray JNICALL Java_com_esri_wuyf_JNI_getEncryptTile(JNIEnv* env,

jobject obj, jstring strDeviceId, jstring strLocation,

jstring strBundleBase, jint level, jint row, jint col) {

jbyteArray result = 0;

const char* deviceId = (*env)->GetStringUTFChars(env, strDeviceId, 0);

const char* location = (*env)->GetStringUTFChars(env, strLocation, 0);

const char* bundleBase = (*env)->GetStringUTFChars(env, strBundleBase, 0);

__android_log_write(ANDROID_LOG_INFO, "JNI 裝置編號", deviceId);

__android_log_write(ANDROID_LOG_INFO, "JNI 資料位元置", location);

__android_log_write(ANDROID_LOG_INFO, "JNI 資料位元於", bundleBase);

// 產生一些路徑

const char* sValid = my_strcat(location, deviceId);

const char* sIndex = my_strcat(my_strcat(location, "_alllayers/"),

my_strcat(bundleBase, ".bundly"));

const char* sTile = my_strcat(my_strcat(location, "_alllayers/"),

my_strcat(bundleBase, ".bundle"));

// 裝置標識需要串連一個秘密的字串

const char* security = "-wuyf_qwert";

const char* s = my_strcat(deviceId, security);

// 產生MD5校正值,MD5結果全部使用小寫

struct MD5Context md5c;

MD5Init(&md5c);

MD5Update(&md5c, s, strlen(s));

unsigned char ss[16];

MD5Final(ss, &md5c);

// 檢查MD5校正是不是滿足,如果不滿足則立即返回,不進行後續處理

int valid = 0;

FILE* fValid;

if ((fValid = fopen(sValid, "rb")) != NULL) {

char str[32];

fread(str, 32, 1, fValid);

int i;

int hasError = 0;

for (i = 0; i

unsigned int s1 = ss[i];

int s2 = str[2 * i];

if (s2 >= 48 && s2

s2 -= 48;

else if (s2 >= 97 && s2

s2 -= 87;

int s3 = str[2 * i + 1];

if (s3 >= 48 && s3

s3 -= 48;

else if (s3 >= 97 && s3

s3 -= 87;

if (s1 != 16 * s2 + s3) {

hasError = 1;

break;

}

}

if (hasError == 0) {

valid = 1;

}

}

fclose(fValid);

if (valid == 1) {

__android_log_write(ANDROID_LOG_INFO, "JNI", "裝置身份校正通過");

// 校正無誤,開始擷取切片

int rGroup = 128 * (row / 128);

int cGroup = 128 * (col / 128);

int index = 128 * (col - cGroup) + (row - rGroup);

__android_log_write(ANDROID_LOG_INFO, "JNI 開始讀取加密索引", sIndex);

FILE* fIndex;

long offset = -1;

if ((fIndex = fopen(sIndex, "rb")) != NULL) {

fseek(fIndex, 16 + 5 * index, SEEK_SET);

char buffer[5];

fread(buffer, 5, 1, fIndex);

int i;

for (i = 0; i

if (buffer[i] == 3) {

buffer[i] = 37;

} else if (buffer[i] == 37) {

buffer[i] = 3;

}

}

offset = (long) (buffer[0] & 0xff) + (long) (buffer[1] & 0xff)

* 256 + (long) (buffer[2] & 0xff) * 65536

+ (long) (buffer[3] & 0xff) * 16777216 + (long) (buffer[4]

& 0xff) * 4294967296;

}

fclose(fIndex);

__android_log_write(ANDROID_LOG_INFO, "JNI 開始讀取資料", sTile);

FILE* fTile;

if ((fTile = fopen(sTile, "rb")) != NULL) {

fseek(fTile, offset, SEEK_SET);

char lengthBytes[4];

fread(lengthBytes, 4, 1, fTile);

int length = (int) (lengthBytes[0] & 0xff) + (int) (lengthBytes[1]

& 0xff) * 256 + (int) (lengthBytes[2] & 0xff) * 65536

+ (int) (lengthBytes[3] & 0xff) * 16777216;

char* tile = malloc(sizeof(char) * length);

fread(tile, length, 1, fTile);

__android_log_write(ANDROID_LOG_INFO, "JNI", "擷取資料成功");

result = (*env)->NewByteArray(env, length);

(*env)->SetByteArrayRegion(env, result, 0, length, tile);

free(tile);

}

fclose(fTile);

}

free((void*) s);

free((void*) sValid);

free((void*) sIndex);

free((void*) sTile);

return result;

}

上述代碼中高亮的2段分別對應了校正裝置和解密資料的關鍵,可以看到這和前面的演算法是可以對應起來的,當然,這個演算法只有資料的發行者掌握。

在Android程式中,擷取地圖資料只需要調用一個Java方法就可以:

result = jni.getEncryptTile(deviceId, location, bundleBase, level, row, col);

現在,即使反編譯了Android程式中的dex檔案,你也無法知道這句代碼背後調用的動態連結程式庫中實際的演算法。

下面是加密ArcGIS離線地圖在Android上的效果:

圖 2 Android上顯示加密離線地圖的效果

附錄:Android上進行JNI開發的步驟準備開發環境

1. 下載安裝Cygwin:http://cygwin.com/setup.exe,注意安裝時需要選擇Devel工具包以及vim(用以編輯環境變數)。

2. 下載Android NDK,我使用的是r5版本:http://dl.google.com/android/ndk/android-ndk-r5-windows.zip,解壓到本地磁碟。

建立一個Java類

在Android工程中建立一個Java類,注意”native”標記:

package com.esri.wuyf;

public class JNI {

public native byte[] getEncryptTile(String deviceId, String location, String bundleBase, int level, int row, int col);

}

使用Java工具產生JNI標頭檔

在Android工程的bin目錄下運行命令列:

Microsoft Windows [版本 6.1.7600]

著作權 (c) 2009 Microsoft Corporation。著作權所有,並保留一切權利。

D:/wuyf/Workspace/Android/AgsEncryptTiles/bin>javah -jni com.esri.wuyf.JNI

執行成功後產生一個com_esri_wuyf_JNI.h檔案,現在在Android工程的根目錄下建立一個“jni”檔案夾,並將產生的這個C標頭檔拷貝到該目錄中:

/* DO NOT EDIT THIS FILE - it is machine generated */

#include

/* Header for class com_esri_wuyf_JNI */

#ifndef _Included_com_esri_wuyf_JNI

#define _Included_com_esri_wuyf_JNI

#ifdef __cplusplus

extern "C" {

#endif

/*

* Class: com_esri_wuyf_JNI

* Method: getEncryptTile

* Signature: (Ljava/lang/String;III)[B

*/

JNIEXPORT jbyteArray JNICALL Java_com_esri_wuyf_JNI_getEncryptTile

(JNIEnv *, jobject, jstring, jstring, jstring, jint, jint, jint);

#ifdef __cplusplus

}

#endif

#endif

實現JNI的具體方法

在Android工程的“jni”目錄下建立一個com_esri_wuyf_JNI.c檔案,並實現標頭檔中的函數:

#include "com_esri_wuyf_JNI.h"

#include "md5.h"

JNIEXPORT jbyteArray JNICALL Java_com_esri_wuyf_JNI_getEncryptTile(JNIEnv* env,

jobject obj, jstring strDeviceId, jstring strLocation,

jstring strBundleBase, jint level, jint row, jint col) {

……

return result;

}

建立Android的makefile檔案

在“jni”目錄下建立一個檔案Android.mk檔案,它是Android的makefile:

LOCAL_PATH:= $(call my-dir)

include $(CLEAR_VARS)

LOCAL_SRC_FILES:= com_esri_wuyf_JNI.c md5.c

LOCAL_C_INCLUDES := $(JNI_H_INCLUDE)

LOCAL_LDLIBS := -llog

LOCAL_PRELINK_MODULE := false

LOCAL_MODULE := JNI

include $(BUILD_SHARED_LIBRARY)

編譯JNI連結庫

從開始菜單進入Cygwin Bash Shell,首先在目前使用者的.bash_profile中添加一個$NDK環境變數(指向Android NDK的解壓目錄),讓我們可以更加方便地編譯Android JNI代碼:

$ vi ~/.bash_profile

export NDK=/cygdrive/d/Software/Develop/Android/android-ndk-windows

下面,在Cygwin Bash Shell中進入Android工程目錄(/cygdrive/d表示Windows中的D盤),並執行NDK的編譯命令:

這個libJNI.so會產生在Android工程的libs/armeabi目錄下,注意,調試時這個連結庫不會自動更新到Android裝置上,因此一旦重新編譯這個連結庫,需要手動push到裝置的相應目錄(這裡是/data/data/com.esri.wuyf/lib)下:

在Java代碼中調用JNI函數

在使用JNI函數的Java類中靜態載入JNI庫,然後建立JNI對象並調用其相應的方法:

static {

System.loadLibrary("JNI");

}

JNI jni = new JNI();

result = jni.getEncryptTile(deviceId, location, bundleBase, level, row, col);

將離線資料push到裝置中

開發中還需要將離線資料批量push到裝置中,這需要用Android的adb工具:

D:/Software/Develop/Android/android-sdk-windows/platform-tools>adb push D:/Temp /sdcard

這表示要將D:/Temp下所有內容push到裝置的SD卡中。注意,這個Temp目錄並不會出現在SD卡中。

另外,如果需要從SD卡大量刪除檔案必須進入shell執行Linux的rm命令:

>adb remount

>adb shell

# rm -R /sdcard/xxx

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.