Практический алгоритм определения типа хеша по строке

· · Хеш, Безопасность, Пароли, Использование существующих активов, Техническое расследование

Часто встречаются ситуации, когда в логах или БД остаётся строка вроде 5f4dcc3b5aa765d61d8327deb882cf99 или $2b$12$..., и нужно определить, «что это за хеш». При миграции существующих систем, расследовании методов аутентификации, анализе логов, интеграции со сторонними системами дело нередко упирается именно в этот вопрос.

Опасность здесь в том, чтобы делать поспешный вывод только по длине строки. Увидев 64-символьную шестнадцатеричную строку и заявить «это SHA-256» - слишком поспешно. Такую же длину дают и стандартные 32-байтовые выводы SHA3-256, SHA-512/256, BLAKE2s-256, BLAKE3. И наоборот, форматы хранения, включающие префикс и параметры - например $2b$ или $argon2id$ - можно определить по одной лишь строке с довольно высокой точностью.

В этой статье слово хеш используется широко: не только для message digest вроде MD5 / SHA-2 / SHA-3, но и для строковых представлений, используемых для хранения паролей, таких как bcrypt / scrypt / Argon2 / PBKDF2. Содержание опирается на официальные материалы - RFC, публикации NIST, Linux crypt(5), документацию Apache, Django, Spring Security, - доступные по состоянию на апрель 2026 года.

Оглавление

  1. Сначала вывод
  2. Таблицы для определения с первого взгляда
  3. Практический порядок определения
  4. Частые ошибки определения
  5. Порядок проверки, если нужна стопроцентная уверенность
  6. Итог
  7. Услуги, связанные с этой темой
  8. Источники

1. Сначала вывод

Сначала коротко изложим выводы.

  • Форматы хранения с префиксом или разделителями довольно легко определить по одной строке. Примеры: $argon2id$..., $2b$..., $5$..., $6$..., {SHA}..., pbkdf2_sha256$...

  • Обычная шестнадцатеричная строка или просто Base64 обычно позволяют только «сузить круг кандидатов». Пример: 32 hex-символа = возможно MD5, но не исключены и MD4 / NTLM

  • Набор символов важен не меньше длины. Наличие + / = говорит о вероятном Base64 по RFC 4648, наличие . вместе с разделителем $ - о вероятной принадлежности к семейству crypt(3). Такие признаки действительно работают.

  • Для стопроцентной уверенности нужен контекст. Ситуация меняется в зависимости от того, находится ли строка в /etc/shadow, .htpasswd, таблице auth_user Django или в системе Spring Security.

Иными словами, «схемы, которые можно определить по одной строке» и «схемы, где строка даёт лишь круг кандидатов» - это разные вещи. Одно только это разделение меняет ход расследования.

2. Таблицы для определения с первого взгляда

2.1 Форматы, которые почти однозначно определяются по префиксу или маркеру формата

«Уверенность» в таблице используется в следующем смысле.

  • Высокая: почти можно определить только по строке
  • Средняя: круг кандидатов сильно сужается, но нужно учитывать различия в реализациях
  • Низкая: невозможно точно определить только по длине или внешнему виду
Визуальный признак Первый подозреваемый Уверенность Примечание Пример
$argon2id$... Argon2id Высокая PHC string format. Часто следуют v=, m=, t=, p= $argon2id$v=19$m=65536,t=3,p=4$MDEyMzQ1Njc4OWFiY2RlZg$uKZLaN6muIyoyIYr5waqw3y+zaDbe9aLSPj6Ln/rbz4
$argon2i$... Argon2i Высокая То же самое $argon2i$v=19$m=65536,t=3,p=4$MDEyMzQ1Njc4OWFiY2RlZg$Kx1koF/7n8EytGJYTS5krh+ag+FlG5ksM4xOsjOSDvo
$argon2d$... Argon2d Высокая То же самое $argon2d$v=19$m=65536,t=3,p=4$MDEyMzQ1Njc4OWFiY2RlZg$HLIGA+T1bwK8akx3LGOco+Df+PvxX6cIXhycO7O7t6c
$2a$... / $2b$... / $2y$... bcrypt Высокая Двузначная стоимость + алфавит crypt-семейства $2b$12$9YQ2u/e5Y/ArOnG.gJKxK.0makLATcYLP1q.Nsabzrw7XErYCfoYO
$1$... md5crypt Высокая Unix-формат хранения паролей MD5 $1$vA7mQ9xZ$Erz32JUFnZ9991KdU5.N3.
$5$... sha256crypt Высокая Не обычный SHA-256 $5$rounds=5000$N3v8Kx2Lq9Rt$uOTla5GAHaRH2aHlUSjkrZUBCuFiahQZ36O/seB39r3
$6$... sha512crypt Высокая Не обычный SHA-512 $6$rounds=5000$N3v8Kx2Lq9Rt$6LUcSUAELX3aC/.60pTB.TFLTQi1mOGRCwKqNCqtRSaXjorxj01HJ9oNni97Kci1uDt7a/Kn4t3OS20Dw/.vi1
$7$... scrypt (crypt-семейство) Высокая Встречается в реализациях семейства Linux crypt(5) $7$CU..../....k2XAnEHBqQ1Ct2aMXFKNa/$y3Q0e/UlCHacIGWQshgvvz6UIbP.BCja.5BfVWP2Ml8
$y$... yescrypt Высокая Встречается в более новых Linux-системах $y$j9T$k2XAnEHBqQ1Ct2aMXFKNa/$OVYXzjlkiQpWT/F1CUE0JrvV4phLY8FB.ofDttnrSQ7
$apr1$... Apache APR1-MD5 Высокая Часто встречается в .htpasswd $apr1$vA7mQ9xZ$ZE64.ohiyK11sPZmtnJZQ.
{SHA}... Base64-представление digest SHA-1 Высокая Часто встречается в Apache / LDAP {SHA}VBPuJHI7uixaa6LQGWx4s+5GKNE=
{SSHA}... salted SHA-1 Высокая Семейство LDAP {SSHA}/OczD0GNNkOAUPbYhA3L9fjmcyBCbHVlTWVzYTQyIQ==
{MD5}... / {SMD5}... MD5 / salted MD5 Высокая Семейство LDAP {MD5}X03MO1qnZdYdgyfeuILPmQ==
{SMD5}fOn1rOv4ZH0OrO/KT9H0fEJsdWVNZXNhNDIh
pbkdf2_sha256$... PBKDF2-HMAC-SHA256 Средняя-высокая Django и другие: реализация добавляет имя формата в начало pbkdf2_sha256$600000$N3v8Kx2Lq9Rt$CLxGB+zTiV1IdOt2y4m9JpaAONzHuRTOd96xKQwRQAs
{bcrypt}$2b$... bcrypt Высокая С обёрткой {id} из Spring Security {bcrypt}$2b$12$9YQ2u/e5Y/ArOnG.gJKxK.0makLATcYLP1q.Nsabzrw7XErYCfoYO
{pbkdf2}... / {scrypt}... Схема с меткой реализации Средняя-высокая Spring Security и подобные; важнее распознать формат обёртки, чем сам алгоритм {pbkdf2}sha256$600000$Qmx1ZU1lc2E0MiE$4eNuai1qNkgs1kXz3+tBUMzAexVsSUz9SrQKEhbk0Cw
{scrypt}ln=14,r=8,p=1$Qmx1ZU1lc2E0MiE$xAgBRhXbMtHB1UHUR0br5bI+1XdXWKbwauiFv5VRQBY

Смысл этой таблицы в том, что форматы, в которых первые несколько символов несут смысл, определяются надёжно. Строки, разделённые $...$, с высокой вероятностью относятся к семейству Unix crypt(3) / MCF / PHC, и смотреть на префикс быстрее, чем на длину.

2.2 Таблица сужения кандидатов по длине для обычного hex / Base64

Эта таблица - для «голых» строк digest без префикса. Для представлений со смешанными :, - или пробелами сначала нужно убрать разделители и посчитать длину.

Длина в байтах Число hex-символов Число символов Base64 (с = / без) Основные кандидаты Пример
4 8 8 / 6 Контрольные суммы вроде CRC32 cbf43926
16 32 24 / 22 MD5, MD4, семейство NTLM 5f4dcc3b5aa765d61d8327deb882cf99
20 40 28 / 27 SHA-1, RIPEMD-160 da39a3ee5e6b4b0d3255bfef95601890afd80709
28 56 40 / 38 SHA-224, SHA-512/224, SHA3-224 d14a028c2a3a2bc9476102bb288234c415a2b01f828ea62ac5b3e42f
32 64 44 / 43 SHA-256, SHA-512/256, SHA3-256, BLAKE2s-256, вывод BLAKE3 по умолчанию (32 байта) e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855
48 96 64 / 64 SHA-384, SHA3-384, BLAKE2b-384 38b060a751ac96384cd9327eb1b1e36a21fdb71114be07434c0cc7bf63f6e1da274edebfe76f65fbd51ad2f14898b95b
64 128 88 / 86 SHA-512, SHA3-512, BLAKE2b-512, Whirlpool cf83e1357eefb8bdf1542850d66d8007d620e4050b5715dc83f4a921d36ce9ce47d0d13c5d85f2b0ff8318d2877eec2f63b931bd47417a81a538327af927da3e

Здесь важно то, что совпадение длины не определяет схему однозначно. У шестнадцатеричных строк длиной 32 / 64 / 128 символов особенно много кандидатов, и если делать вывод только по этому признаку, легко ошибиться.

2.3 Типичные примеры, вводящие в заблуждение

Как выглядит строка Типичный поспешный вывод Как надо понимать на самом деле Пример
5f4dcc3b5aa765d61d8327deb882cf99 Точно MD5 Похоже на MD5, но не исключены MD4 / семейство NTLM или специфическое для приложения использование MD5 8846f7eaee8fb117ad06bdd830b7586c
64 hex-символа, например 2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824 Точно SHA-256 SHA-256 - кандидат, но возможны и SHA3-256 / SHA-512/256 / BLAKE2s-256 / BLAKE3 e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855
$6$rounds=5000$salt$hash Шестнадцатеричное представление SHA-512 На самом деле нет - это строка хеша пароля sha512crypt $6$rounds=5000$N3v8Kx2Lq9Rt$6LUcSUAELX3aC/.60pTB.TFLTQi1mOGRCwKqNCqtRSaXjorxj01HJ9oNni97Kci1uDt7a/Kn4t3OS20Dw/.vi1
{SHA}VBPuJHI7uixaa6LQGWx4s+5GKNE= Какой-то «SHA» В Apache / LDAP чаще всего означает digest SHA-1, закодированный в Base64 {SHA}VBPuJHI7uixaa6LQGWx4s+5GKNE=
{bcrypt}$2b$12$... Некая собственная схема {bcrypt} bcrypt с обёрткой Spring Security {bcrypt}$2b$12$9YQ2u/e5Y/ArOnG.gJKxK.0makLATcYLP1q.Nsabzrw7XErYCfoYO

3. Практический порядок определения

Теперь по шагам разберём, как реально смотреть на строку. Рекомендуемый порядок: префикс → разделитель → набор символов → длина → контекст.

3.1 Сначала смотрим на первые символы

Первые 1-10 символов уже сильно сужают круг кандидатов.

  • $argon2id$ / $argon2i$ / $argon2d$ Явно подозреваем PHC string format Argon2. Составляющие легко отследить по столбцу «Пример» в разделе 2.1.

  • $2a$ / $2b$ / $2y$ Явно подозреваем bcrypt.

  • $1$ / $5$ / $6$ / $7$ / $y$ Подозреваем хеш пароля семейства Unix crypt(3).

  • {SHA} / {SSHA} / {MD5} / {SMD5} Подозреваем представления семейства LDAP / Apache.

  • {bcrypt} / {pbkdf2} / {scrypt} Подозреваем формат хранения с меткой реализации, как в Spring Security.

Хитрость здесь в том, чтобы смотреть не только на сам алгоритм, но и на формат хранения. Например, $6$ - это не «digest SHA-512», а «строка хеша пароля, использующая SHA-512». Спутав это, вы собьёте с курса всё дальнейшее расследование.

3.2 Смотрим на количество разделителей

Далее смотрим на разделители вроде $, :, {}, ,, =.

  • Несколько символов $ Подозреваем формат, который вместе хранит параметры, salt и hash. Типичные примеры - Argon2, bcrypt, sha256crypt, sha512crypt.

  • Начинается с {name} Подозреваем обёртку с явно названной схемой, как в LDAP / Spring Security.

  • Форма вроде algo:salt:hash или algo$iterations$salt$hash Подозреваем формат, специфичный для фреймворка или приложения. Классический пример - pbkdf2_sha256$iterations$salt$hash в Django.

Чем больше разделителей в строке, тем легче определить схему. И наоборот, простой кусок шестнадцатеричной строки или Base64 без разделителей остаётся довольно неопределённым.

3.3 Смотрим на набор символов

Набор символов важен не меньше длины.

Шестнадцатеричное представление

Если строка состоит только из [0-9a-fA-F], сначала подозреваем шестнадцатеричное представление. В этом случае число символов ÷ 2 = длина в байтах.

  • 32 hex → 16 байт
  • 40 hex → 20 байт
  • 64 hex → 32 байта
  • 128 hex → 64 байта

Base64 / Base64url по RFC 4648

Наличие + / = заставляет заподозрить обычный Base64. Наличие - _ - Base64url. Padding-символ = иногда опускается, поэтому длина бывает «одной из двух возможных», например 43 / 44, 86 / 88.

radix64 crypt-семейства

Если встречаются . и /, да ещё и разделитель $...$, естественнее заподозрить не обычный Base64, а алфавит crypt-семейства. bcrypt, sha256crypt, sha512crypt, md5crypt, yescrypt, scrypt и им подобные используют именно этот набор символов.

Момент неброский, но очень действенный. Если решить, что «раз есть точка, значит это сломанный Base64», легко упустить bcrypt и семейство crypt(3).

3.4 Считаем длину

После набора символов смотрим на длину. Логика простая.

  • Для hex: длина в байтах = число символов / 2
  • Для Base64: число символов ≈ 4 × ceil(длина в байтах / 3) При этом опущенный padding = может укорачивать строку на 0-2 символа

На этом этапе круг кандидатов сужается. Но безопаснее не делать скачок вида «64 hex-символа, значит точно SHA-256».

3.5 Подтверждаем контекстом

В конце решающую роль играет контекст. Именно здесь можно приблизиться к стопроцентной уверенности.

  • Находится в /etc/shadow Подозреваем форматы паролей Linux вроде $y$, $6$, $5$, $1$

  • Находится в .htpasswd Подозреваем форматы семейства Apache вроде $apr1$, {SHA}, bcrypt

  • Находится в настройках Django или auth_user.password Подозреваем форматы Django вроде pbkdf2_sha256$... или argon2$...

  • Находится в таблице аутентификации Spring Security Подозреваем форматы с префиксом {id} вроде {bcrypt}... или {pbkdf2}...

  • 32 hex-символа встречается в контексте интеграции с SMB / AD Серьёзно рассматриваем семейство NTLM / MD4

На практике нередко быстрее посмотреть на продукт, фреймворк или имя файла конфигурации, откуда взялась строка, чем разбирать саму строку.

4. Частые ошибки определения

4.1 Жёстко фиксировать «64 hex = SHA-256»

Это встречается очень часто. SHA-256, конечно, сильный кандидат, но такой же 32-байтовый вывод дают несколько схем: SHA3-256, SHA-512/256, BLAKE2s-256, вывод BLAKE3 по умолчанию - все они той же длины.

Длина - материал для формирования круга кандидатов, а не для окончательного вывода.

4.2 Принимать $6$ за обычный SHA-512

$6$... - это префикс sha512crypt. Это не «шестнадцатеричный digest SHA-512», а строка хеша пароля, включающая salt и число раундов.

Аналогично:

  • $5$ - это sha256crypt
  • $1$ - это md5crypt

Наличие префикса уже означает, что перед вами не «просто digest».

4.3 Считать {SHA} то ли SHA-256, то ли SHA-512

В контексте Apache или LDAP {SHA} не означает расплывчато «что-то из семейства SHA». В большинстве случаев это означает digest SHA-1, закодированный в Base64. {SSHA} - это salted SHA-1.

Если обращаться с {SHA} расплывчато, как с «каким-то SHA», по одному внешнему виду, это приведёт к ошибкам в коде проверки и логике миграции.

4.4 Считать хеш пароля и хеш содержимого одним и тем же

Обе строки называются «хешем», но их назначение различается.

  • digest для проверки целостности файла;
  • digest для подписи API;
  • строка hash / KDF для хранения пароля.

Эти три вещи похожи внешне, но обращаться с ними нужно по-разному. В частности, хеш пароля часто включает в строку salt, число раундов, memory cost, parallelism и тому подобное, и подход «сравнить сырые digest» здесь не сработает.

4.5 Забывать про XOF и digest переменной длины

SHAKE128 / SHAKE256 - это XOF, поэтому длину вывода можно выбирать свободно. BLAKE2 тоже позволяет менять длину digest, а BLAKE3 обладает расширяемым выводом.

Иными словами, вывод «такая длина, значит такая схема» ошибается всякий раз, когда слишком полагается на предположение о классическом digest фиксированной длины.

5. Порядок проверки, если нужна стопроцентная уверенность

При миграции или интеграции аутентификации в конечном счёте нужна определённость. В этом случае проверка в следующем порядке снижает риск ошибки.

5.1 Определить источник хранения

Сначала устанавливаем, откуда взялась строка.

  • Linux shadow?
  • Basic auth Apache / Nginx?
  • LDAP?
  • Django / Spring Security?
  • БД собственного приложения?

Спецификация источника хранения часто оказывается более надёжным доказательством, чем сама строка.

5.2 Искать «формат хранения» в официальной документации

Далее ищем не название алгоритма, а формат хранения.

  • Django password format
  • Spring Security password storage format
  • crypt(5) sha512crypt format
  • Apache htpasswd password formats

Использование ключевых слов format / storage / encoding облегчает поиск.

5.3 Если есть известный открытый текст, сверить с кандидатами напрямую

Если есть тестовая учётная запись или известный открытый текст, быстрее всего вычислить значение по каждой из кандидатных схем и сравнить. Для хеша пароля при этом нужно извлечь salt и число раундов из строки и пересчитать заново.

5.4 Проверить код реализации или конфигурацию

Если исследуемая система - своя собственная, в конечном счёте надёжнее всего посмотреть на код и конфигурацию.

  • используемая библиотека;
  • настройки фреймворка;
  • опции, заданные при генерации;
  • способ кодирования вывода (hex / Base64 / Base64url / алфавит crypt).

Обычно этого достаточно, чтобы поставить точку.

5.5 На будущее - хранить с меткой схемы

Если вы сами проектируете систему на будущее, выбор формата, встраивающего название схемы в строку, сильно облегчит будущую миграцию.

  • PHC string format Argon2
  • {id}encodedPassword в Spring Security
  • algo$iterations$salt$hash в Django
  • форматы семейства Unix crypt(3) с префиксом

Так тому, кто будет смотреть на это позже, будет гораздо проще разобраться. И наоборот, дизайн, при котором в БД лежит «просто 64 hex-символа», недружелюбен к себе же самому в будущем.

6. Итог

При определении схемы по строковому представлению хеша удобно смотреть в таком порядке.

  1. Есть ли префикс
  2. Какие разделители используются
  3. Какой набор символов
  4. Скольким байтам соответствует длина
  5. Каков контекст источника хранения

Важнее всего два момента:

  • Форматы хранения с префиксом определяются довольно точно
  • Обычная шестнадцатеричная строка / Base64 часто дают лишь круг кандидатов

Поэтому практическое решение выглядит так.

  • Для $argon2id$..., $2b$..., $6$..., {SHA}..., pbkdf2_sha256$... одна строка уже даёт значительное продвижение
  • Для просто 32 / 40 / 64 / 128 hex-символов думаем «сужаем круг кандидатов», а не «выносим окончательный вердикт»
  • Если действительно нужна определённость, идём смотреть на продукт-источник, конфигурацию и реализацию

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

7. Услуги, связанные с этой темой

Техническая консультация и ревью архитектуры

Определение схемы хеша пароля, оставшегося в существующей БД, миграция платформы аутентификации, расследование логов в смешанных системах Windows / Web - всё это требует упорядочивания не только внешнего вида строки, но и реализации источника хранения, и политики миграции. Если рассматривать всё вместе - от определения схемы до проектирования миграции, - легче избежать ошибок.

Расследование сбоев и анализ причин

Нередко встречаются расследования, которые упираются в то, что «непонятно, что это за строка, и проверка не продвигается». Разграничение того, где именно определяется схема - в логах, конфигурационных файлах, схеме БД или реализации приложения, - заметно ускоряет установление причины.

8. Источники

  1. RFC 1321 - The MD5 Message-Digest Algorithm
  2. NIST FIPS 180-4 - Secure Hash Standard (SHA-1, SHA-2, SHA-512/224, SHA-512/256)
  3. NIST FIPS 202 - SHA-3 Standard: Permutation-Based Hash and Extendable-Output Functions
  4. PHC string format specification
  5. Argon2 reference implementation
  6. RFC 7693 - The BLAKE2 Cryptographic Hash and Message Authentication Code (MAC)
  7. BLAKE3 C README - default output length and extendable output
  8. crypt(5) - prefixes and hashed passphrase formats
  9. Apache HTTP Server 2.4 - Password Formats
  10. slappasswd(8) - RFC 2307 schemes such as {SHA} and {SSHA}
  11. Django documentation - example of pbkdf2_sha256$...
  12. Spring Security - DelegatingPasswordEncoder storage format {id}encodedPassword

Недавние статьи с теми же тегами помогут подробнее изучить близкие темы.

Что сделать перед утилизацией Windows PC — практический чек-лист по стиранию данных, отвязке учётных записей и резервному копированию

Разбираем, что нужно сделать перед утилизацией, передачей, продажей или возвратом по лизингу Windows PC: резервное копирование, стирание ...

Эти страницы показывают тему статьи в более широком контексте услуг и решений.

Статья напрямую связана со следующими услугами.

Частые вопросы

Вопросы, которые часто возникают при консультациях по теме статьи.

В каком порядке лучше смотреть, чтобы определить тип хеша по строке?
Удобнее смотреть в порядке: префикс → разделитель → набор символов → длина → контекст. Форматы, в которых первые несколько символов несут смысл, легко определить: $argon2id$ - явно Argon2, $2b$ - bcrypt, $5$ - sha256crypt, $6$ - sha512crypt. И наоборот, обычная шестнадцатеричная строка или Base64 без префикса позволяют лишь сузить круг кандидатов, а окончательно вопрос решается только после проверки продукта-источника, фреймворка и настроек.
Можно ли утверждать, что 64-символьная шестнадцатеричная строка - это SHA-256?
Категорично утверждать это опасно. SHA-256 - сильный кандидат, но такой же 32-байтовый вывод дают и другие схемы - SHA3-256, SHA-512/256, BLAKE2s-256, вывод BLAKE3 по умолчанию, - и по одной лишь длине их не различить. Длина - материал для формирования круга кандидатов, а не для окончательного вывода. Если нужна определённость, необходимо дойти до проверки спецификации источника, формата хранения из официальной документации, сверки с известным открытым текстом и проверки кода реализации или конфигурации.
Строка, начинающаяся с $6$, - это хеш SHA-512?
Нет. $6$ - это префикс формата хранения паролей sha512crypt из мира Unix. Это не сам по себе шестнадцатеричный digest SHA-512, а строка хеша пароля, содержащая salt и число раундов. Аналогично $5$ означает sha256crypt, а $1$ - md5crypt. Наличие префикса уже означает, что перед вами не «просто digest», и путаница здесь приводит к ошибкам в миграции и коде проверки.
Что можно понять по набору символов в строке?
Набор символов - подсказка не менее важная, чем длина. Если строка состоит только из символов 0-9a-fA-F, это шестнадцатеричное представление, и половина количества символов - это длина в байтах. Наличие + / = говорит о Base64 по RFC 4648, а - и _ - о Base64url. Если встречаются точка и слеш, а разделителем служит $, естественнее заподозрить не обычный Base64, а алфавит crypt-семейства - как в bcrypt или sha512crypt. Если принять точку в строке за признак «сломанного» Base64, легко упустить из виду форматы crypt-семейства.

Об авторе

Страница с профилем автора статьи.

Го Комура

Представитель KomuraSoft LLC

Специализируется на разработке программного обеспечения для Windows, техническом консалтинге и расследовании сбоев, особенно в проектах с унаследованными системами и трудно воспроизводимыми ошибками.

Публичные ссылки

Вернуться в блог