← Назад к вопросам
Как сделать уникальную 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 функции критичен для безопасности и производительности!