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

Как сделать уникальную hash функцию?

2.7 Senior🔥 241 комментариев
#Python Core#Безопасность

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

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

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

Как сделать уникальную hash функцию

Hash функции — одна из самых важных концепций в программировании. Уникальность, быстрота и распределение — ключевые свойства хорошей hash функции.

1. Основные требования к hash функции

# Хорошая hash функция должна быть:

# 1. Детерминированной (одинаковый input → одинаковый output)
from hashlib import sha256
print(sha256(b"hello").hexdigest())
print(sha256(b"hello").hexdigest())
# Оба выведут: 2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824

# 2. Равномерно распределённой (малые изменения входа → совершенно другой хеш)
print(sha256(b"hello").hexdigest()[:8])   # 2cf24dba
print(sha256(b"hello!").hexdigest()[:8])  # 334d016f — совсем другое!

# 3. Быстрой (вычисляется за milliseconds)
import time
start = time.time()
for _ in range(1000000):
    sha256(b"test").hexdigest()
print(f"Time: {time.time() - start:.2f}s")  # ~0.3s

# 4. Необратимой (невозможно восстановить original по хешу)
from hashlib import sha256
hash_value = sha256(b"secret").hexdigest()
print(hash_value)  # Невозможно вернуть к "secret"

2. Встроенная функция hash() для объектов

# Python имеет встроенную hash функцию для большинства объектов
print(hash("hello"))        # 8409374635188
print(hash((1, 2, 3)))      # 1614090818
print(hash(42))              # 42

# Но для списков и словарей?
print(hash([1, 2, 3]))      # TypeError: unhashable type: 'list'
print(hash({"a": 1}))       # TypeError: unhashable type: 'dict'

# Объекты с одинаковым содержимым имеют одинаковый hash (обычно)
print(hash("hello") == hash("hello"))  # True
print(hash((1, 2)) == hash((1, 2)))    # True

3. Криптографические hash функции (SHA, MD5)

from hashlib import sha256, sha512, md5, blake2b

data = b"Hello, World!"

# SHA256 (256-bit / 32 байта)
sha256_hash = sha256(data).hexdigest()
print(f"SHA256: {sha256_hash}")  # dffd6021bb2bd5b0af676290809ec3a53191dd81c7f70a4b28688a362182986f

# SHA512 (512-bit / 64 байта)
sha512_hash = sha512(data).hexdigest()
print(f"SHA512: {sha512_hash}")

# MD5 (32-bit / 16 байт) — УСТАРЕЛ, не используй для безопасности
md5_hash = md5(data).hexdigest()
print(f"MD5: {md5_hash}")  # 65a8e27d8d55e529787d08dc5d7d0da8

# BLAKE2b (современный, быстрый)
blake2_hash = blake2b(data).hexdigest()
print(f"BLAKE2b: {blake2_hash}")

# Для больших данных используй обновление
import hashlib
hasher = hashlib.sha256()
hasher.update(b"Part 1")
hasher.update(b"Part 2")
hasher.update(b"Part 3")
result = hasher.hexdigest()

4. Коллизии и вероятность

# Парадокс дня рождения: вероятность коллизии растёт быстрее чем кажется
import hashlib
import math

def birthday_paradox_collision_count(hash_bits):
    """Примерное количество hashes до коллизии (парадокс дня рождения)"""
    # sqrt(2^n) где n — размер хеша в битах
    return int(math.sqrt(2 ** hash_bits))

print(f"MD5 (128-bit): {birthday_paradox_collision_count(128):.2e}")  # 2.15e+19
print(f"SHA256 (256-bit): {birthday_paradox_collision_count(256):.2e}")  # 1.84e+38
print(f"SHA512 (512-bit): {birthday_paradox_collision_count(512):.2e}")  # Огромное число

# На практике MD5 уже взломан (найдены коллизии)
import hashlib
# Плохо
hash_md5 = hashlib.md5(b"data").hexdigest()

# Хорошо
hash_sha256 = hashlib.sha256(b"data").hexdigest()

5. Для пароля: используй специальные функции (bcrypt, argon2)

# НИКОГДА не используй простой SHA для паролей!
# Плохо
from hashlib import sha256
password_hash = sha256(b"user_password").hexdigest()
# Проблема: быстро (можно перебрать все комбинации)

# Хорошо: bcrypt (медленный, защищённый от перебора)
from bcrypt import hashpw, gensalt, checkpw

password = b"user_password"
salt = gensalt(rounds=12)  # rounds — сложность (14 медленнее)
hash_bcrypt = hashpw(password, salt)
print(hash_bcrypt)  # b'$2b$12$...'

# Проверка пароля
if checkpw(b"user_password", hash_bcrypt):
    print("Password correct!")

# Еще лучше: argon2 (современный стандарт)
from argon2 import PasswordHasher

ph = PasswordHasher()
hash_argon2 = ph.hash("user_password")
print(hash_argon2)  # $argon2id$v=19$m=...

# Проверка
try:
    ph.verify(hash_argon2, "user_password")
    print("Password correct!")
except:
    print("Password wrong!")

6. Кастомная hash функция для объектов

from dataclasses import dataclass
from hashlib import sha256
import json

@dataclass
class User:
    id: int
    name: str
    email: str
    
    def __hash__(self):
        """Кастомная hash функция для User"""
        # Вариант 1: Использовать встроенный hash для атрибутов
        return hash((self.id, self.name, self.email))
    
    def __eq__(self, other):
        """Если переопределяешь __hash__, нужна __eq__"""
        if not isinstance(other, User):
            return False
        return self.id == other.id and self.name == other.name

# Использование
user1 = User(1, "John", "john@example.com")
user2 = User(1, "John", "john@example.com")
user3 = User(2, "Jane", "jane@example.com")

print(hash(user1) == hash(user2))  # True (одинаковые значения)
print(user1 == user2)               # True
print(hash(user1) == hash(user3))  # False

# Используй в set и dict
users_set = {user1, user2, user3}
print(len(users_set))  # 2 (user1 и user2 считаются одинаковыми)

users_dict = {user1: "active", user3: "inactive"}
print(users_dict[user2])  # "active" (user2 считается ключом как user1)

7. Кастомная hash для сложных объектов (JSON approach)

from hashlib import sha256
import json

class DataHash:
    """Hash функция для сложных структур данных"""
    
    @staticmethod
    def hash_dict(data: dict) -> str:
        """Hash словаря (независимо от порядка ключей)"""
        # Сортируем для консистентности
        json_str = json.dumps(data, sort_keys=True, default=str)
        return sha256(json_str.encode()).hexdigest()
    
    @staticmethod
    def hash_list(data: list) -> str:
        """Hash списка"""
        json_str = json.dumps(data, default=str)
        return sha256(json_str.encode()).hexdigest()
    
    @staticmethod
    def hash_string(data: str) -> str:
        """Hash строки"""
        return sha256(data.encode()).hexdigest()

# Использование
data1 = {"name": "John", "age": 30, "email": "john@example.com"}
data2 = {"age": 30, "name": "John", "email": "john@example.com"}  # Другой порядок

print(DataHash.hash_dict(data1))  # a1b2c3d4...
print(DataHash.hash_dict(data2))  # a1b2c3d4... — ОДИНАКОВЫЙ!

# Отлично для проверки изменений
original_hash = DataHash.hash_dict(data1)
data1["age"] = 31
modified_hash = DataHash.hash_dict(data1)
print(f"Changed: {original_hash != modified_hash}")  # True

8. Hash функция для файлов

from hashlib import sha256

def hash_file(filepath: str, chunk_size=8192) -> str:
    """Hash файла (работает с большими файлами)"""
    hasher = sha256()
    
    with open(filepath, 'rb') as f:
        while True:
            chunk = f.read(chunk_size)
            if not chunk:
                break
            hasher.update(chunk)
    
    return hasher.hexdigest()

# Использование
file_hash = hash_file("/path/to/large/file.iso")
print(f"File hash: {file_hash}")

# Проверка целостности файла
if hash_file(downloaded_file) == expected_hash:
    print("File is intact!")
else:
    print("File corrupted!")

9. Распределение hash функции (тест)

from hashlib import sha256
import statistics

def test_hash_distribution(hash_func, items, buckets=256):
    """Проверить равномерность распределения хешей"""
    distribution = [0] * buckets
    
    for item in items:
        hash_value = int(hash_func(str(item).encode()).hexdigest(), 16)
        bucket = hash_value % buckets
        distribution[bucket] += 1
    
    avg = statistics.mean(distribution)
    stddev = statistics.stdev(distribution)
    
    print(f"Average items per bucket: {avg:.2f}")
    print(f"Standard deviation: {stddev:.2f}")
    print(f"Min: {min(distribution)}, Max: {max(distribution)}")
    
    return distribution

# Тест SHA256
items = [f"item_{i}" for i in range(10000)]
dist = test_hash_distribution(sha256, items)
# Average items per bucket: 39.06
# Standard deviation: 5.71
# Min: 23, Max: 59
# Хорошее распределение!

10. Сравнение hash функций

┌────────────────────────────────────────────────────┐
│ Когда использовать какую hash функцию              │
├────────────────────────────────────────────────────┤
│                                                    │
│ Встроенная hash()                                  │
│ + Быстрая, встроенная в Python                     │
│ - Не криптографическая, не уникальная             │
│ Когда: В dict/set, для кешей                      │
│                                                    │
│ SHA256                                             │
│ + Стандарт, 256-bit, безопасная                   │
│ - Медленнее чем BLAKE2                             │
│ Когда: Для проверки целостности, хеширование     │
│                                                    │
│ BLAKE2b                                            │
│ + Быстрее SHA, криптографическая                  │
│ - Менее известна чем SHA                           │
│ Когда: Высокие требования к скорости              │
│                                                    │
│ bcrypt / argon2                                    │
│ + Защита от перебора, медленная                   │
│ - Медленнее, предназначена для паролей            │
│ Когда: ТОЛЬКО для хеширования паролей!            │
│                                                    │
└────────────────────────────────────────────────────┘

11. Лучшие практики

# 1. Не используй MD5 для безопасности
# Плохо
from hashlib import md5
md5(data).hexdigest()

# Хорошо
from hashlib import sha256
sha256(data).hexdigest()

# 2. Для паролей используй bcrypt/argon2
# Плохо
sha256(password).hexdigest()

# Хорошо
from argon2 import PasswordHasher
PasswordHasher().hash(password)

# 3. Добавляй salt для паролей (bcrypt/argon2 делают это автоматически)
import os
salt = os.urandom(32)
hashed = sha256(salt + password).hexdigest()

# 4. Сравнивай хеши в постоянное время (защита от timing attack)
from hmac import compare_digest
compare_digest(hash1, hash2)  # Вместо hash1 == hash2

# 5. Для больших файлов читай по частям (как в примере выше)
# Плохо
with open(file, 'rb') as f:
    sha256(f.read()).hexdigest()  # Всё в памяти!

# Хорошо
hasher = sha256()
for chunk in iter(lambda: f.read(8192), b''):
    hasher.update(chunk)

Практический пример: кеширование с hash ключами

from hashlib import sha256
from typing import Any

class HashCache:
    def __init__(self):
        self.cache = {}
    
    def _make_key(self, *args, **kwargs) -> str:
        """Сделать уникальный ключ из аргументов"""
        key_data = str((args, sorted(kwargs.items()))).encode()
        return sha256(key_data).hexdigest()
    
    def get(self, *args, **kwargs) -> Any:
        key = self._make_key(*args, **kwargs)
        return self.cache.get(key)
    
    def set(self, value: Any, *args, **kwargs):
        key = self._make_key(*args, **kwargs)
        self.cache[key] = value

cache = HashCache()
cache.set("result1", "user", 123)
cache.set("result2", "post", 456)
print(cache.get("user", 123))  # "result1"
print(cache.get("post", 456))  # "result2"

Помни: выбор правильной hash функции критичен для безопасности и производительности!

Как сделать уникальную hash функцию? | PrepBro