MessageDigest.digest: примеры (JAVA)

Практика работы с MessageDigest.digest
Раздел: Шифрование и хеширование (Cryptography, MessageDigest)
MessageDigest.digest(byte[] input): byte[]

Общее описание

Класс java.security.MessageDigest предоставляет реализацию алгоритмов хеширования (MD5, SHA-1, SHA-256 и т. п.). Метод digest выполняет вычисление итогового дайджеста (хеша) от ранее переданных данных или переданного массива и возвращает результат в виде байтового массива или записывает его в предоставленный буфер. Метод полезен для контроля целостности, хранения проверочных сумм и подготовки входных данных для криптографических схем.

Поведение метода зависит от сигнатуры. После выполнения вычисления внутреннее состояние экземпляра сбрасывается к начальному (аналогично вызову reset()), поэтому повторный вызов без новых вызовов update вернёт дайджест пустого состояния.

Сигнатуры и семантика аргументов

  • byte[] digest()

    Выполняет завершающий шаг на основе накопленных ранее вызовов update. Возвращает новый массив байтов длиной, равной размеру дайджеста (например, 16 для MD5, 32 для SHA-256).

  • byte[] digest(byte[] input)

    Комбинирует вызов update(input) и digest() в одной операции. Возвращает новый массив байтов с дайджестом входных данных.

  • int digest(byte[] buf, int offset, int len) throws DigestException

    Записывает вычисленный дайджест в предоставленный массив buf, начиная с позиции offset. Параметр len указывает максимально доступный размер для записи (т. е. число байтов, которое можно разместить начиная с offset). Метод возвращает количество записанных байтов (обычно равное длине дайджеста). Если доступного места недостаточно, генерируется java.security.DigestException. После записи внутреннее состояние сбрасывается.

Замечания об аргументах и возвращаемых значениях:

  • Длина возвращаемого массива или записанного диапазона равна getDigestLength() у конкретного экземпляра MessageDigest.
  • Методы нечувствительны к кодировке байтов; при хешировании строк следует явно указывать кодировку, например getBytes(StandardCharsets.UTF_8).
  • MessageDigest не является потокобезопасным. Для параллельных вычислений требуется отдельный экземпляр на поток или синхронизация.

Короткие примеры

Ниже приведены минимальные примеры основных вариантов использования метода digest с результатами.

1) digest() после update:

import java.security.MessageDigest;
import java.util.HexFormat;

MessageDigest md = MessageDigest.getInstance("SHA-256");
md.update("abc".getBytes(java.nio.charset.StandardCharsets.UTF_8));
byte[] hash = md.digest();
System.out.println(HexFormat.of().formatHex(hash));
ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad

2) digest(byte[] input) в одну строчку:

MessageDigest md = MessageDigest.getInstance("MD5");
byte[] md5 = md.digest("hello".getBytes(java.nio.charset.StandardCharsets.UTF_8));
System.out.println(javax.xml.bind.DatatypeConverter.printHexBinary(md5).toLowerCase());
5d41402abc4b2a76b9719d911017c592

3) digest(buf, offset, len) - запись в заранее выделенный массив:

MessageDigest md = MessageDigest.getInstance("SHA-1");
byte[] input = "abc".getBytes(java.nio.charset.StandardCharsets.UTF_8);
byte[] buf = new byte[64];
md.update(input);
int written = md.digest(buf, 8, buf.length - 8);
System.out.println("written=" + written);
// вывести хекс от части массива
String hex = java.util.HexFormat.of().formatHex(buf, 8, 8 + written);
System.out.println(hex);
written=20
a9993e364706816aba3e25717850c26c9cd0d89d

Похожие возможности в Java

  • java.security.DigestInputStream - потоковая обёртка для автоматического обновления MessageDigest при чтении данных. Подходит для больших файлов, чтобы не загружать всё в память.
  • javax.crypto.Mac - реализация HMAC (ключевой хеш). Предпочтительнее для аутентификации сообщений, когда требуется секретный ключ.
  • java.util.zip.CRC32 - контрольная сумма, не криптографическая, но быстрая и компактная. Используется для обнаружения случайных ошибок, не для безопасности.
  • MessageDigestSpi - для реализации собственных провайдеров алгоритмов. Используется редко, когда нужен кастомный алгоритм.

Выбор зависит от задачи: для криптографической целостности - MessageDigest или Mac (в случае ключа). Для потоковой обработки больших объёмов предпочтительнее DigestInputStream.

Аналоги в других языках

Короткие примеры и отличия от Java:

  • PHP
    echo hash('sha256', 'abc');
    ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad

    hash выполняет вычисление в одну операцию; для итеративного обновления доступен hash_init / hash_update / hash_final.

  • JavaScript (Node.js)
    const crypto = require('crypto');
    const h = crypto.createHash('sha256').update('abc').digest('hex');
    console.log(h);
    ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad

    В браузере доступна crypto.subtle.digest, возвращающая Promise с ArrayBuffer.

  • Python
    import hashlib
    print(hashlib.sha256(b'abc').hexdigest())
    ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad

    hashlib поддерживает incremental update через объект хеша.

  • SQL (пример для MySQL/Postgres)
    -- MySQL
    SELECT SHA2('abc', 256);
    -- PostgreSQL (extension pgcrypto)
    SELECT digest('abc', 'sha256');
    ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad
    \xBA7816... (варианты представления зависят от СУБД)
  • C# (.NET)
    using System.Security.Cryptography;
    using System.Text;
    
    using var sha = SHA256.Create();
    byte[] b = sha.ComputeHash(Encoding.UTF8.GetBytes("abc"));
    Console.WriteLine(BitConverter.ToString(b).Replace("-", "").ToLower());
    ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad
  • Go
    import ("crypto/sha256"; "fmt")
    
    h := sha256.Sum256([]byte("abc"))
    fmt.Printf("%x\n", h)
    ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad
  • Kotlin
    val md = java.security.MessageDigest.getInstance("SHA-256")
    val digest = md.digest("abc".toByteArray(Charsets.UTF_8))
    println(java.util.HexFormat.of().formatHex(digest))
    ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad

Отличия в основном в API: Java предоставляет объектно-ориентированный интерфейс с incremental update, как и многие другие языки. Различия в ошибкоустойчивости, потоковой обработке и представлении результата (raw bytes, hex, base64).

Типичные ошибки и примеры

  • Отсутствие явной кодировки строк

    Пример: "текст".getBytes() может дать разный результат на разных платформах. Рекомендуется указывать StandardCharsets.UTF_8.

    MessageDigest md = MessageDigest.getInstance("SHA-256");
    byte[] h = md.digest("привет".getBytes());
    System.out.println(java.util.HexFormat.of().formatHex(h));
    (результат зависит от платформы; на UTF-8 будет детерминирован)
  • Неправильная работа с digest(buf, offset, len)

    Если доступного места меньше, возникает DigestException.

    MessageDigest md = MessageDigest.getInstance("SHA-256");
    md.update("abc".getBytes(java.nio.charset.StandardCharsets.UTF_8));
    byte[] buf = new byte[10];
    int written = md.digest(buf, 0, buf.length); // недостаточно места
    System.out.println(written);
    Exception in thread "main" java.security.DigestException: Insufficient space in the output buffer to store the digest
  • Попытка многопоточного совместного использования одного экземпляра

    MessageDigest не потокобезопасен. Несинхронизированный доступ приведёт к некорректным результатам.

    // два потока параллельно вызывают update(...) на одном экземпляре MessageDigest
    // результат хеша будет непредсказуем
    (неопределённый, возможны неверные дайджесты)
  • Ошибочное сравнение байтов через Arrays.equals в криптографическом контексте

    Arrays.equals может быть подвержен тайминг-атакам; для критичных задач предпочтительна MessageDigest.isEqual (реализована с постоянным временем).

Изменения в API

API MessageDigest в Java остаётся стабильным на протяжении долгого времени. Основные изменения связаны с добавлением новых алгоритмов поставщиками и поддержкой современных стандартов через провайдеров безопасности. Формальные сигнатуры методов digest() и связанной функциональности не претерпели существенных изменений в последних версиях платформы.

Отдельно: поддержка удобных утилит для представления байтов (например, java.util.HexFormat) появилась в более новых версиях JDK; это облегчает преобразование результата в текст.

Продвинутые и редкие сценарии

Несколько подробных примеров с пояснениями.

1) Инкрементальное хеширование больших файлов через DigestInputStream:

Пример java
import java.io.FileInputStream;
import java.security.DigestInputStream;
import java.security.MessageDigest;
import java.util.HexFormat;

MessageDigest md = MessageDigest.getInstance("SHA-256");
try (DigestInputStream dis = new DigestInputStream(new FileInputStream("big.bin"), md)) {
    byte[] buffer = new byte[8192];
    while (dis.read(buffer) != -1) {
        // чтение и одновременное обновление md
    }
}
byte[] digest = md.digest();
System.out.println(HexFormat.of().formatHex(digest));
(хеш файла big.bin)

Пояснение: DigestInputStream освобождает от необходимости самостоятельно вызывать update при чтении кусками.

2) Снимок состояния хеша через клонирование:

Пример java
MessageDigest md = MessageDigest.getInstance("SHA-256");
md.update("prefix".getBytes(java.nio.charset.StandardCharsets.UTF_8));
MessageDigest snapshot = (MessageDigest) md.clone();

// продолжение для варианта A
md.update("A".getBytes(java.nio.charset.StandardCharsets.UTF_8));
byte[] a = md.digest();

// продолжение для варианта B на основе snapshot
snapshot.update("B".getBytes(java.nio.charset.StandardCharsets.UTF_8));
byte[] b = snapshot.digest();

System.out.println(java.util.HexFormat.of().formatHex(a));
System.out.println(java.util.HexFormat.of().formatHex(b));
(два разных хеша: prefix+A и prefix+B)

Пояснение: клонирование позволяет эффективно ветвить вычисления без повторного хеширования префикса.

3) Итеративное хеширование (простая реализация устойчивости к перебору, не заменяет PBKDF2):

Пример java
MessageDigest md = MessageDigest.getInstance("SHA-256");
byte[] data = "password".getBytes(java.nio.charset.StandardCharsets.UTF_8);
int iterations = 10000;
byte[] cur = data;
for (int i = 0; i < iterations; i++) {
    cur = md.digest(cur);
}
System.out.println(java.util.HexFormat.of().formatHex(cur));
(результат зависит от количества итераций)

Пояснение: для реального KDF применяется javax.crypto.SecretKeyFactory и PBKDF2WithHmacSHA256. Простой цикл может быть полезен для некоторых задач, но не для стандартизованного хранения паролей.

4) Константное время сравнения 결과 с MessageDigest.isEqual:

Пример java
byte[] a = MessageDigest.getInstance("SHA-256").digest("x".getBytes());
byte[] b = MessageDigest.getInstance("SHA-256").digest("y".getBytes());
boolean eq = MessageDigest.isEqual(a, b);
System.out.println(eq);
false

Пояснение: использование isEqual снижает риск тайминг-атак при сравнении хешей в аутентификационных сценариях.

5) Комбинация нескольких алгоритмов (например, двойной хеш):

Пример java
MessageDigest md1 = MessageDigest.getInstance("SHA-256");
MessageDigest md2 = MessageDigest.getInstance("SHA-1");
byte[] first = md1.digest("data".getBytes(java.nio.charset.StandardCharsets.UTF_8));
byte[] combined = md2.digest(first);
System.out.println(java.util.HexFormat.of().formatHex(combined));
(результат SHA-1 от SHA-256("data"))

Пояснение: подобные комбинации специфичны и редко повышают безопасность по сравнению с правильно подобранным современным алгоритмом.

джава MessageDigest.digest function comments

En
MessageDigest.digest Performs a final update on the digest using the specified array of bytes, then completes the digest computation