← Назад к вопросам

Что такое хеширование?

1.3 Junior🔥 121 комментариев
#Soft Skills и карьера

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI23 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Хеширование (Hashing)

Хеширование — это криптографический процесс преобразования данных произвольного размера в строку фиксированной длины, называемую хешем или хеш-кодом. Хеш-функция — это математический алгоритм, который преобразует входные данные (message) в выходной хеш-код так, что невозможно обратить процесс. Хеширование применяется в криптографии, защите данных, поиске и проверке целостности.

Основные характеристики хеш-функции

1. Детерминированность

Одинаковые входные данные всегда производят одинаковый хеш:

import java.security.MessageDigest;
import java.util.Arrays;

public class HashingExample {
    
    public static String hashMD5(String input) throws Exception {
        MessageDigest md = MessageDigest.getInstance("MD5");
        byte[] messageDigest = md.digest(input.getBytes());
        
        StringBuilder sb = new StringBuilder();
        for (byte b : messageDigest) {
            sb.append(String.format("%02x", b));
        }
        return sb.toString();
    }
    
    public static void main(String[] args) throws Exception {
        String password = "myPassword123";
        
        String hash1 = hashMD5(password);
        String hash2 = hashMD5(password);
        
        System.out.println("Хеш 1: " + hash1);
        System.out.println("Хеш 2: " + hash2);
        System.out.println("Равны: " + hash1.equals(hash2));  // true
    }
}

2. Лавинный эффект (Avalanche Effect)

Малое изменение входных данных резко меняет хеш:

public class AvalancheEffect {
    
    public static void main(String[] args) throws Exception {
        String input1 = "password";
        String input2 = "passwore";  // Изменили 'd' на 'e'
        
        String hash1 = hashSHA256(input1);
        String hash2 = hashSHA256(input2);
        
        System.out.println("Хеш 1: " + hash1);
        System.out.println("Хеш 2: " + hash2);
        System.out.println("Похожи: " + hash1.startsWith(hash2.substring(0, 5)));  // false
    }
    
    public static String hashSHA256(String input) throws Exception {
        MessageDigest md = MessageDigest.getInstance("SHA-256");
        byte[] messageDigest = md.digest(input.getBytes());
        
        StringBuilder sb = new StringBuilder();
        for (byte b : messageDigest) {
            sb.append(String.format("%02x", b));
        }
        return sb.toString();
    }
}

3. Необратимость

Невозможно восстановить исходные данные из хеша:

// Хеш: a94a8fe5ccb19ba61c4c0873d391e987982fbbd3
// Это SHA1("test")
// Но обратно: нельзя из хеша получить "test" без перебора

// Это отличает от шифрования:
// Шифрование: plaintext → ciphertext → plaintext (обратимо, нужен ключ)
// Хеширование: data → hash (необратимо)

4. Фиксированный размер выхода

Независимо от размера входа, хеш всегда одного размера:

public class FixedHashSize {
    
    public static void main(String[] args) throws Exception {
        String short_input = "a";
        String long_input = "Lorem ipsum dolor sit amet, consectetur adipiscing elit...";
        
        String hash1 = hashSHA256(short_input);
        String hash2 = hashSHA256(long_input);
        
        System.out.println("Хеш короткого: " + hash1.length());   // 64 символа
        System.out.println("Хеш длинного: " + hash2.length());    // 64 символа
        // SHA-256 всегда выдает 64 символа (256 бит)
    }
}

Популярные хеш-функции

MD5 (УСТАРЕЛА, НЕ ИСПОЛЬЗОВАТЬ)

// Выдает 128-битный хеш (32 символа в hex)
public class MD5Hash {
    public static String hashMD5(String input) throws Exception {
        MessageDigest md = MessageDigest.getInstance("MD5");
        byte[] messageDigest = md.digest(input.getBytes());
        StringBuilder sb = new StringBuilder();
        for (byte b : messageDigest) {
            sb.append(String.format("%02x", b));
        }
        return sb.toString();
    }
}
// ПРОБЛЕМА: Найдены коллизии (разные данные дают одинаковый хеш)
// НЕ ИСПОЛЬЗУЙ MD5 для криптографии!

SHA-1 (УСТАРЕЛА, ИЗБЕГАЙ)

// Выдает 160-битный хеш (40 символов в hex)
public class SHA1Hash {
    public static String hashSHA1(String input) throws Exception {
        MessageDigest md = MessageDigest.getInstance("SHA-1");
        byte[] messageDigest = md.digest(input.getBytes());
        StringBuilder sb = new StringBuilder();
        for (byte b : messageDigest) {
            sb.append(String.format("%02x", b));
        }
        return sb.toString();
    }
}
// ПРОБЛЕМА: Слабее SHA-256, уязвима для атак

SHA-256 (РЕКОМЕНДУЕТСЯ)

// Выдает 256-битный хеш (64 символа в hex)
public class SHA256Hash {
    public static String hashSHA256(String input) throws Exception {
        MessageDigest md = MessageDigest.getInstance("SHA-256");
        byte[] messageDigest = md.digest(input.getBytes());
        StringBuilder sb = new StringBuilder();
        for (byte b : messageDigest) {
            sb.append(String.format("%02x", b));
        }
        return sb.toString();
    }
    
    public static void main(String[] args) throws Exception {
        String input = "password123";
        String hash = hashSHA256(input);
        System.out.println("SHA-256: " + hash);
        // Результат: f2ca1d6991019f...  (64 символа)
    }
}

SHA-512 (МАКСИМАЛЬНАЯ БЕЗОПАСНОСТЬ)

// Выдает 512-битный хеш (128 символов в hex)
public static String hashSHA512(String input) throws Exception {
    MessageDigest md = MessageDigest.getInstance("SHA-512");
    byte[] messageDigest = md.digest(input.getBytes());
    StringBuilder sb = new StringBuilder();
    for (byte b : messageDigest) {
        sb.append(String.format("%02x", b));
    }
    return sb.toString();
}

Применение: Проверка целостности данных

public class IntegrityCheck {
    
    public static void main(String[] args) throws Exception {
        // Сценарий: Скачиваем файл с интернета
        String downloadedFile = "file.zip";
        String providedHash = "a64e04b66f5da5a0fccd7b7a37f7e4e2";  // На сайте
        
        // Вычисляем хеш скачанного файла
        String computedHash = computeFileHash(downloadedFile);
        
        // Проверяем
        if (computedHash.equals(providedHash)) {
            System.out.println("Файл не повреждён");
        } else {
            System.out.println("ОШИБКА: Файл повреждён или подделан!");
        }
    }
    
    public static String computeFileHash(String filename) throws Exception {
        MessageDigest md = MessageDigest.getInstance("SHA-256");
        byte[] buffer = new byte[8192];
        int bytesRead;
        
        java.io.FileInputStream fis = new java.io.FileInputStream(filename);
        while ((bytesRead = fis.read(buffer)) != -1) {
            md.update(buffer, 0, bytesRead);
        }
        fis.close();
        
        byte[] digest = md.digest();
        StringBuilder sb = new StringBuilder();
        for (byte b : digest) {
            sb.append(String.format("%02x", b));
        }
        return sb.toString();
    }
}

Применение: Хранение паролей

❌ ПЛОХО: Просто хеш

// Сохраняем в БД
String passwordHash = hashSHA256("password123");
// Проблема: одинаковые пароли имеют одинаковый хеш
// Можно использовать "rainbow tables" для взлома

✅ ХОРОШО: Хеш + соль (Salt)

import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.PBKDFKeySpec;
import java.security.SecureRandom;
import java.util.Base64;

public class PasswordHashing {
    
    private static final int ITERATIONS = 10000;
    private static final int KEY_LENGTH = 256;
    private static final int SALT_LENGTH = 16;
    
    // Генерировать соль
    public static byte[] generateSalt() {
        SecureRandom random = new SecureRandom();
        byte[] salt = new byte[SALT_LENGTH];
        random.nextBytes(salt);
        return salt;
    }
    
    // Хешировать пароль с солью
    public static String hashPassword(String password, byte[] salt) throws Exception {
        PBKDFKeySpec spec = new PBKDFKeySpec(
            password.toCharArray(),
            salt,
            ITERATIONS,
            KEY_LENGTH
        );
        
        SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256");
        byte[] hash = factory.generateSecret(spec).getEncoded();
        
        // Возвращаем соль + хеш
        return Base64.getEncoder().encodeToString(salt) + ":" + 
               Base64.getEncoder().encodeToString(hash);
    }
    
    // Проверить пароль
    public static boolean verifyPassword(String password, String storedHash) throws Exception {
        String[] parts = storedHash.split(":");
        byte[] salt = Base64.getDecoder().decode(parts[0]);
        String hash = parts[1];
        
        String computedHash = hashPassword(password, salt).split(":")[1];
        return hash.equals(computedHash);
    }
    
    public static void main(String[] args) throws Exception {
        String password = "MySecurePassword123!";
        
        // Регистрация
        byte[] salt = generateSalt();
        String hashedPassword = hashPassword(password, salt);
        System.out.println("Сохраняем в БД: " + hashedPassword);
        
        // Проверка при логине
        boolean isCorrect = verifyPassword(password, hashedPassword);
        System.out.println("Пароль верен: " + isCorrect);  // true
        
        boolean isWrong = verifyPassword("WrongPassword", hashedPassword);
        System.out.println("Неверный пароль верен: " + isWrong);  // false
    }
}

Коллизии (Collisions)

Коллизия — это когда две разные строки производят одинаковый хеш:

// Для хорошей хеш-функции вероятность коллизии минимальна
// Но по "принципу ящиков" коллизии неизбежны:

// SHA-256: 2^256 возможных хешей
// Вероятность коллизии при N хешей: √(2^256) = 2^128 ≈ 3.4 × 10^38

// Парадокс дня рождения:
// Из 2^128 случайных хешей вероятность коллизии ~50%

Хеширование vs Шифрование

Хеширование:         Шифрование:
data → hash         plaintext → ciphertext → plaintext
↓                   ↓                      ↓
Необратимо          Обратимо (с ключом)
Нет ключа           Требуется ключ
Проверка целостности Конфиденциальность
Примеры: SHA-256    Примеры: AES, RSA

Best Practices

  1. Используй SHA-256 или выше для криптографии
  2. ИЗБЕГАЙ MD5 и SHA-1 — они устарели и небезопасны
  3. НИКОГДА не хеш пароли просто так — используй PBKDF2, bcrypt или Argon2
  4. Добавляй соль (salt) к паролям
  5. Используй SecureRandom для генерации соли
  6. Сравнивай хеши в постоянном времени (защита от timing attacks)
  7. Документируй алгоритм хеширования для каждого поля
// Правильно: Использование bcrypt
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;

public class SecurePassword {
    public static void main(String[] args) {
        BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();
        
        String password = "MyPassword123!";
        String hashedPassword = encoder.encode(password);
        
        // Проверка
        boolean matches = encoder.matches(password, hashedPassword);
        System.out.println("Пароль верен: " + matches);  // true
    }
}

Хеширование — это фундаментальный инструмент в криптографии и безопасности. Понимание его особенностей критично для разработки защищённых приложений.