Problemy z implementacją skrótu SHA1 w systemie Android
Mam dwa małe fragmenty do obliczenia SHA1.
Jeden jest bardzo szybki, ale wydaje się, że nie jest poprawny, a drugi jest bardzo powolny, ale poprawny.
Myślę, że konwersja FileInputStream
na ByteArrayInputStream
jest problemem.
Szybka wersja:
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();
Wersja Slow:
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();
Metoda konwersji:
private static String byteArray2Hex(byte[] hash) {
Formatter formatter = new Formatter();
for (byte b : hash) {
formatter.format("%02x", b);
}
return formatter.toString();
}
Mam nadzieję, że jest inna możliwość, aby go uruchomić, ponieważ potrzebuję wydajności.
4 answers
Użyłem wysokowydajnej implementacji c++, którą załadowałem za pomocą JNI.
Aby uzyskać więcej szczegółów napisz komentarz, proszę.
EDIT:
Wymagania dla JNI to Android NDK . Dla Windows potrzebny jest dodatkowo cygwin lub coś podobnego.
Jeśli zdecydowałeś się na Cygwina, dam ci kilka instrukcji, jak uruchomić go z NDK: {]}
- Pobierz konfigurację .exe Z Cygwina i wykonaj go.
- Kliknij na Next i wybór Zainstaluj z Internetu potwierdź za pomocą Next .
- następne dwa kroki dostosuj ustawienia zgodnie z potrzebami i jak zawsze kliknij Next .
- Wybierz połączenie internetowe i taką samą procedurę jak w końcowych etapach.
- strona pobierania przyciągnie wzrok wybierz ją lub weź tylko stronę pobierania, która znajduje się w Twoim kraju. Nie ma nic więcej do powiedzenia.
- potrzebujemy pakietów make i gcc-g++. Możesz je znaleźć za pomocą wyszukiwania w lewym górnym rogu, kliknij na Pomiń, aż wyświetli się wersja i zostanie wybrane pierwsze pole. Zrób to, co zawsze robiliśmy po selekcji.
- otrzymasz informację, że istnieją zależności, które muszą zostać rozwiązane. Zwykle nie jest konieczne robienie tego samemu i potwierdzanie tego.
- rozpoczęto pobieranie i instalację.
- jeśli potrzebujesz możesz utworzyć skróty inaczej kliknij na wyjątkowe wykończenie .
- Pobierz plik zip i rozpakuj NDK do ścieżki nie zawierającej spacji.
- możesz zacząć teraz cygwin.
- przejdź do NDK. Ścieżka / cydrive daje wszystkie dostępne Napędy, np.
cd /cygdrive/d
nawiguje do napędu z literą D . - w folderze głównym NDK można wykonać plik NDK-build Z
./ndk-build
. Powinien wystąpić błąd jakAndroid NDK: Could not find application project directory !
.
Musisz nawigować w Projekt Android do wykonania polecenia. Zacznijmy więc od projektu.
Zanim zaczniemy od projektu poszukajmy implementacji algorytmu hash w C / C++. Wziąłem kod z tej strony CSHA1 .
Powinieneś edytować kod źródłowy zgodnie z Twoimi wymaganiami.
Tworzysz folder o nazwie jni w swoim projekcie Android. Zawiera wszystkie natywne pliki źródłowe i Android.mk (więcej o tym pliku później).
Skopiuj pobrane (i edytowane) pliki źródłowe w tym folderze.
Mój pakiet Javy nazywa się de.dhbw.plik.sha1 , więc nazwałem moje pliki źródłowe podobnie, aby łatwo je znaleźć.
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)
Kod Java:
Użyłem AsyncTask Z ProgressDialog , aby dać użytkownikowi kilka opinii na temat akcji.
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();
// [...]
}
Kod natywny (C++):
Pamiętaj dostęp do zmiennych w kodzie natywnym lub w inny sposób przy użyciu wątków wymaga synchronizacji lub wkrótce pojawi się błąd segmentacji!
Aby używać JNI należy dodać #include <jni.h>
.
Do logowania wstawić następujący include #include <android/log.h>
.
Teraz możesz zalogować się za pomocą __android_log_print(ANDROID_LOG_DEBUG, DEBUG_TAG, "Version [%s]", "19");
.
Pierwszym argumentem jest typ wiadomości, a drugim biblioteka powodująca.
Widać, że miałem numer wersji w kodzie. Jest to bardzo pomocne, ponieważ czasami konstruktor apk nie używa nowego rodzimych bibliotek. Rozwiązywanie problemów można bardzo skrócić, jeśli zła wersja jest online.
Konwencje nazewnictwa w kodzie natywnym są nieco bardziej prymitywne: Java_[package name]_[class name]_[method name]
.
Pierwsze argumenty są zawsze podane, ale w zależności od zastosowania należy wyróżnić:
-
func(JNIEnv * env, jobject jobj)
- > wywołanie JNI jest metodą instancji -
func(JNIEnv * env, jclass jclazz)
- > wywołanie JNI jest metodą statyczną
Nagłówek metody calcFileSha1(...)
:JNIEXPORT void JNICALL Java_de_dhbw_file_sha1_SHA1HashFileAsyncTask_calcFileSha1(JNIEnv * env, jobject jobj, jstring file)
JDK dostarcza binarny javah.exe, który generuje plik nagłówkowy dla kodu natywnego. Użycie jest bardzo proste, wystarczy zadzwonić z pełną kwalifikowaną klasą:javah de.dhbw.file.sha1.SHA1HashFileAsyncTask
W moim przypadku muszę dać bootclasspath dodatkowo, ponieważ używam klas Androida:
javah -bootclasspath <path_to_the_used_android_api> de.dhbw.file.sha1.SHA1HashFileAsyncTask
To byłby wygenerowany plik:
/* 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
Możesz zmienić plik bez uprzedzenia. Ale nie używaj javah
ponownie!
klasa i metody
Aby uzyskać instancję klasy, możesz użyć jclass clz = callEnv->FindClass(CALL_CLASS);
. W tym przypadku jest CALL_CLASS
pełna ścieżka do klasy de/dhbw/file/sha1/SHA1HashFileAsyncTask.
Aby znaleźć metodę potrzebujesz JNIEnv I instancji klasy: jmethodID midSet = callEnv->GetMethodID(callClass, "setFileSize", "(J)V");
Pierwszy argument jest instancją klasy, drugi nazwą metody, a trzeci podpisem metody.
Podpis można uzyskać z JDK podane binarne javap.exe . Po prostu wywołaj go z pełną kwalifikowaną ścieżką klasy f. E. javap -s de.dhbw.file.sha1.SHA1HashFileAsyncTask
.
Otrzymasz wynik:
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
[...]
}
Jeśli metoda zostanie znaleziona, zmienna nie jest równa 0.
Wywołanie metody jest bardzo proste:
callEnv->CallVoidMethod(callObj, midSet, size);
Pierwszy argument to podany jobject Z metody "main" I myślę, że pozostałe są jasne.
Pamiętaj, że możesz wywoływać z kodu natywnego, chociaż prywatne metody klasy, ponieważ kod natywny jest częścią tego!
struny
Podany łańcuch zostanie skonwertowany następującym kodem:
jboolean jbol;
const char *fileName = env->GetStringUTFChars(file, &jbol);
I w drugą stronę:
TCHAR* szReport = new TCHAR;
jstring result = callEnv->NewStringUTF(szReport);
Może być każda char*
zmienna.
wyjątki
Można rzucać za pomocą JNIEnv:
callEnv->ThrowNew(callEnv->FindClass("java/lang/Exception"),
"Hash generation failed");
Możesz również sprawdzić, czy wystąpił wyjątek również z JNIEnv :
if (callEnv->ExceptionOccurred()) {
callEnv->ExceptionDescribe();
callEnv->ExceptionClear();
}
specyfikacje
Zbuduj/Wyczyść
Zbuduj
Po utworzeniu wszystkich plików i wypełnieniu ich treścią możemy je zbudować.
Otwórz cygwin, przejdź do katalogu głównego projektu i stamtąd wykonaj NDK-build , który znajduje się w katalogu głównym NDK.
To rozpocząć kompilację, jeśli jest to sukces można otrzymamy taki wynik:
$ /cygdrive/d/android-ndk-r5c/ndk-build
Compile++ thumb : SHA1Calc <= SHA1Calc.cpp
SharedLibrary : libSHA1Calc.so
Install : libSHA1Calc.so => libs/armeabi/libSHA1Calc.so
Jeśli wystąpi jakiś błąd, otrzymasz typowe wyjście z kompilatora.
czyste
Otwórz cygwin, przełącz się w projekcie Androida i wykonaj polecenie /cygdrive/d/android-ndk-r5c/ndk-build clean
.
Zbuduj apk
Po zbudowaniu natywnych bibliotek możesz zbudować swój projekt. Znalazłem clean, korzystne jest użycie funkcji eclipse clean projekt .
Debugowanie
Debugowanie kodu Javy nie różni się tak jak poprzednio.
Debugowanie kodu c++ nastąpi następnym razem.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-06-30 12:39:03
Zrób to:
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();
Wydajność wynika z obsługi danych za pomocą bloków. Bufor 8 kB, jak tutaj, powinien być wystarczająco blokowy. Nie musisz używać BufferedInputStream
, ponieważ bufor 8 kB służy również jako bufor We / Wy.Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-06-14 22:09:16
Powodem, dla którego szybki jest szybki, jest (myślę) to, że Twój kod nie jest mieszaniem zawartości pliku!
FileInputStream fis = new FileInputStream("C:/Users/Ich/Downloads/srware_iron.exe");
ByteArrayInputStream byteArrayInputStream =
new ByteArrayInputStream(fis.toString().getBytes());
Wywołanie fis.toString()
nie odczytuje zawartości pliku. Raczej daje ciąg, który (podejrzewam) wygląda mniej więcej tak:
"java.io.FileInputStream@xxxxxxxx"
Które następnie przechodzisz do obliczenia haszu SHA1.
Prosty sposób na odczytanie całej zawartości strumienia wejściowego do byte[]
jest użycie metody Apache Commons I / O helper - IOUtils.toByteArray(InputStream)
.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-06-14 23:46:54
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();
}
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-10-08 11:57:54