Перенесення користувачів — технічний квест, який ми вирішуємо без втрати доступу
Зазначимо: коли бізнес вирішує переїхати на нову CMS або фреймворк, найделікатніше питання — користувачі. Їхні паролі зашифровані старим алгоритмом хешування паролів — sha512 з сіллю (Drupal 7), phpass (WordPress) або md5($password.$salt) із самописної CRM. Нова система ці хеші не розуміє, і просто скопіювати базу не можна — ніхто не ввійде. Більше того, інженери часто припускаються помилки: копіюють таблицю користувачів як є, а потім з'ясовують, що всі паролі недійсні. Результат — масове скидання паролів і втрата лояльності. Ми провели понад 50 міграцій, включаючи проекти з 200 000 користувачів, і знаємо, як провести перенесення так, щоб користувачі навіть не помітили переїзду. Нижче — перевірена стратегія та інструменти (Python, Golang, Laravel, Django), які ми використовуємо в кожному проекті. Ми гарантуємо безпеку даних: жоден користувач не втратить доступ.
Які проблеми вирішуємо?
- Несумісність алгоритмів хешування паролів: стара система використовує
sha512абоMD5, нова — тількиbcrypt. - Дублікати користувачів за email при об'єднанні баз.
- Втрата ролей і прав доступу при перенесенні ролей.
- Необхідність зберегти активні сесії та уникнути масового перелогіну.
- SSO міграція для тимчасової спільної роботи старої та нової платформ.
Чому lazy migration краще за повне рехешування?
Lazy migration дозволяє перенести користувачів без простою та втрати доступу. Старі хеші залишаються в базі, а при кожному вході перевіряється алгоритм і при успіху пароль перехешовується новим. Це в 3–5 разів швидше за повне примусове скидання паролів і не викликає відтік аудиторії. Крім того, він дозволяє поступово переносити користувачів без простою — можна мігрувати базу у фоновому режимі, а 100% навантаження на стару систему зберігається.
| Стратегія | Час реалізації | Вплив на користувачів | Безпека даних |
|---|---|---|---|
| Lazy migration | 1-2 дні | Мінімальний, процес прозорий | Висока при правильній реалізації |
| Примусове скидання паролів | 1-3 дні | Вимагає від користувача зміни пароля | Висока |
| Повне рехешування | 3-7 днів | Середній, можлива тимчасова недоступність | Середня (помилки при масовому перехешуванні) |
Примусове скидання для legacy алгоритмів
Якщо підтримка застарілих алгоритмів (MD5, SHA1) небажана, ми надсилаємо користувачам email з токеном для скидання паролів. Це безпечніше, ніж зберігати ненадійні хеші. Сценарій: скрипт знаходить усіх користувачів з алгоритмом legacy_md5, генерує одноразовий токен і надсилає листа. Термін дії токена — 7 днів. Після успішної зміни пароль зберігається вже з bcrypt.
Вирішення несумісності алгоритмів
У цьому випадку ми впроваджуємо проміжний шар верифікації. Наприклад, для phpass (WordPress) використовуємо власну реалізацію на Python. «PHPass is a portable public domain password hashing framework» — його алгоритм підтримується через кастомну функцію. При зміні алгоритму хешування важливо зберегти старий хеш до моменту входу користувача. Аналогічно для PBKDF2 (Django) і sha512 (Drupal 7). Це дозволяє зберегти сумісність без переписування всієї системи.
Як реалізувати lazy migration?
Покроковий план:
- Проаналізуйте існуючі хеші та визначте алгоритми.
- Реалізуйте функцію верифікації для кожного алгоритму.
- Додайте логіку перехешування при успішному вході.
- Розробіть ETL-скрипт для перенесення даних.
- Протестуйте на копії бази.
- Запустіть міграцію та моніторте помилки.
Основна ідея — зберігати вихідний хеш і мітку алгоритму в полі password_algorithm. При вході викликаємо функцію verify_password, яка перевіряє алгоритм, і при успіху — upgrade_password_hash. Розглянемо реалізацію на Python.
# models/user.py
class User(BaseModel):
password_hash: str
password_algorithm: str # 'bcrypt', 'phpass', 'sha512', 'legacy_md5'
def verify_password(self, plain_password: str) -> bool:
if self.password_algorithm == 'bcrypt':
return bcrypt.checkpw(plain_password.encode(), self.password_hash.encode())
elif self.password_algorithm == 'phpass':
return phpass_check(plain_password, self.password_hash)
elif self.password_algorithm == 'legacy_md5':
return hashlib.md5(plain_password.encode()).hexdigest() == self.password_hash
elif self.password_algorithm == 'pbkdf2_sha256':
return django_pbkdf2_check(plain_password, self.password_hash)
return False
def upgrade_password_hash(self, plain_password: str):
new_hash = bcrypt.hashpw(plain_password.encode(), bcrypt.gensalt(rounds=12))
self.password_hash = new_hash.decode()
self.password_algorithm = 'bcrypt'
db.save(self)
Перевірка сумісності phpass (WordPress)
Деталі реалізації для WordPress
WordPress використовує [phpass](https://en.wikipedia.org/wiki/PHPass). Для інтеграції з Python:import hashlib
ITOA64 = './0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'
def phpass_check(password: str, stored_hash: str) -> bool:
if stored_hash.startswith('$P$') or stored_hash.startswith('$H$'):
return _phpass_verify(password, stored_hash)
return hashlib.md5(password.encode()).hexdigest() == stored_hash
def _phpass_verify(password: str, hash_str: str) -> bool:
count_log2 = ITOA64.index(hash_str[3])
count = 1 << count_log2
salt = hash_str[4:12]
hash_val = hashlib.md5((salt + password).encode()).digest()
for _ in range(count):
hash_val = hashlib.md5(hash_val + password.encode()).digest()
output = _encode64(hash_val, 16)
return hash_str[12:34] == output[:22]
ETL: перенесення користувачів із мапінгом ролей
Для масового перенесення використовуємо ETL-скрипт. Приклад для WordPress:
def migrate_users_from_wordpress(wp_db, new_db):
cursor = wp_db.cursor(dictionary=True)
cursor.execute("""
SELECT u.ID, u.user_login, u.user_pass, u.user_email,
u.user_registered, u.display_name,
um.meta_value as first_name, um2.meta_value as last_name
FROM wp_users u
LEFT JOIN wp_usermeta um ON u.ID = um.user_id AND um.meta_key = 'first_name'
LEFT JOIN wp_usermeta um2 ON u.ID = um2.user_id AND um2.meta_key = 'last_name'
ORDER BY u.ID
""")
migrated = 0
skipped = 0
for wp_user in cursor.fetchall():
existing = new_db.get_user_by_email(wp_user['user_email'])
if existing:
skipped += 1
continue
algorithm = detect_wp_hash_algorithm(wp_user['user_pass'])
new_db.create_user({
'username': wp_user['user_login'],
'email': wp_user['user_email'],
'password_hash': wp_user['user_pass'],
'password_algorithm': algorithm,
'display_name': wp_user['display_name'],
'created_at': wp_user['user_registered'],
'legacy_id': wp_user['ID'],
})
# Мапінг ролей: з wp_usermeta meta_key = 'wp_capabilities'
roles = get_user_roles(wp_db, wp_user['ID'])
new_db.assign_roles(wp_user['user_email'], roles)
migrated += 1
print(f"Migrated: {migrated}, Skipped: {skipped}")
Обробка входу та перехешування
def login(email: str, password: str):
user = db.get_user_by_email(email)
if not user:
return None
if user.verify_password(password):
if user.password_algorithm != 'bcrypt':
user.upgrade_password_hash(password)
return create_session(user)
return None
Що входить у роботу з міграції користувачів?
- Аудит поточної бази користувачів та алгоритмів хешування.
- Розробка скриптів ETL з урахуванням мапінгу ролей і додаткових полів.
- Реалізація lazy migration (або примусового скидання) з тестуванням на копії.
- Налаштування моніторингу помилок після деплою (24-годинне спостереження).
- Документація щодо процедури відкату та резервне копіювання.
Строки виконання та типові помилки
Орієнтовні строки
- Для бази до 100 000 користувачів: 2–3 робочі дні на аудит, розробку та тестування. Вартість аудиту від 5000 грн, комплексна міграція — від 25000 грн.
- Для бази від 100 000 до 500 000: 5–7 днів з урахуванням ETL та навантажувального тестування.
- У великих проектах з десятками ролей і кастомними полями строк може бути збільшений.
Типові помилки при міграції
- Пропуск перевірки дублікатів за email — призводить до конфліктів і втрати даних.
- Ігнорування мапінгу ролей — користувач переноситься, але втрачає права доступу.
- Спроба рехешувати всі паролі одразу — викликає CPU-навантаження та помилки.
- Відсутність плану відкату — при невдачі немає швидкого відновлення.
- Незакриття старих сесій — користувачі залишаються в системі під старими даними.
Поради:
- Завжди тестуйте на копії бази.
- Використовуйте транзакції та скрипти з rollback.
- Налаштуйте моніторинг помилок після деплою (перші 24 години критичні).
- Зберігайте резервну копію старої бази та скрипт зворотної міграції.
Порівняння алгоритмів хешування
| Алгоритм | Довжина хешу | Стійкість до брутфорсу | Стандарт у CMS |
|---|---|---|---|
| bcrypt | 60 символів | Висока (cost adjustable) — у 1000 разів стійкіший за MD5 | Laravel, Symfony, Rails |
| phpass | 34 символи | Середня (MD5-based) | WordPress, Drupal 7 |
| PBKDF2 | 98 символів | Висока | Django, Python |
| MD5 | 32 символи | Низька | Застарілі системи |
Послуга під ключ: від аудиту до деплою. Виконаємо міграцію за 3-5 робочих днів залежно від обсягу. Напишіть нам для безкоштовної оцінки проекту — у вартість входить тестування та 24-годинний моніторинг. Отримайте точний кошторис з гарантією результату.







