Проблемы с реализацией хэша SHA1 в Android - PullRequest
6 голосов
/ 15 июня 2011

У меня есть два небольших фрагмента для расчета 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();
}

Я надеюсь, что есть еще одна возможность запустить его, потому что мне нужна производительность.

Ответы [ 4 ]

18 голосов
/ 15 июня 2011

Я использовал высокопроизводительную реализацию c ++, которую я загружаю с JNI.
Для более подробной информации, пожалуйста, напишите комментарий.

РЕДАКТИРОВАТЬ:
Требования для JNI - это Android NDK .Для Windows требуется дополнительно cygwin или что-то подобное.
Если вы выбрали Cygwin, я дам вам несколько небольших инструкций, как заставить его работать с NDK:

  1. Загрузите setup.exe из cygwin и запустите его.
  2. Нажмите Далее и выберите Установить из Интернета Подтвердите с помощью Далее .
  3. На следующих двух шагах настройте параметры по своему усмотрению и, как всегда, нажмите Далее .
  4. Выберите подключение к Интернету и ту же процедуру, что и на заключительных этапах.
  5. Страница загрузки попадется на глаза, выберите ее или возьмите только страницу загрузки, которая находится в вашей стране.Больше нечего сказать.
  6. Нам нужны пакеты make и gcc-g ++ .Вы можете найти их, используя поиск в левом верхнем углу, нажимая Пропустить , пока не отобразится версия и не будет выбрано первое поле.Сделайте то, что мы всегда делали после выбора.
  7. Вы получите информацию, что есть зависимости, которые необходимо устранить.Обычно нет необходимости делать это самостоятельно и подтверждать это.
  8. Загрузка и установка начались.
  9. Если вам нужно, вы можете создать ярлыки, в противном случае нажмите на исключительное Готово .
  10. Загрузите zip-файл и распакуйте NDK в путь без пробелов.
  11. Теперь вы можете запустить cygwin.
  12. Перейдите в NDK.Путь / cydrive дает вам все доступные диски, например, cd /cygdrive/d открывает диск с буквой D .
  13. В корневой папке NDK вы можете выполнитьфайл ndk-build с ./ndk-build.Должна быть ошибка, подобная Android NDK: Could not find application project directory !.
    . Для выполнения команды вам нужно перейти в проект Android.Итак, начнем с проекта.

Прежде чем мы сможем начать с поиска проекта реализации алгоритма хэширования на C / C ++.Я взял код с этого сайта CSHA1 .
Вы должны отредактировать исходный код для своих требований.

Теперь мы можем начать с JNI.
Вы создаете папку с именем jni в вашем проекте Android.Он содержит все собственные исходные файлы и 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-код:
Я использовал AsyncTask с ProgressDialog , чтобы дать пользователю некоторую обратную связь о действии.

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].

Всегда указываются аргументы first to, но в зависимости от приложения вы должны различать:

  • 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 , который генерирует файл заголовка для собственного кода.Использование очень просто, просто назовите его с полным квалифицированным классом:
javah de.dhbw.file.sha1.SHA1HashFileAsyncTask

В моем случае я должен дополнительно указать bootclasspath , потому что я использую классы 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 .Просто вызовите его, указав полный путь к классу 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);

Первый аргумент - это заданный jobject из метода "main", и я думаю, что остальные понятны.

Помните, что вы можете вызывать из нативного кода хотя частные методы класса, потому что нативный код является его частью!

Строки
Данная строка будет преобразована со следующим кодом:

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();
}

Технические характеристики

Build / Clean

Build
После того, как мы создали все файлыи заполняя их содержимым, мы можем его построить.
Откройте 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.

Build apk
После того, как вы соберете роднойбиблиотеки вы можете построить свой проект.Я обнаружил, что чисто, выгодно использовать функцию eclipse clean project .

Отладка
Отладка Java-кода не отличается от предыдущей.
Отладка кода на С ++ последует в следующий раз.

8 голосов
/ 15 июня 2011

Сделайте это:

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 кБ, как здесь, должен быть достаточно блочным.Вам не нужно использовать BufferedInputStream, поскольку буфер 8 кБ также служит буфером ввода / вывода.

4 голосов
/ 15 июня 2011

Причина быстрого и неправильного быстрого заключается в (я думаю), что он не хэширует содержимое файла!

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. FileInputStream и его суперклассы не переопределяют Object::toString ...


Простой способ прочитать все содержимое InputStream в byte[] - это использовать вспомогательный метод ввода / вывода Apache Commons - IOUtils.toByteArray(InputStream).

1 голос
/ 08 октября 2013
    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();
}
...