ГлавнаяБлогПочему UUID спасает от утечки данных: задача о немецких танках
Алгоритмы

Почему UUID спасает от утечки данных: задача о немецких танках

Узнайте, как UUID решает проблему утечки данных через ID. Реальный пример из Второй мировой войны и код на Python для защиты вашего приложения.

Al
Редакция Algolitalgolit.ru
8 мин чтения29 июня 2026 г.

Задача о немецких танках: урок для современных разработчиков

Во время Второй мировой войны союзники столкнулись с вопросом: сколько танков на самом деле производит Германия? Официальные методы — шпионаж, перехват сообщений и догадки — давали оценку в 1500 танков в месяц. Но статистики, используя только серийные номера с захваченных танков и простую формулу, получили 327. После войны выяснилось, что реальное число — 342. Ошибка разведки — более 1000, ошибка статистиков — 15. Урок: последовательные номера выдают секреты. Если ваши ID идут по порядку, любой, увидев несколько, может оценить общее количество. Эта статья покажет, как избежать этой уязвимости с помощью UUID.

Как работает утечка: пример с вашим приложением

Представьте: у вас есть база данных с автоинкрементным первичным ключом. Пользователь регистрируется и получает ID = 4317. Через неделю он регистрируется снова — ID = 4981. Вычитаем: 664 регистрации за неделю. Ваш рост раскрыт без взлома. То же с заказами: /invoices/58 говорит, что у вас всего 58 счетов. А последовательные ID легко перебирать: если я вижу /api/orders/9000, могу попробовать /8999 — это IDOR (Insecure Direct Object Reference), уязвимость из OWASP Top 10.

# Пример: утечка через автоинкремент
user_ids = [4317, 4981]  # ID двух регистраций
signups = user_ids[1] - user_ids[0]  # 664
print(f"Новых пользователей за неделю: {signups}")

UUID: случайность против утечки

UUID (Universally Unique Identifier) — 128-битное случайное число, например, f47ac10b-58cc-4372-a567-0e02b2c3d479. Оно не имеет порядка: нельзя определить предыдущий, следующий ID или общее количество. Задача о немецких танках бессильна против UUID. Дополнительный плюс: UUID уникален глобально без координации — два сервера могут генерировать ID одновременно без коллизий.

import uuid

# Генерация UUIDv4
user_id = uuid.uuid4()
print(f"Новый UUID: {user_id}")
# Вывод: f47ac10b-58cc-4372-a567-0e02b2c3d479

Проблемы UUIDv4: производительность

UUIDv4 случайный, что плохо для B-деревьев индексов. Каждая вставка — как пьяный гость, садящийся между двумя людьми: база данных постоянно перестраивает страницы. Это write amplification и фрагментация. Для высоконагруженных таблиц — катастрофа. Решение — UUIDv7 (стандарт 2024): старшие биты содержат временную метку, младшие — случайность. ID растут упорядоченно, но остаются непредсказуемыми.

# Пример генерации UUIDv7 (требуется Python 3.12+)
import uuid

user_id = uuid.uuid7()
print(f"UUIDv7: {user_id}")
# Вывод: 018f3b1a-2c7e-7a00-8000-000000000001 (время + случайность)

Альтернативы: ULID и Snowflake

ULID — сортируемая текстовая кодировка с временной меткой. Snowflake ID (Twitter) — 64 бита: временная метка + ID машины + счетчик. Компактнее UUID, но слегка выдают время создания. Выбирайте осознанно.

# Пример ULID (библиотека ulid-py)
import ulid

user_id = ulid.new()
print(f"ULID: {user_id}")
# Вывод: 01ARZ3NDEKTSV4RRFFQ69G5FAV

Важно: UUID не заменяет авторизацию

Случайный ID — это замок, но нужен ещё охранник — проверка прав доступа. UUID защищает от перебора, но не от IDOR, если не проверять, кому принадлежит ресурс. Всегда проверяйте авторизацию.

# Flask пример: проверка авторизации
@app.route('/api/orders/<uuid:order_id>')
def get_order(order_id):
    order = Order.query.get(order_id)
    if order.user_id != current_user.id:
        return 'Forbidden', 403
    return jsonify(order.to_dict())

Что делать прямо сейчас

  1. Не используйте автоинкремент для публичных ID. Оставьте его как внутренний первичный ключ для быстрых JOIN-ов.
  2. Добавьте отдельное поле для внешнего ID — UUIDv7. Генерируйте его при создании записи.
  3. Проверьте авторизацию во всех эндпоинтах, даже если ID случайные.
# Пример: миграция для добавления внешнего UUID
ALTER TABLE users ADD COLUMN public_id UUID DEFAULT gen_random_uuid();

# В коде (SQLAlchemy)
import uuid
from sqlalchemy import Column, Integer, UUID

class User(Base):
    __tablename__ = 'users'
    id = Column(Integer, primary_key=True)  # внутренний
    public_id = Column(UUID, default=uuid.uuid7, unique=True)  # внешний

Статистики всё ещё существуют. И они всё ещё самодовольны. Не дайте им прочитать ваши серийные номера.

#UUID#безопасность#IDOR#производительность#базы данных
Al
Редакция Algolit

Пишем про алгоритмы, подготовку к собеседованиям и карьеру в IT — так, чтобы было понятно и полезно.

Хочешь закрепить знания на практике?

Решай задачи на Algolit — интерактивная платформа для обучения

Начать бесплатно →