Android中SHA1哈希实现的问题

CSc*_*ulz 6 java hash android sha1 stream

我有两个用于计算SHA1的小片段.

一个是非常快但似乎它是不正确的,另一个是非常缓慢但正确的.
我认为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();
Run Code Online (Sandbox Code Playgroud)

慢版:

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();
Run Code Online (Sandbox Code Playgroud)

转换方式:

private static String byteArray2Hex(byte[] hash) {
    Formatter formatter = new Formatter();
    for (byte b : hash) {
        formatter.format("%02x", b);
    }
    return formatter.toString();
}
Run Code Online (Sandbox Code Playgroud)

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

CSc*_*ulz 18

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

编辑:
JNI的要求是Android NDK.对于Windows,还需要cygwin或类似的东西.
如果您决定使用cygwin,我会给您一些小指示,告诉您如何使用NDK:

  1. 从cygwin 下载setup.exe并执行它.
  2. 单击下一步,然后选择从Internet确认使用下一步确认.
  3. 接下来的两个步骤会根据需要调整设置,并始终单击" 下一步".
  4. 选择您的互联网连接和与最终阶段相同的过程.
  5. 下载页面将吸引眼球选择它或只是一个下载页面,这是在您的国家.没有什么可说的了.
  6. 我们需要包makegcc-g ++.您可以使用左上角的搜索找到它们,单击Skip直到显示版本并选择第一个字段.这是我们在选择后一直做的事情.
  7. 您将获得必须解决的依赖关系信息.通常没有必要自己做并确认它.
  8. 下载和安装已开始.
  9. 如果需要,您可以创建快捷方式,否则单击特殊完成.
  10. 下载zip文件并将NDK解压缩到包含路径的非空格.
  11. 你现在可以从cygwin开始了.
  12. 导航到NDK.路径/ cydrive为您提供所有可用的驱动器fe cd /cygdrive/d导航到带有字母D的驱动器.
  13. 在NDK的根文件夹,您可以执行该文件NDK,建立./ndk-build.应该会出现错误Android NDK: Could not find application project directory !.
    您必须在Android项目中导航才能执行该命令.那么让我们从一个项目开始吧.

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

现在我们可以从JNI开始.
您在Android项目中创建一个名为jni的文件夹.它包含所有本机源文件和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)
Run Code Online (Sandbox Code Playgroud)

Java代码:
我使用带有ProgressDialogAsyncTask为用户提供有关操作的一些反馈.

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();

    // [...]
}
Run Code Online (Sandbox Code Playgroud)

本机代码(C++):

记得在本机代码中访问变量或以其他方式使用线程需要同步,否则很快就会出现分段错误!

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

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

本机代码中的命名约定有点夸张: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,它生成本机代码的头文件.用法非常简单,只需使用完全限定的类调用它:
javah de.dhbw.file.sha1.SHA1HashFileAsyncTask

在我的情况下,我必须另外给bootclasspath,因为我使用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
Run Code Online (Sandbox Code Playgroud)

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

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

要找到一个方法,您需要JNIEnv和类的实例:
jmethodID midSet = callEnv->GetMethodID(callClass, "setFileSize", "(J)V"); 第一个参数是类的实例,第二个参数是方法的名称,第三个参数是方法的签名.
您可以从JDK给出的二进制javap.exe获得签名.只需使用类fe的完整限定路径调用它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

  [...]
}
Run Code Online (Sandbox Code Playgroud)

如果找到方法,则变量不等于0.
调用方法非常简单:

callEnv->CallVoidMethod(callObj, midSet, size);
Run Code Online (Sandbox Code Playgroud)

第一个参数是来自"main"方法的给定jobject,我认为其他的是明确的.

请记住,您可以通过本机代码调用该类的私有方法,因为本机代码是其中的一部分!

字符串
给定的字符串将使用以下代码进行转换:

jboolean jbol;
const char *fileName = env->GetStringUTFChars(file, &jbol);
Run Code Online (Sandbox Code Playgroud)

另一种方式:

TCHAR* szReport = new TCHAR;
jstring result = callEnv->NewStringUTF(szReport);
Run Code Online (Sandbox Code Playgroud)

它可以是每个char*变量.

例外情况
可与抛出的JNIEnv:

callEnv->ThrowNew(callEnv->FindClass("java/lang/Exception"), 
    "Hash generation failed");
Run Code Online (Sandbox Code Playgroud)

您还可以检查JNIEnv是否也发生了异常:

if (callEnv->ExceptionOccurred()) {
    callEnv->ExceptionDescribe();
    callEnv->ExceptionClear();
}
Run Code Online (Sandbox Code Playgroud)

产品规格

构建/清洁

构建
在我们创建了所有文件并用内容填充之后,我们可以构建它.
打开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
Run Code Online (Sandbox Code Playgroud)

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

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

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

调试
java代码的调试与以前没有什么不同.
c ++代码的调试将在下次进行.


Tho*_*nin 8

做这个:

MessageDigest md = MessageDigest.getInstance("SHA1");
InputStream in = new FileInputStream("hereyourinputfilename");
byte[] buf = new byte[8192];
for (;;) {
    int len = in.read(buf);
    if (len < 0)
        break;
    md.update(buf, 0, len);
}
in.close();
byte[] hash = md.digest();
Run Code Online (Sandbox Code Playgroud)

性能来自于按块处理数据.这里的8 kB缓冲区应该足够块.您不必使用a,BufferedInputStream因为8 kB缓冲区也可用作I/O缓冲区.