I have two small snippets for calculating SHA1.
One is very fast but it seems that it isn't correct and the other is very slow but correct.
I think the FileInputStream
conversion to ByteArrayInputStream
is the problem.
Fast version:
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();
Slow version:
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();
Conversion method:
private static String byteArray2Hex(byte[] hash) {
Formatter formatter = new Formatter();
for (byte b : hash) {
formatter.format("%02x", b);
}
return formatter.toString();
}
I hope there is another possibility to get it running because I need the performance.
I used a high performance c++ implementation which I load with JNI.
For more details write a comment, please.
EDIT:
Requirements for JNI is the Android NDK. For Windows is needed in addition cygwin or something similar.
If you decided for cygwin, I give you some little instructions how to get it working with the NDK:
cd /cygdrive/d
navigates to the drive with the letter D../ndk-build
. There should be an error occurs likeAndroid NDK: Could not find application project directory !
.You have to navigate in an Android project to execute the command. So let's start with a project.
Before we can start with the project search for a C/C++ implementation of the hash algorithm. I took the code from this site CSHA1.
You should edit the source code for your requirements.
Now we can start with JNI.
You create a folder called jni in your Android project. It contains all native source files and the Android.mk (more about that file later), too.
Copy your downloaded (and edited) source files in that folder.
My java package is called de.dhbw.file.sha1, so I named my source files similar to find them easily.
Android.mk:
Java code:
I used the AsyncTask with a ProgressDialog to give the user some feedback about the action.
Native code (C++):
Remember accessing variables inside native code or other way around using threads needs synchronizing or you will get a segmentation fault soon!
For JNI usage you have to add
#include <jni.h>
.For logging insert following include
#include <android/log.h>
.Now you can log with
__android_log_print(ANDROID_LOG_DEBUG, DEBUG_TAG, "Version [%s]", "19");
.The first argument is the type of message and the second the causing library.
You can see I had a version number in my code. It is very helpful because sometimes the apk builder doesn't use the new native libraries. Troubleshooting can be extremely shortened, if the wrong version is online.
The naming conventions in the native code are a little bit crasier:
Java_[package name]_[class name]_[method name]
.The first to arguments are always given, but depending on the application you should distinguish:
func(JNIEnv * env, jobject jobj)
-> JNI call is an instance methodfunc(JNIEnv * env, jclass jclazz)
-> JNI call is a static methodThe header for the method
calcFileSha1(...)
:JNIEXPORT void JNICALL Java_de_dhbw_file_sha1_SHA1HashFileAsyncTask_calcFileSha1(JNIEnv * env, jobject jobj, jstring file)
The JDK delivers the binary javah.exe, which generates the header file for the native code. The usage is very simple, simply call it with the full qualified class:
javah de.dhbw.file.sha1.SHA1HashFileAsyncTask
In my case I have to give the bootclasspath additionally, because I use Android classes:
javah -bootclasspath <path_to_the_used_android_api> de.dhbw.file.sha1.SHA1HashFileAsyncTask
That would be the generated file:
You can change the file without further notice. But do not use
javah
again!Class and methods
To get a class instance you can use
jclass clz = callEnv->FindClass(CALL_CLASS);
. In this case isCALL_CLASS
the full qualified path to the class de/dhbw/file/sha1/SHA1HashFileAsyncTask.To find a method you need the JNIEnv and an instance of the class:
jmethodID midSet = callEnv->GetMethodID(callClass, "setFileSize", "(J)V");
The first argument is the instance of the class, the second the name of the method and the third is the signature of the method.The signature you can get with the from JDK given binary javap.exe. Simply call it with the full qualified path of the class f.e.
javap -s de.dhbw.file.sha1.SHA1HashFileAsyncTask
.You will get an result like:
If the method is found the variable is not equal 0.
Calling the method is very easy:
The first argument is the given jobject from the "main" method and I think the others are clear.
Remember that you can call from native code although private methods of the class, because the native code is part of it!
Strings
The given string would be converted with following code:
And the other way:
It can be every
char*
variable.Exceptions
Can be thrown with the JNIEnv:
You can also check if there is an exception occurred also with JNIEnv:
Specifications
Build/Clean
Build
After we have created all files and filled them with content, we can build it.
Open cygwin, navigate to the project root and execute from there the ndk-build, which is in the NDK root.
This start the compile, if it is success you will get an output like that:
If there is any error, you will get the typical output from the compiler.
Clean
Open cygwin, switch in your Android project and execute the command
/cygdrive/d/android-ndk-r5c/ndk-build clean
.Build apk
After you have build the native libraries you can build your project. I've found clean, it is advantageous to use the eclipse feature clean project.
Debugging
Debugging of java code isn't different as before.
The debugging of c++ code will follow in the next time.
The reason the fast one is fast is (I think) that your code is not hashing the file contents!
The
fis.toString()
call does not read the contents of the file. Rather it gives you a string that (I suspect) looks something like this:which you are then proceeding to calculate the SHA1 hash for.
The simple way to read the entire contents of an InputStream to a
byte[]
is to use an Apache Commons I/O helper method -IOUtils.toByteArray(InputStream)
.Do this:
Performance comes from handling data by blocks. An 8 kB buffer, as here, ought to be blocky enough. You do not have to use a
BufferedInputStream
since the 8 kB buffer also serves as I/O buffer.