当前位置: 首页 > 面试题库 >

Android中SHA1哈希实现的问题

高溪叠
2023-03-14
问题内容

我有两个用于计算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();

慢版本:

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一起使用:

  1. 从cygwin 下载 setup.exe 并执行。
  2. 点击 下一步 ,并选择 从网络安装 确认有 下一步
  3. 接下来的两个步骤将根据需要调整设置,并一如既往地单击“ 下一步”
  4. 选择您的互联网连接,并按照与最后阶段相同的步骤进行操作。
  5. 选择一个下载页面会吸引您的注意,或者仅下载您所在国家/地区的下载页面。没什么可说的了。
  6. 我们需要软件包 makegcc-g ++ 。您可以使用左上角的搜索找到它们,单击“ 跳过 直到显示版本” ,然后选择第一个字段。完成选择后我们一直做的事情。
  7. 您将获得必须解决的依赖关系信息。通常无需自己进行确认。
  8. 开始下载和安装。
  9. 如果需要,您可以创建快捷方式,否则单击特殊的 Finish
  10. 下载该zip文件,然后将NDK解压缩到不包含路径的路径中。
  11. 您可以立即开始cygwin。
  12. 导航到NDK。路径 / cydrive 为您提供所有可用的驱动器,例如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)

Java代码:
我将 AsyncTaskProgressDialog结合使用, 以向用户提供有关该操作的一些反馈。

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

    // [...]
}

本机代码(C ++):

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

要使用JNI,您必须添加#include <jni.h>

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

本机代码中的命名约定有点麻烦:Java_[package name]_[class name]_[method name]

始终提供first到arguments,但是应根据应用程序进行区分:

  • 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

您可以更改文件而无需另行通知。 但是不要再使用javah

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

要查找方法,您需要 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

  [...]
}

如果找到该方法,则变量不等于0。
调用该方法非常容易:

callEnv->CallVoidMethod(callObj, midSet, size);

第一个参数是“ main”方法给定的 jobject ,我认为其他方法很明确。

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

字符串
给定的字符串将与下面的代码转换:

jboolean jbol;
const char *fileName = env->GetStringUTFChars(file, &jbol);

另一种方式:

TCHAR* szReport = new TCHAR;
jstring result = callEnv->NewStringUTF(szReport);

它可以是每个char*变量。

例外情况
可与抛出 的JNIEnv

callEnv->ThrowNew(callEnv->FindClass("java/lang/Exception"), 
    "Hash generation failed");

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

if (callEnv->ExceptionOccurred()) {
    callEnv->ExceptionDescribe();
    callEnv->ExceptionClear();
}

技术指标

  • Java本机接口规范

建立/清洁

生成
创建完所有文件并将其填充内容之后,就可以对其进行生成。
打开cygwin,导航到项目根目录,然后从NDK根目录中执行 ndk-build
这开始编译,如果成功,您将得到如下输出:

$ /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
在构建本机库之后,您可以构建您的项目。我发现干净了,使用eclipse功能 清理项目 是有利的。

调试
Java代码的调试与以前没有什么不同。
C ++代码的调试将在下一次进行。



 类似资料:
  • 问题内容: 我有一个简单的问题,当我想将SHA1哈希的结果存储在MySQL数据库中时发生: 我将散列结果存储在 VARCHAR 字段中多长时间? 问题答案: 我将使用可变长度的数据,但不使用固定长度的数据。由于SHA-1值 始终为 160位长,因此将仅在固定长度字段的长度上浪费一个额外的字节。 而且我也不会存储返回的值。因为每个字符只使用4位,因此需要160/4 = 40个字符。但是,如果每个字符

  • 我正在写一个Django应用程序,需要与现有的Java播放框架应用程序一起工作。Play应用程序使用PasswordHash.java来存储密码。它以冒号分隔的格式存储密码。每个哈希都存储为::。 例如,下面是密码“测试”的条目: 在这里,我们可以通过拆分字符串并找到: 迭代次数: 盐: PBKDF2哈希:。 我修改了Django的check_密码机制以与此格式兼容,但发现它认为密码不正确。我用了

  • 问题内容: 在Objective-C中,它看起来像这样: 我需要Swift这样的东西,可以吗? 请显示工作示例。 问题答案: 您的Objective-C代码(使用类别)可以直接转换为Swift(使用扩展名)。 首先,您必须创建一个“桥接头”并添加 然后: 这可以写得更短和更快速 Swift 2更新: 要返回以Base-64编码的字符串而不是十六进制编码的字符串,只需替换 与 Swift 3更新:

  • 问题内容: 我已经到处寻找答案,但是似乎只能找到可以满足您需求的软件。有人知道如何在python中执行此操作吗? 问题答案: 我写了一段python代码,根据 .torrent文件 中的内容验证 下载文件 的哈希值。假设您要检查下载是否损坏,则可能会发现此功能有用。 __ 您需要bencode包才能使用它。Bencode是.torrent文件中使用的序列化格式。它可以封送列表,字典,字符串和数字,

  • 我试图解决这个问题,我需要实现线性探测。 给定一个整数数组和一个哈希表大小。使用线性探测将数组元素填充到哈希表中以处理冲突。 例1: 例2: 您的任务: 您不需要读取输入或打印任何内容。 您的任务是完成函数linearProbing(),该函数将空哈希表(hash)、哈希表大小(hashSize)、整数数组arr[]及其大小N作为输入,并将数组arr[]的所有元素插入给定的哈希表中。 哈希表的空单

  • 好吧,这里都是我的问题。我正在尝试用MD5哈希加密字符串。下面是我试图哈希的字符串: 以下是预期输出: 下面是我在Android应用程序中得到的信息: 下面是我正在使用的代码: 在Android上运行这段代码时,我得到了上述意想不到的结果,但当我运行这段代码时,就像一个Java程序传递相同的字符串一样,我得到了预期的输出... 究竟是怎么回事?几天来,我一直被这件事难住,在网上搜寻线索。我已经尝试