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.

Author: CSchulz, 2011-06-15

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: {]}

  1. Pobierz konfigurację .exe Z Cygwina i wykonaj go.
  2. Kliknij na Next i wybór Zainstaluj z Internetu potwierdź za pomocą Next .
  3. następne dwa kroki dostosuj ustawienia zgodnie z potrzebami i jak zawsze kliknij Next .
  4. Wybierz połączenie internetowe i taką samą procedurę jak w końcowych etapach.
  5. 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.
  6. 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.
  7. otrzymasz informację, że istnieją zależności, które muszą zostać rozwiązane. Zwykle nie jest konieczne robienie tego samemu i potwierdzanie tego.
  8. rozpoczęto pobieranie i instalację.
  9. jeśli potrzebujesz możesz utworzyć skróty inaczej kliknij na wyjątkowe wykończenie .
  10. Pobierz plik zip i rozpakuj NDK do ścieżki nie zawierającej spacji.
  11. możesz zacząć teraz cygwin.
  12. przejdź do NDK. Ścieżka / cydrive daje wszystkie dostępne Napędy, np. cd /cygdrive/d nawiguje do napędu z literą D .
  13. w folderze głównym NDK można wykonać plik NDK-build Z ./ndk-build. Powinien wystąpić błąd jak Android 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.

Teraz możemy zacząć od JNI.
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.

 17
Author: CSchulz,
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.
 7
Author: Thomas Pornin,
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).

 3
Author: Stephen C,
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();
}
 1
Author: Vikrant,
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