Как получить hash файла java
Привет, Хабр! Представляю вашему вниманию перевод статьи "Java Cryptography" автора Jakob Jenkov.
Данная публикация является переводом первой статьи Java Cryptography из серии статей для начинающих, желающих освоить основы криптографии в Java.
Оглавление:
В этой статье объясняются основы того, как пользоваться Java Cryptography API для выполнения различных задач в которых требуется безопасное шифрование.
В этой статье не объясняются основы криптографической теории. Вам придется посмотреть эту информацию где-нибудь еще.
Расширение криптографии Java
Java cryptography API предоставляется так называемым расширением Java Сryptography Extension(JCE). JCE уже давно является частью платформы Java. Изначально JCE был отделен от Java из-за того, что в США действовали экспортные ограничения на технологии шифрования. Поэтому самые стойкие алгоритмы шифрования не были включены в стандартную платформу Java. Эти более надежные алгоритмы шифрования можно применять, если ваша компания находится в США, но в остальных случаях придется применять более слабые алгоритмы или реализовывать свои собственные алгоритмы шифрования и подключать их к JCE.
С 2017 года правила экспорта алгоритмов шифрования в США были значительно ослаблены и в большей части мира можно пользоваться международными стандартами шифрования через Java JCE.
Архитектура криптографии Java
Java Cryptography Architecture (JCA) — название внутреннего дизайна API криптографии в Java. JCA структурирован вокруг нескольких основных классов и интерфейсов общего назначения. Реальная функциональность этих интерфейсов обеспечивается поставщиками. Таким образом, можно использовать класс Cipher (Шифр) для шифрования и расшифровки некоторых данных, но конкретная реализация шифра (алгоритм шифрования) зависит от конкретного используемого поставщика.
Также можно реализовать и подключить свои собственные провайдеры, но вы должны быть осторожны с этим. Правильно реализовать шифрование без дыр в безопасности сложно! Если вы не знаете, что делаете, вам, вероятно, лучше использовать встроенный поставщик Java или использовать надежного поставщика, такого как Bouncy Castle.
Основные классы и интерфейсы
API криптографии Java состоит из следующих пакетов Java:
- java.security
- java.security.cert
- java.security.spec
- java.security.interfaces
- javax.crypto
- javax.crypto.spec
- javax.crypto.interfaces
Основные классы и интерфейсы этих пакетов:
- Provider
- SecureRandom
- Cipher
- MessageDigest
- Signature
- Mac
- AlgorithmParameters
- AlgorithmParameterGenerator
- KeyFactory
- SecretKeyFactory
- KeyPairGenerator
- KeyGenerator
- KeyAgreement
- KeyStore
- CertificateFactory
- CertPathBuilder
- CertPathValidator
- CertStore
Provider (Поставщик криптографии)
Класс Provider (java.security.Provider) является центральным классом в Java crypto API. Для того чтобы использовать Java crypto API, вам нужно установить поставщика криптографии. Java SDK поставляется с собственным поставщиком криптографии. Если вы явно не установите поставщик криптографии, то будет использоваться поставщик по умолчанию. Однако этот поставщик криптографии может не поддерживать алгоритмы шифрования, которые вы хотите использовать. Поэтому вам, возможно, придется установить свой собственный поставщик криптографии.
Один из самых популярных поставщиков криптографии для Java crypto API называется Bouncy Castle. Вот пример, где в качестве поставщика криптографии устанавливается BouncyCastleProvider:
Cipher (Шифр)
Класс Cipher (javax.crypto.Cipher) представляет криптографический алгоритм. Шифр может использоваться как для шифрования, так и для расшифровки данных. Класс Cipher объясняется более подробно в следующих разделах, ниже будет его краткое описание.
Создание экземпляра класса шифр, который использует алгоритм шифрования AES для внутреннего использования:
Метод Cipher.getInstance(. ) принимает строку, определяющую, какой алгоритм шифрования использовать, а также некоторые другие параметры алгоритма.
В приведенном выше примере:
- AES — алгоритм шифрования
- CBC — это режим, в котором может работать алгоритм AES.
- PKCS5Padding — это то, как алгоритм AES должен обрабатывать последние байты данных для шифрования. Что именно это означает, ищите в руководстве по криптографии в целом, а не в этой статье.
Инициализация шифра
Перед использованием экземпляра шифра его необходимо инициализировать. Экземпляр шифра инициализируется вызывом метода init(). Метод init() принимает два параметра:
- Режим — Шифрование / Расшифровка
- Ключ
Первый параметр указывает, режим работы экземпляра шифр: шифровать или расшифровывать данные. Второй параметр указывает, какой ключ они используют для шифрования или расшифровки данных.
Обратите внимание, что способ создания ключа в этом примере небезопасен и не должен использоваться на практике. В этой статье в следующих разделах будет рассказано, как создавать ключи более безопасно.
Чтобы инициализировать экземпляр шифра для расшифровки данных, вы должны использовать Cipher.DECRYPT_MODE, например:
Шифрование или дешифрование данных
После инициализации шифра вы можете начать шифрование или расшифровку данных вызовом методов update() или doFinal(). Метод update() используется, если вы шифруете или расшифровываете фрагмент данных. Метод doFinal() вызывается, когда вы шифруете последний фрагмент данных или если блок данных, который вы передаете в doFinal(), является единичным набором данных для шифрования.
Пример шифрования данных с помощью метода doFinal():
Чтобы расшифровать данные, нужно передать зашифрованный текст(данные) в метод doFinal() или doUpdate().
Keys (Ключи)
Для шифрования или дешифрования данных вам нужен ключ. Существует два типа ключей в зависимости от того, какой тип алгоритма шифрования используется:
- Симметричные ключи
- Асимметричные ключи
Симметричные ключи используются для симметричных алгоритмов шифрования. Алгоритмы симметричного шифрования используют один и тот же ключ для шифрования и расшифровки.
Асимметричные ключи используются для алгоритмов асимметричного шифрования. Алгоритмы асимметричного шифрования используют один ключ для шифрования, а другой для дешифрования. Алгоритмы шифрования с открытым и закрытым ключом — примеры асимметричных алгоритмов шифрования.
Каким-то образом сторона, которая должна расшифровать данные, должна знать ключ, необходимый для дешифрования данных. Если дешифрующая сторона не является стороной шифрующей данные, эти две стороны должны договориться о ключе или обменяться ключом. Это называется обменом ключами.
Безопасность ключа
Генерация ключа
Чтобы сгенерировать случайные ключи шифрования вы можете использовать класс Java KeyGenerator. KeyGenerator будет более подробно описан в следующих главах, вот небольшой пример его использования здесь:
Полученный экземпляр SecretKey можно передать в метод Cipher.init(), например так:
Генерация пары ключей
Алгоритмы асимметричного шифрования используют пару ключей, состоящую из открытого ключа и закрытого ключа, для шифрования и дешифрования данных. Для создания асимметричной пары ключей вы можете использовать KeyPairGenerator (java.security.KeyPairGenerator). KeyPairGenerator будет более подробно описан в следующих главах, ниже простой пример использования Java KeyPairGenerator:
Хранилище ключей (Key Store)
Java KeyStore — это база данных, которая может содержать ключи. Java KeyStore представлен классом KeyStore (java.security.KeyStore). Хранилище ключей может содержать ключи следующих типов:
- Закрытые ключи (Private keys)
- Открытые ключи и сертификаты(Public keys + certificates)
- Секретные ключи (Secret keys)
Закрытый и открытый ключи используются в асимметричном шифровании. Открытый ключ может иметь связанный сертификат. Сертификат — это документ, удостоверяющий личность человека, организации или устройства, претендующего на владение открытым ключом.
Сертификат обычно имеет цифровую подпись проверяющей стороны в качестве доказательства.
Секретные ключи используются в симметричном шифровании.Класс KeyStore довольно сложный, поэтому он описан более подробно далее в отдельной главе по Java KeyStore.
Инструмент управления ключами (Keytool)
Java Keytool — это инструмент командной строки, который может работать с файлами Java KeyStore. Keytool может генерировать пары ключей в файл KeyStore, экспортировать сертификаты и импортировать сертификаты в KeyStore и некоторые другие функции. Keytool поставляется с установкой Java. Keytool более подробно описан далее в отдельной главе по Java Keytool.
Когда вы получаете зашифрованные данные от другой стороны, можете ли вы быть уверенными что никто не изменил зашифрованные данные по пути к вам?
Краткое введение в класс MessageDigest:
Пример вызова update() несколько раз с последующим вызовом digest():
Экземпляр Java Mac создается вызовом метода Mac.getInstance(), передавая в качестве параметра имя используемого алгоритма. Вот как это выглядит:
Прежде чем создать MAC из данных, вы должны инициализировать экземпляр Mac ключом. Вот пример инициализации экземпляра Mac ключом:
После инициализации экземпляра Mac вы можете вычислить MAC из данных, вызвав методы update() и doFinal(). Если у вас есть все данные для расчета MAC, вы можете сразу вызвать метод doFinal(). Вот как это выглядит:
Подпись (Signature)
Класс Signature (java.security.Signature) используется для цифровой подписи данных. Когда данные подписаны, цифровая подпись создается из этих данных. Таким образом, подпись отделена от данных.
Для создания экземпляра Signature, вызывается метод Signature.getInstance (. ):
Подпись данных
Чтобы подписать данные, вы должны инициализировать экземпляр подписи в режиме подписи вызывая метод initSign(. ), передавая закрытый ключ для подписи данных. Пример инициализации экземпляра подписи в режиме подписи:
После инициализации экземпляра подписи, его можно использовать для подписи данных. Это делается вызовом метода update(), передавая данные для подписи в качестве параметра. Можно вызывать метод update() несколько раз, чтобы дополнить данные для создании подписи. После передачи всех данных в метод update(), вызывается метод sign() для получения цифровой подписи. Вот как это выглядит:
Проверка подписи
Чтобы проверить подпись, нужно инициализировать экземпляр подписи в режиме проверки путем вызова метода initVerify(. ), передавая в качестве параметра открытый ключ, который используется для проверки подписи. Пример инициализации экземпляра подписи в режиме проверки выглядит:
После инициализации в режиме проверки в метод update() передаются данные, которые подписаны подписью. Вызов метода verify(), возвращает значение true или false в зависимости от того, можно ли проверить подпись или нет. Вот как выглядит проверка подписи:
Я ищу использовать Java для получения контрольной суммы MD5 файла. Я был действительно удивлен, но я не смог найти ничего, что показывает, как получить контрольную сумму MD5 файла.
Как это сделать?
Может быть, это поможет. Вы также можете посмотреть спецификации, но это займет больше времени, потому что это сложно. MD5 больше не считается криптографически безопасным, но его все же достаточно для проверки согласованности файлов и он быстрее, чем SHA. Каноническое использование контрольных сумм MD5 для файлов состоит в том, чтобы избежать враждебных замен распределенных файлов. Вот где это небезопасно. Но в сценарии, где враждебные подвиги не являются проблемой, это идеально подходит.Есть декоратор входного потока java.security.DigestInputStream , так что вы можете вычислить дайджест, используя входной поток, как обычно, вместо того, чтобы делать дополнительный проход по данным.
@AlPhaba Вы объявили is как InputStream или FileInputStream ? Похоже, вы использовали FileInputStream , что приведет к этой ошибке. @barwnikk Отлично работает в Java 8. MethodNotFound Не является исключением из стандартной Java; возможно вы говорите об ошибке компилятора? В любом случае, если это не работает для вас, это проблема локальной конфигурации или проблема с другим кодом. @barwnikk Опять же, это ваша локальная проблема конфигурации. Это действительный код Java 7 и Java 8. Если вы застряли с инструментами с 2006 года, вам придется адаптироваться. @erickson Вы не обновляете объект MessageDigest содержимым файла. Рт? Этот код будет печатать всегда один и тот же дайджест. @JPM Предположим, что вы уже загрузили и поместили в commons-codec.jar свой путь к классам? да, и я экспортировал в свой проект Android .. Я могу пройти через код и класс есть в исходных файлах . странно, должно быть, некоторые проблемы Android Eclipse. Ницца! Для новых проектов я всегда думаю дважды перед добавлением новой зависимости, но для существующего проекта мне просто нужно проверить, есть ли библиотека, чтобы использовать ее. +1В Java-How-to Real есть пример использования класса MessageDigest .
Посмотрите на этой странице примеры использования CRC32 и SHA-1.
Пример в Java-How-To от Real отлично работает и прост в реализации. Цикл чтения немного неуклюж. read() не вернет ноль, и do/while не очень подходит. byte [] buffer = новый байт [1024]; мы можем изменить размер с 1024 на что-то более оптимальное?Для вашего Files.hash() варианта использования вычисляет и возвращает значение дайджеста для файла.
Например, SHA-1 расчет дайджеста (измените SHA-1 на MD5, чтобы получить дайджест MD5)
Обратите внимание, что crc32 намного быстрее чем md5так что пользуйтесь crc32если вам не нужна криптографически безопасная контрольная сумма. Обратите внимание, чтоmd5 не следует использовать для хранения паролей и тому подобного, так как это просто для грубой силы, для использования паролей Bcrypt, Scrypt или SHA-256 вместо.
Для долгосрочной защиты с помощью хэшей схема подписи Merkle повышает безопасность, а Исследовательская группа по постквантовой криптографии, спонсируемая Европейской комиссией, рекомендовала использовать эту криптографию для долговременной защиты от квантовых компьютеров ( см. ).
Обратите внимание, что crc32 имеет более высокий уровень столкновений, чем другие.
Вам нужно зашифровать пароль либо другие данные, в таком случае можно использовать MD5 шифрование.
Для приведения примеров я создал класс MD5Util.java в котором реализую два метода генерации MD5.
Ниже приведен пример реализации:
Хеш содержит 128 бит (16 байт) поэтому мы в строке 17 указали 16 байтов, в строке 19 было указанно 32 так как обычно хеш 16 байтов представляется как последовательность из 32 шестнадцатеричных цифр.
Этот метод заключается в использовании готовой реализации MD5.
Для того чтобы использовать реализацию Apache Common Codec нужно подключить его к проекту. Если использовать Maven то нужно подключить к вашему проекту зависимость:
И ниже пример использования:
Использование
Вызов методов утильного класса MD5Util.java со строкой devcolibri:
ПОХОЖИЕ ПУБЛИКАЦИИ
21 комментариев к статье "MD5 пример использования в Java"
а почему не указал в обратную сторону , т.е. как расшифровать md5 например , если хранить пароли пользователей в базе
Потому что расшифровать md5 возможно, но это очень и очень трудоемкий процесс. Но если хранить пароль в БД и вы хотите сравнить их, то расшифровывать пароль не надо нужно просто второй не зашифрованный зашифровать и сравнить их.
На данный момент расшифровать MD5 уже совершенно не трудоемкий процесс, будет ли туториал по современным методам хеширования?
Ну тогда может продепонстрируете этот не трудоемкий процесс? :)
Хеш-сумму, полученную с помощью любого метода хеширования (не важно, MD5 это, или какой-нибудь SHA1) расшифровать невозможно, и не будет никогда возможно. Начнём с того, что MD5 это не шифрование вообще, а всего лишь хеш-сумма. Хеш можно взять с любого объёма данных. Представьте, у нас есть файл размером 50Гб, мы берём с него хеш-сумму и получаем 32-байтовую строку (в обычном MD5). Получается, что мы сжали данные объёмом в 53687091200 байт всего до 32. Вы сами-то в этом верите?
Есть радужные таблицы.
А я и не говорил что нужно хранить пароль, сам MD5 расшифровать трудоемко по ресурсам и времени.
Да, вероятно такие вопросы идут из-за не очень продуманного введения к статье. Практики много, теории ноль, что в итоге ведет к непониманию и оторванности решения от постановки задачи.
В статье же не сря метка NOTE стоит :) Это тип заметки, который не подразумевает детальное описание всего. Но вас я понял, учту)
Вычисление хэша от строки
Алгоритм хэширования MD5 является частью стандартной библиотеки Java, поэтому вычислить хэш от строки (например, от пароля) достаточно легко.
public String getMd5Hash(String source) <try <
var md = MessageDigest.getInstance( "MD5" );
md.update(source.getBytes());
byte [] digest = md.digest();
return bytesToHex(digest);
> catch (NoSuchAlgorithmException e) <
throw new RuntimeException(e);
>
>
Тут мы используем стандартный класс MessageDigest, который по имени алгоритма возвращает его реализацию. Если алгоритм по имени не найден, то метод getInstance() кидает исключение NoSuchAlgorithmException. Поскольку мы передаём в него константу «MD5», то точно знаем, что такой ошибки не возникнет. Поэтому обрачиваем это проверяемое исключение в RuntimeException, которое является непроверяемым, чтобы не «портить» сигнатуру метода.
Затем преобразуем исходную строку, от которой требуется посчитать хэш, в массив байт. Затем передаём этот массив в метод update(). В результате он возвращает хэш также в виде массива байт. Для удобства отображения массива байт в виде строки, напишем вспомогательный метод bytesToHex().
private String bytesToHex( byte [] bytes) <var builder = new StringBuilder();
for ( var b : bytes) <
builder.append(String.format( "%02x" , b & 0xff ));
>
return builder.toString();
>
Тут мы каждый байт преобразуем в строку, выполняя побитовое объединение этого байта с байтом 0xff. Во избежания создания лишних экземпляров строк, конкатенацию результата делаем с помощью StringBuilder, который в конце преобразуем в строку.
Байты в виде строки представляются в шестнадцатеричной (hexadecimal) системе счисления, поэтому содержат цифры от 0 до 9 и буквы от a до f.
Теперь вычислим хэш:
public static void main(String[] args) <var example = new PasswordExample();
var hash = example.getMd5Hash( "P@$$w0rd" );
System.out.println(hash); // c53e479b03b3220d3d56da88c4cace20
>
Если вы используете Spring, то всё то же самое можно сделать в одну строчку с помощью метода DigestUtils.md5DigestAsHex().
Вычисление хэша от файла
Для контроля целостности файла можно вычислить его хэш, а затем сравнить с заранее известным. Вычислить хэш можно следующим образом:
public String getMd5HashForFile(String filename) throws IOException <try <
var md = MessageDigest.getInstance( "MD5" );
var buffer = new byte [ 8192 ];
try ( var is = Files.newInputStream(Paths.get(filename))) <
int read;
while ((read = is.read(buffer)) > 0 ) <
md.update(buffer, 0 , read);
>
>
byte [] digest = md.digest();
return bytesToHex(digest);
> catch (NoSuchAlgorithmException e) <
throw new RuntimeException(e);
>
>
Для чтения файла создаём InputStream в конструкции try-with-resources, которая автоматически закроет этот поток по завершению работы с ним. Сам поток получаем с помощью метода Files.newInputStream(). Затем в цикле считываем данные в созданный нами буфер - массив байт фиксированного размера. На каждой итерации вызываем метод update() для вычисления нового хэша на основе данных из буфера и прошлого значения хэша. Затем аналогично предыдущему примеру получаем значение хэша в виде массива байт и выводим его в виде строки.
Теперь передав в метод полный путь до проверяемого файла вы можете быстро вычислить его хэш. Если у вас Linux, то для проверки значения хэша можете использовать утилиту md5sum.
Читайте также: