[英]Issues with SHA1 hash implementation in Android
我有两个用于计算 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 一起工作的小说明:
cd /cygdrive/d
导航到带有字母D的驱动器。./ndk-build
执行文件ndk-build 。 应该会出现错误,例如Android NDK: Could not find application project directory !
. 在我们开始项目之前,先搜索 hash 算法的 C/C++ 实现。 我从这个站点CSHA1 获取了代码。
您应该根据您的要求编辑源代码。
现在我们可以从 JNI 开始。
在 Android 项目中创建一个名为jni的文件夹。 它还包含所有本机源文件和Android.mk (稍后会详细介绍该文件)。
将下载(和编辑)的源文件复制到该文件夹中。
我的 java package 被称为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();
// [...]
}
本机代码(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]
。
第一个到 arguments 总是给出,但是根据应用你应该区分:
func(JNIEnv * env, jobject jobj)
-> JNI 调用是一个实例方法func(JNIEnv * env, jclass jclazz)
-> JNI 调用是一个 static 方法方法calcFileSha1(...)
的 header :
JNIEXPORT void JNICALL Java_de_dhbw_file_sha1_SHA1HashFileAsyncTask_calcFileSha1(JNIEnv * env, jobject jobj, jstring file)
JDK 提供二进制javah.exe ,它为本地代码生成 header 文件。 用法很简单,用完全合格的class调用即可:
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
了!
Class 及方法
要获取 class 实例,您可以使用jclass clz = callEnv->FindClass(CALL_CLASS);
. 在这种情况下, CALL_CLASS
是 class de/dhbw/file/sha1/SHA1HashFileAsyncTask的完整限定路径。
要找到需要JNIEnv和 class 实例的方法:
jmethodID midSet = callEnv->GetMethodID(callClass, "setFileSize", "(J)V");
第一个参数是 class 的实例,第二个是方法的名称,第三个是方法的签名。
您可以使用从 JDK 给定的二进制javap.exe获得的签名。 只需使用 class 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);
第一个参数是来自“主要”方法的给定作业,我认为其他参数很清楚。
请记住,尽管 class 的私有方法,您可以从本机代码调用,因为本机代码是它的一部分!
字符串
给定的字符串将使用以下代码进行转换:
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();
}
规格
构建/清理
建造
在我们创建了所有文件并用内容填充它们之后,我们可以构建它。
打开 cygwin,导航到项目根目录并从那里执行ndk-build ,它位于 NDK 根目录中。
这开始编译,如果成功,你将得到一个 output 像这样:
$ /cygdrive/d/android-ndk-r5c/ndk-build
Compile++ thumb : SHA1Calc <= SHA1Calc.cpp
SharedLibrary : libSHA1Calc.so
Install : libSHA1Calc.so => libs/armeabi/libSHA1Calc.so
如果有任何错误,您将从编译器中得到典型的 output。
干净的
打开 cygwin,切换到您的 Android 项目并执行命令/cygdrive/d/android-ndk-r5c/ndk-build clean
。
构建APK
构建本机库后,您可以构建项目。 我发现干净,使用 eclipse 功能清洁项目是有利的。
调试
java 代码的调试和以前没有什么不同。
c++代码的调试将在下一次进行。
做这个:
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();
性能来自按块处理数据。 像这里一样,一个 8 kB 的缓冲区应该足够块状。 您不必使用BufferedInputStream
,因为 8 kB 缓冲区也可用作 I/O 缓冲区。
快速的原因是快速且不正确的(我认为)它没有散列文件内容!
FileInputStream fis = new FileInputStream("C:/Users/Ich/Downloads/srware_iron.exe");
ByteArrayInputStream byteArrayInputStream =
new ByteArrayInputStream(fis.toString().getBytes());
fis.toString()
调用不会读取文件的内容。 相反,它为您提供了一个(我怀疑)看起来像这样的字符串:
"java.io.FileInputStream@xxxxxxxx"
然后您将继续计算 SHA1 hash 。 FileInputStream
及其超类不会覆盖Object::toString
...
将 InputStream 的全部内容读取到byte[]
的简单方法是使用 Apache Commons I/O 辅助方法 - IOUtils.toByteArray(InputStream)
。
public void computeSHAHash(String path)// path to your file
{
String SHAHash = null;
try
{
MessageDigest md = MessageDigest.getInstance("SHA1");
InputStream in = new FileInputStream(path);
byte[] buf = new byte[8192];
int len = -1;
while((len = in.read(buf)) > 0)
{
md.update(buf, 0, len);
}
in.close();
byte[] data = md.digest();
try
{
SHAHash = convertToHex(data);
}
catch (IOException e)
{
// TODO Auto-generated catch block
e.printStackTrace();
}
} catch (NoSuchAlgorithmException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (FileNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
Toast.makeToast(getApplicationContext(),"Generated Hash ="+SHAHash,Toast.LENGTH_SHORT).show();
}
private static String convertToHex(byte[] data) throws java.io.IOException
{
StringBuffer sb = new StringBuffer();
String hex = null;
hex = Base64.encodeToString(data, 0, data.length, NO_OPTIONS);
sb.append(hex);
return sb.toString();
}
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.