gpt4 book ai didi

java - Android 中 SHA1 哈希实现的问题

转载 作者:塔克拉玛干 更新时间:2023-11-02 20:19:26 26 4
gpt4 key购买 nike

我有两个用于计算 SHA1 的小 fragment 。

一个非常快但似乎不正确,另一个非常慢但正确。
我认为FileInputStream转换为 ByteArrayInputStream是问题所在。

快速版:

MessageDigest md = MessageDigest.getInstance("SHA1");
FileInputStream fis = new FileInputStream("path/to/file.exe");
ByteArrayInputStream byteArrayInputStream =
new ByteArrayInputStream(fis.toString().getBytes());
DigestInputStream dis = new DigestInputStream(byteArrayInputStream, md);
BufferedInputStream bis = new BufferedInputStream(fis);
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();

int ch;
while ((ch = dis.read()) != -1) {
byteArrayOutputStream.write(ch);
}

byte[] newInput = byteArrayOutputStream.toByteArray();
System.out.println("in digest : " +
byteArray2Hex(dis.getMessageDigest().digest()));

byteArrayOutputStream = new ByteArrayOutputStream();
DigestOutputStream digestOutputStream =
new DigestOutputStream(byteArrayOutputStream, md);
digestOutputStream.write(newInput);

System.out.println("out digest: " +
byteArray2Hex(digestOutputStream.getMessageDigest().digest()));
System.out.println("length: " +
new String(
byteArray2Hex(digestOutputStream.getMessageDigest().digest())).length());

digestOutputStream.close();
byteArrayOutputStream.close();
dis.close();

慢版:
MessageDigest algorithm = MessageDigest.getInstance("SHA1");
FileInputStream fis = new FileInputStream("path/to/file.exe");
BufferedInputStream bis = new BufferedInputStream(fis);
DigestInputStream dis = new DigestInputStream(bis, algorithm);

// read the file and update the hash calculation
while (dis.read() != -1);

// get the hash value as byte array
byte[] hash = algorithm.digest();

转换方法:
private static String byteArray2Hex(byte[] hash) {
Formatter formatter = new Formatter();
for (byte b : hash) {
formatter.format("%02x", b);
}
return formatter.toString();
}

我希望有另一种可能让它运行,因为我需要性能。

最佳答案

我使用了一个用 JNI 加载的高性能 C++ 实现。
有关更多详细信息,请写评论。

编辑:
JNI 的要求是 Android NDK .对于 Windows 还需要 cygwin或类似的东西。
如果你决定使用 cygwin,我会给你一些关于如何让它与 NDK 一起工作的小说明:

  • 从 cygwin 下载 setup.exe 并执行它。
  • 单击下一步并选择从 Internet 安装,然后单击下一步确认。
  • 接下来的两个步骤根据需要调整设置,并一如既往地单击下一步。
  • 选择您的互联网连接和与最后阶段相同的程序。
  • 下载页面会吸引眼球,选择它或仅获取您所在国家/地区的下载页面。没有什么可说的了。
  • 我们需要包 make 和 gcc-g++。您可以使用左上角的搜索找到它们,点击 跳过 直到显示版本并选择第一个字段。做我们在选择后一直做的事情。
  • 您将获得信息,即存在必须解决的依赖项。通常不需要自己做并确认。
  • 下载和安装开始。
  • 如果您需要,您可以创建快捷方式,否则点击异常(exception) 完成 .
  • 下载 zip 文件并将 NDK 解压缩到不包含空格的路径。
  • 您现在可以开始使用 cygwin。
  • 导航到 NDK。路径/cydrive 为您提供所有可用的驱动器 f.e. cd /cygdrive/d导航到带有字母 的驱动器D .
  • 在 NDK 的根文件夹中,您可以使用 ./ndk-build 执行文件 ndk-build .应该会出现类似 Android NDK: Could not find application project directory ! 的错误.
    您必须在 Android 项目中导航才能执行该命令。所以让我们从一个项目开始。

  • 在我们开始项目之前,搜索哈希算法的 C/C++ 实现。我从这个站点获取了代码 CSHA1 .
    您应该根据您的要求编辑源代码。

    现在我们可以从 JNI 开始。
    您在 Android 项目中创建了一个名为 jni 的文件夹。它也包含所有 native 源文件和 Android.mk(稍后会详细介绍该文件)。
    将您下载(和编辑)的源文件复制到该文件夹​​中。

    我的 java 包名为 de.dhbw.file.sha1,因此我将源文件命名为类似的名称,以便轻松找到它们。

    Android.mk:
    LOCAL_PATH := $(call my-dir)

    include $(CLEAR_VARS)

    LOCAL_LDLIBS := -llog

    # How the lib is called?
    LOCAL_MODULE := SHA1Calc
    # Which is your main SOURCE(!) file?
    LOCAL_SRC_FILES := de_dhbw_file_sha1_SHA1Calc.cpp

    include $(BUILD_SHARED_LIBRARY)

    Java代码:
    我使用带有 ProgressDialog 的 AsyncTask 向用户提供有关该操作的一些反馈。
    package de.dhbw.file.sha1;

    // TODO: Add imports

    public class SHA1HashFileAsyncTask extends AsyncTask<String, Integer, String> {
    // [...]

    static {
    // loads a native library
    System.loadLibrary("SHA1Calc");
    }

    // [...]

    // native is the indicator for native written methods
    protected native void calcFileSha1(String filePath);

    protected native int getProgress();

    protected native void unlockMutex();

    protected native String getHash();

    // [...]
    }

    native 代码(C++):

    请记住,访问 native 代码中的变量或使用线程的其他方式需要同步,否则您很快就会遇到段错误!

    对于 JNI 用法,您必须添加 #include <jni.h> .

    对于记录插入以下包括 #include <android/log.h> .
    现在您可以使用 __android_log_print(ANDROID_LOG_DEBUG, DEBUG_TAG, "Version [%s]", "19"); 登录.
    第一个参数是消息的类型,第二个参数是导致库。
    你可以看到我的代码中有一个版本号。这非常有用,因为有时 apk 构建器不使用新的 native 库。如果错误的版本在线,则可以极大地缩短故障排除时间。

    native 代码中的命名约定有点疯狂: Java_[package name]_[class name]_[method name] .

    始终给出第一个参数,但根据应用程序,您应该区分:
  • func(JNIEnv * env, jobject jobj) -> JNI 调用是一个实例方法
  • func(JNIEnv * env, jclass jclazz) -> JNI 调用是静态方法

  • 方法的标题 calcFileSha1(...) : JNIEXPORT void JNICALL Java_de_dhbw_file_sha1_SHA1HashFileAsyncTask_calcFileSha1(JNIEnv * env, jobject jobj, jstring file)
    JDK 提供二进制 javah.exe,它生成 native 代码的头文件。用法很简单,直接用全限定类调用即可: javah de.dhbw.file.sha1.SHA1HashFileAsyncTask
    就我而言,我必须另外提供引导类路径,因为我使用 Android 类: javah -bootclasspath <path_to_the_used_android_api> de.dhbw.file.sha1.SHA1HashFileAsyncTask
    那将是生成的文件:
    /* DO NOT EDIT THIS FILE - it is machine generated */
    #include <jni.h>
    /* Header for class de_dhbw_file_sha1_SHA1HashFileAsyncTask */

    #ifndef _Included_de_dhbw_file_sha1_SHA1HashFileAsyncTask
    #define _Included_de_dhbw_file_sha1_SHA1HashFileAsyncTask
    #ifdef __cplusplus
    extern "C" {
    #endif
    #undef de_dhbw_file_sha1_SHA1HashFileAsyncTask_ERROR_CODE
    #define de_dhbw_file_sha1_SHA1HashFileAsyncTask_ERROR_CODE -1L
    #undef de_dhbw_file_sha1_SHA1HashFileAsyncTask_PROGRESS_CODE
    #define de_dhbw_file_sha1_SHA1HashFileAsyncTask_PROGRESS_CODE 1L
    /*
    * Class: de_dhbw_file_sha1_SHA1HashFileAsyncTask
    * Method: calcFileSha1
    * Signature: (Ljava/lang/String;)V
    */
    JNIEXPORT void JNICALL Java_de_dhbw_file_sha1_SHA1HashFileAsyncTask_calcFileSha1
    (JNIEnv *, jobject, jstring);

    /*
    * Class: de_dhbw_file_sha1_SHA1HashFileAsyncTask
    * Method: getProgress
    * Signature: ()I
    */
    JNIEXPORT jint JNICALL Java_de_dhbw_file_sha1_SHA1HashFileAsyncTask_getProgress
    (JNIEnv *, jobject);

    /*
    * Class: de_dhbw_file_sha1_SHA1HashFileAsyncTask
    * Method: unlockMutex
    * Signature: ()V
    */
    JNIEXPORT void JNICALL Java_de_dhbw_file_sha1_SHA1HashFileAsyncTask_unlockMutex
    (JNIEnv *, jobject);

    /*
    * Class: de_dhbw_file_sha1_SHA1HashFileAsyncTask
    * Method: getHash
    * Signature: ()Ljava/lang/String;
    */
    JNIEXPORT jstring JNICALL Java_de_dhbw_file_sha1_SHA1HashFileAsyncTask_getHash
    (JNIEnv *, jobject);

    #ifdef __cplusplus
    }
    #endif
    #endif

    您可以更改文件,恕不另行通知。但不要使用 javah再次!

    类和方法
    要获取类实例,您可以使用 jclass clz = callEnv->FindClass(CALL_CLASS); .在这种情况下是 CALL_CLASS类 de/dhbw/file/sha1/SHA1HashFileAsyncTask 的完全限定路径。

    要查找方法,您需要 JNIEnv 和类的实例: jmethodID midSet = callEnv->GetMethodID(callClass, "setFileSize", "(J)V");第一个参数是类的实例,第二个是方法的名称,第三个是方法的签名。
    您可以使用来自 JDK 的给定二进制 javap.exe 获得的签名。只需使用类 f.e. 的完全限定路径调用它。 javap -s de.dhbw.file.sha1.SHA1HashFileAsyncTask .
    你会得到这样的结果:
    Compiled from "SHA1HashFileAsyncTask.java"
    public class de.dhbw.file.sha1.SHA1HashFileAsyncTask extends android.os.AsyncTas
    k<java.lang.String, java.lang.Integer, java.lang.String> {
    [...]
    static {};
    Signature: ()V

    public de.dhbw.file.sha1.SHA1HashFileAsyncTask(android.content.Context, de.dhb
    w.file.sha1.SHA1HashFileAsyncTask$SHA1AsyncTaskListener);
    Signature: (Landroid/content/Context;Lde/dhbw/file/sha1/SHA1HashFileAsyncTas
    k$SHA1AsyncTaskListener;)V

    protected native void calcFileSha1(java.lang.String);
    Signature: (Ljava/lang/String;)V

    protected native int getProgress();
    Signature: ()I

    protected native void unlockMutex();
    Signature: ()V

    protected native java.lang.String getHash();
    Signature: ()Ljava/lang/String;

    [...]

    public void setFileSize(long);
    Signature: (J)V

    [...]
    }

    如果找到该方法,则该变量不等于 0。
    调用该方法非常简单:
    callEnv->CallVoidMethod(callObj, midSet, size);

    第一个参数是来自“main”方法的给定作业,我认为其他参数很清楚。

    请记住,虽然类的私有(private)方法,但您可以从 native 代码调用,因为 native 代码是其中的一部分!

    字符串
    将使用以下代码转换给定的字符串:
    jboolean jbol;
    const char *fileName = env->GetStringUTFChars(file, &jbol);

    另一种方式:
    TCHAR* szReport = new TCHAR;
    jstring result = callEnv->NewStringUTF(szReport);

    可以是每个 char*多变的。

    异常(exception)
    可以用 JNIEnv 抛出:
    callEnv->ThrowNew(callEnv->FindClass("java/lang/Exception"), 
    "Hash generation failed");

    您还可以检查 JNIEnv 是否也发生了异常:
    if (callEnv->ExceptionOccurred()) {
    callEnv->ExceptionDescribe();
    callEnv->ExceptionClear();
    }

    规范
  • Java Native Interface Specifications

  • 构建/清理

    构建
    在我们创建了所有文件并用内容填充它们之后,我们可以构建它。
    打开 cygwin,导航到项目根目录并从那里执行 ndk-build,它位于 NDK 根目录中。
    这将开始编译,如果成功,您将得到如下输出:
    $ /cygdrive/d/android-ndk-r5c/ndk-build
    Compile++ thumb : SHA1Calc <= SHA1Calc.cpp
    SharedLibrary : libSHA1Calc.so
    Install : libSHA1Calc.so => libs/armeabi/libSHA1Calc.so

    如果有任何错误,您将获得编译器的典型输出。

    清洁
    打开cygwin,切换到你的Android项目并执行命令 /cygdrive/d/android-ndk-r5c/ndk-build clean .

    构建 apk
    构建 native 库后,您可以构建项目。我发现很干净,使用 eclipse 功能是有利的 清洁项目 .

    调试
    java代码的调试和以前没有什么不同。
    C++代码的调试会在下一次进行。

    关于java - Android 中 SHA1 哈希实现的问题,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/6350657/

    26 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com