草庐IT

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

coder 2023-11-28 原文

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

    有关java - Android 中 SHA1 哈希实现的问题的更多相关文章

    1. ruby - 在 64 位 Snow Leopard 上使用 rvm、postgres 9.0、ruby 1.9.2-p136 安装 pg gem 时出现问题 - 2

      我想为Heroku构建一个Rails3应用程序。他们使用Postgres作为他们的数据库,所以我通过MacPorts安装了postgres9.0。现在我需要一个postgresgem并且共识是出于性能原因你想要pggem。但是我对我得到的错误感到非常困惑当我尝试在rvm下通过geminstall安装pg时。我已经非常明确地指定了所有postgres目录的位置可以找到但仍然无法完成安装:$envARCHFLAGS='-archx86_64'geminstallpg--\--with-pg-config=/opt/local/var/db/postgresql90/defaultdb/po

    2. ruby - 通过 rvm 升级 ruby​​gems 的问题 - 2

      尝试通过RVM将RubyGems升级到版本1.8.10并出现此错误:$rvmrubygemslatestRemovingoldRubygemsfiles...Installingrubygems-1.8.10forruby-1.9.2-p180...ERROR:Errorrunning'GEM_PATH="/Users/foo/.rvm/gems/ruby-1.9.2-p180:/Users/foo/.rvm/gems/ruby-1.9.2-p180@global:/Users/foo/.rvm/gems/ruby-1.9.2-p180:/Users/foo/.rvm/gems/rub

    3. ruby - 通过 RVM (OSX Mountain Lion) 安装 Ruby 2.0.0-p247 时遇到问题 - 2

      我的最终目标是安装当前版本的RubyonRails。我在OSXMountainLion上运行。到目前为止,这是我的过程:已安装的RVM$\curl-Lhttps://get.rvm.io|bash-sstable检查已知(我假设已批准)安装$rvmlistknown我看到当前的稳定版本可用[ruby-]2.0.0[-p247]输入命令安装$rvminstall2.0.0-p247注意:我也试过这些安装命令$rvminstallruby-2.0.0-p247$rvminstallruby=2.0.0-p247我很快就无处可去了。结果:$rvminstall2.0.0-p247Search

    4. ruby - Fast-stemmer 安装问题 - 2

      由于fast-stemmer的问题,我很难安装我想要的任何ruby​​gem。我把我得到的错误放在下面。Buildingnativeextensions.Thiscouldtakeawhile...ERROR:Errorinstallingfast-stemmer:ERROR:Failedtobuildgemnativeextension./System/Library/Frameworks/Ruby.framework/Versions/2.0/usr/bin/rubyextconf.rbcreatingMakefilemake"DESTDIR="cleanmake"DESTDIR=

    5. java - 等价于 Java 中的 Ruby Hash - 2

      我真的很习惯使用Ruby编写以下代码:my_hash={}my_hash['test']=1Java中对应的数据结构是什么? 最佳答案 HashMapmap=newHashMap();map.put("test",1);我假设? 关于java-等价于Java中的RubyHash,我们在StackOverflow上找到一个类似的问题: https://stackoverflow.com/questions/22737685/

    6. ruby - 如果指定键的值在数组中相同,如何合并哈希 - 2

      我有一个这样的哈希数组:[{:foo=>2,:date=>Sat,01Sep2014},{:foo2=>2,:date=>Sat,02Sep2014},{:foo3=>3,:date=>Sat,01Sep2014},{:foo4=>4,:date=>Sat,03Sep2014},{:foo5=>5,:date=>Sat,02Sep2014}]如果:date相同,我想合并哈希值。我对上面数组的期望是:[{:foo=>2,:foo3=>3,:date=>Sat,01Sep2014},{:foo2=>2,:foo5=>5:date=>Sat,02Sep2014},{:foo4=>4,:dat

    7. ruby - 如何根据特征实现 FactoryGirl 的条件行为 - 2

      我有一个用户工厂。我希望默认情况下确认用户。但是鉴于unconfirmed特征,我不希望它们被确认。虽然我有一个基于实现细节而不是抽象的工作实现,但我想知道如何正确地做到这一点。factory:userdoafter(:create)do|user,evaluator|#unwantedimplementationdetailshereunlessFactoryGirl.factories[:user].defined_traits.map(&:name).include?(:unconfirmed)user.confirm!endendtrait:unconfirmeddoenden

    8. ruby - 如何在 Grape 中定义哈希数组? - 2

      我使用Ember作为我的前端和GrapeAPI来为我的API提供服务。前端发送类似:{"service"=>{"name"=>"Name","duration"=>"30","user"=>nil,"organization"=>"org","category"=>nil,"description"=>"description","disabled"=>true,"color"=>nil,"availabilities"=>[{"day"=>"Saturday","enabled"=>false,"timeSlots"=>[{"startAt"=>"09:00AM","endAt"=>

    9. ruby - 安装 Ruby 时遇到问题(无法下载资源 "readline--patch") - 2

      当我尝试安装Ruby时遇到此错误。我试过查看this和this但无济于事➜~brewinstallrubyWarning:YouareusingOSX10.12.Wedonotprovidesupportforthispre-releaseversion.Youmayencounterbuildfailuresorotherbreakages.Pleasecreatepull-requestsinsteadoffilingissues.==>Installingdependenciesforruby:readline,libyaml,makedepend==>Installingrub

    10. C# 到 Ruby sha1 base64 编码 - 2

      我正在尝试在Ruby中复制Convert.ToBase64String()行为。这是我的C#代码:varsha1=newSHA1CryptoServiceProvider();varpasswordBytes=Encoding.UTF8.GetBytes("password");varpasswordHash=sha1.ComputeHash(passwordBytes);returnConvert.ToBase64String(passwordHash);//returns"W6ph5Mm5Pz8GgiULbPgzG37mj9g="当我在Ruby中尝试同样的事情时,我得到了相同sha

    随机推荐