Всем привет! В этой серии статей я бы хотел разобрать тонкости работы с GPG, которые по моему недостаточно освещены в интернете. Сегодня я вам расскажу про подписи(и немного затронем модель Web of trust). Я бы их поделил на 2 вида: подписи файлов и подписи ключей.

Подписи файлов

Для начала разберем пример:

Боб хочет передать Алисе некое сообщение (нас сейчас не волнует что оно в открытом виде). Боб по каналу связи передает своё сообщение и его чексумму. Алиса получает сообщение, высчитывает чексумму и если она совпадает с тей которую прислал Боб - значит сообщение, СКОРЕЕ ВСЕГО, не побито. Но давайте посмотрим что будет если появится человек посередине - Ева. Она может перехватить сообщение Боба и изменить его, а потом просто пересчитать чексумму получившегося сообщения и отправить Алисе. Алиса же проверит сообщения и не найдет подмены.

Как же нам такое исправить? Нам помогут самые распространенные и простые для понимания подписи - это подписи файлов, зачем они нужны? Такие подписи выполняют 2 важные задачи: проверяют целостность файла при отправке (с чем справлялись и простые чексуммы) и проверяют подлинность отправителя. Я предлагаю более подробно разобрать 2 предназначение. С помощью подписи можно проверить что некий файл был действительно отправлен владельцем ключа и что его не подменили.

Разберём на 2 примере:

Условимся что у Боба и Алисы есть наборы GPG-ключей друг-друга. Боб, как и раньше, передает Алисе сообщения, НО вместо чексуммы он передает с сообщениям подпись, сделанную его приватным ключом. Алиса проверяет публичным ключом Боба подпись сообщения. В данном случае Ева тут бессильна, потому что после изменения сообщения подпись станет недействительной, а чтобы её подделать - нужен приватный ключ Боба

Блин, сухая теория, давайте переходить к практике. Создаем новый ключ, записываем файл и подписываем:

❯ gpg --full-gen-key
gpg (GnuPG) 2.4.8; Copyright (C) 2025 g10 Code GmbH
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
Выберите тип ключа:   (1) RSA and RSA   (2) DSA and Elgamal   (3) DSA (sign only)   (4) RSA (sign only)   (9) ECC (sign and encrypt) *default*  (10) ECC (только для подписи)  (14) Existing key from card
Ваш выбор? 1
длина ключей RSA может быть от 1024 до 4096.
Какой размер ключа Вам необходим? (3072) 4096
Запрошенный размер ключа - 4096 бит
Выберите срок действия ключа.         0 = не ограничен        = срок действия ключа - n дней      w = срок действия ключа - n недель      m = срок действия ключа - n месяцев      y = срок действия ключа - n лет
Срок действия ключа? (0) 1y
Ключ действителен до Вт 25 авг 2026 22:36:06 EEST
Все верно? (y/N) y
GnuPG должен составить идентификатор пользователя для идентификации ключа.
Ваше полное имя: David
Адрес электронной почты: daika100@habr.com
Примечание: Вы выбрали следующий идентификатор пользователя:    "David "
Сменить (N)Имя, (C)Примечание, (E)Адрес; (O)Принять/(Q)Выход? o
Необходимо получить много случайных чисел. Желательно, чтобы Вы
в процессе генерации выполняли какие-то другие действия (печать
на клавиатуре, движения мыши, обращения к дискам); это даст генератору
случайных чисел больше возможностей получить достаточное количество энтропии.
Необходимо получить много случайных чисел. Желательно, чтобы Вы
в процессе генерации выполняли какие-то другие действия (печать
на клавиатуре, движения мыши, обращения к дискам); это даст генератору
случайных чисел больше возможностей получить достаточное количество энтропии.
gpg: создан каталог '/home/david/.gnupg/openpgp-revocs.d'
gpg: сертификат отзыва записан в '/home/david/.gnupg/openpgp-revocs.d/FB3A670DF78954124EB2DF68EF1515E8722BAB9D.rev'.
открытый и секретный ключи созданы и подписаны.
pub   rsa4096 2025-08-25 [SC] [   годен до: 2026-08-25]      FB3A670DF78954124EB2DF68EF1515E8722BAB9D
uid                      David sub   rsa4096 2025-08-25 [E] [   годен до: 2026-08-25]
❯ echo "test hi" > msg
❯ gpg --sign -u daika100@habr.com msg
❯ cat msg.gpg
�X���
��r+���bmsgh��0test hi
�3
# и еще много байт

Как видим gpg создал файл с какой-то кашей, это наше сжатое сообщение + подпись.

ЭТОТ ФАЙЛ СОДЕРЖИТ НЕ ЗАШИФРОВАННОЕ СООБЩЕНИЕ, ПРОСТО СЖАТОЕ

Давайте попробуем ASCII-формат:

❯ gpg --armor --sign -u daika100@habr.com msg
❯ cat msg.asc
-----BEGIN PGP MESSAGE-----
owEBWAKn/ZANAwAKAe8VFehyK6udAawRYgNtc2dorL78dGVzdCBoaQqJAjMEAAEK
AB0WIQT7OmcN94lUEk6y32jvFRXociurnQUCaKy+/AAKCRDvFRXociurnX+oEAC4
5QpavKF6yNNOmPRLr4UQ4FfVciqfWeGRgq9FEtSmUkishzwjuNw9+UNG8aH7BlUg
# обрезано для легкости чтения
-----END PGP MESSAGE-----

Видим стандартный ASCII-формат GPG. Но такой подход не совсем правильный, более правильно использовать --clear-sign:

❯ gpg --clear-sign -u daika100@habr.com msg
❯ ❯ cat msg.asc
-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA512
test hi
-----BEGIN PGP SIGNATURE-----
iQIzBAEBCgAdFiEE+zpnDfeJVBJOst9o7xUV6HIrq50FAmiuCY0ACgkQ7xUV6HIr
q53MQhAAgFzLJsLLplWzeclgKPKf3oqZ9h/aN/E33ii1UDp7Bq1Hy6nxzlje+uqr
T5iRzOymMpdFYBi44/xkYNUj3xWaKkuvae2suNrjE5RP9tqRw1vDKGZtn/vP3T4p
# тоже обрезано
-----END PGP SIGNATURE-----

Таким образом оригинальное сообщение легче прочитать. Давайте пробовать вытащить с предыдущих вариантов данные и проверим их:

❯ gpg --verify msg.asc
gpg: Подпись сделана Пн 25 авг 2025 22:52:28 EEST
gpg:                ключом RSA с идентификатором FB3A670DF78954124EB2DF68EF1515E8722BAB9D
gpg: Действительная подпись пользователя "David " [абсолютное]
❯ gpg -d msg.asc
test hi
gpg: Подпись сделана Пн 25 авг 2025 22:52:28 EEST
gpg:                ключом RSA с идентификатором FB3A670DF78954124EB2DF68EF1515E8722BAB9D
gpg: Действительная подпись пользователя "David " [абсолютное]

Как видим для gpg достаточно просто команды для расшифровки: gpg -d - но для наглядности я показал, как и просто проверить целостность. Здорово, но если мы публикуем программу и не уверены будут ли все заниматься проверкой подписи? Для этого есть dettach-sign, т.е отсоединенная подпись - подпись в отдельном файле:

❯ gpg --detach-sign -u daika100@habr.com msg
❯ cat msg.sig
�3
,�TN��h��r+��h�	��r+�����T�k��n��P%;ەY�gW�W,fd_��݁��^.ٕ�t����
# обрезано для легкости чтения

Делаем armored:

❯ gpg --armor --detach-sign -u daika100@habr.com msg
❯ cat msg.asc
-----BEGIN PGP SIGNATURE-----
iQIzBAABCgAdFiEE+zpnDfeJVBJOst9o7xUV6HIrq50FAmiuDdwACgkQ7xUV6HIr
q51wxQ/9EG2Asn6aQ87ST1wm3z2F7qLNxuUQ8m4y8dTxq/etUxLXaPwgij8Eae2G
# обрезано для легкости чтения
BjUv9ctxbGYh9ctes6KVZVHINR16U3VmXTGdG5DSHUBcvCKb3VM=
=yBZq
-----END PGP SIGNATURE-----

Видим похожую картину которая была при --clear-sign. Оно и не удивительно, можно сказать что --clear-sign просто сшивает armored detach sign и оригинальный файл в определенный формат. Как проверять такую подпись? Есть два синтаксиса: --verify file.sig либо --verify file.sig file. Второй используется когда имя подписи не повторяет имя файла или она находится в другом месте. Пробуем:

❯ gpg --verify msg.asc
gpg: предполагается, что подписанные данные находятся в 'msg'
gpg: Подпись сделана Ср 27 авг 2025 20:24:35 EEST
gpg:                ключом RSA с идентификатором FB3A670DF78954124EB2DF68EF1515E8722BAB9D
gpg: Действительная подпись пользователя "David " [абсолютное]
❯ gpg --verify msg.asc msg
gpg: Подпись сделана Ср 27 авг 2025 20:24:35 EEST
gpg:                ключом RSA с идентификатором FB3A670DF78954124EB2DF68EF1515E8722BAB9D
gpg: Действительная подпись пользователя "David " [абсолютное]

Работает идентично. А если сообщение изменить? Давайте проверим:

❯ cat msg
test hi
❯ echo ! >> msg
❯ cat msg
test hi
!
❯ gpg --verify msg.asc
gpg: предполагается, что подписанные данные находятся в 'msg'
gpg: Подпись сделана Ср 27 авг 2025 20:24:35 EEST
gpg:                ключом RSA с идентификатором FB3A670DF78954124EB2DF68EF1515E8722BAB9D
gpg: ПЛОХАЯ подпись пользователя "David " [абсолютное]

О, как видим GPG сразу заметил изменения даже двух байт! Такому файлу не стоит доверять.

Все это хорошо, но до этого мы проверяли свои подписи и у нас был приватный ключ. Но мы же тут собрались проверять чужие сообщения! Я удалю ключи и экспортирую только публичный:

❯ gpg -o key.pub --export daika100@habr.com
❯ gpg -o key --export-secret-keys daika100@habr.com
❯ gpg --delete-secret-keys daika100@habr.com
gpg (GnuPG) 2.4.8; Copyright (C) 2025 g10 Code GmbH
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
sec  rsa4096/EF1515E8722BAB9D 2025-08-25 David Удалить данный ключ из таблицы? (y/N) y
Это секретный ключ! - все равно удалить? (y/N) y
❯ gpg --delete-keys daika100@habr.com
gpg (GnuPG) 2.4.8; Copyright (C) 2025 g10 Code GmbH
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
pub  rsa4096/EF1515E8722BAB9D 2025-08-25 David Удалить данный ключ из таблицы? (y/N) y
❯ gpg --import key.pub
gpg: ключ EF1515E8722BAB9D: импортирован открытый ключ "David "
gpg: Всего обработано: 1
gpg:                  импортировано: 1
❯ gpg --verify msg.asc
gpg: предполагается, что подписанные данные находятся в 'msg'
gpg: Подпись сделана Ср 27 авг 2025 20:24:35 EEST
gpg:                ключом RSA с идентификатором FB3A670DF78954124EB2DF68EF1515E8722BAB9D
gpg: Действительная подпись пользователя "David " [неизвестно]
gpg: Внимание: Данный ключ не заверен доверенной подписью!
gpg:           Нет указаний на то, что подпись принадлежит владельцу.
Отпечаток первичного ключа: FB3A 670D F789 5412 4EB2  DF68 EF15 15E8 722B AB9D

Опа! Подпись действительна, но появилось предупреждение о том что ключ не заверен доверенной подписью. Что ж давайте разбираться!

Подпись и доверия к ключам

Как и в предыдущем разделе начнем с примера:

В предыдущий раз, мы предположили, что у Боба и Алисы есть ключи друг-друга, но что если это не так? Самое простое это Бобу просто передать Алисе свой ключ. А что если посередине опять будет Ева? Она может сгенерировать свой ключ с таким же идентификатором какой только что получила и отправить Алисе. Тогда когда Боб отправит сообщение, Ева его проверит перехватившим ключом, при нужде изменит, подпишет и отправит Алисе с новой подписью.

То есть слепо доверять ключам нельзя, что же делать? Самое простое решение, которое рекомендует проект GNU - это встретится лично с человеком, проверить его паспортные данные, взять почту и отпечаток ключа, прийти домой, по отпечатку скачать ключ с key servers, проверить все данные ключа, подписать ключ и отправить владельцу... Стоп! Подписать ключ? Да! Но перед тем как понять что это, давайте вспомним HTTPS. Когда мы идем на сайт с шифрованием, он нам предоставляет свой сертификат заверенный заверочным центром, сертификат того тоже кем-то заверен и так до корневых центров, сертификатам которых все доверяют. Такая модель называется иерархической.

В GPG используется другая модель под названием Web of Trust (сеть доверия). Её суть заключается в том что здесь нет корневых центров, вместо них используются подписи людей, которым ты доверяешь. Существуют 5 видов доверия:

  1. Не знаю или не буду отвечать

  2. НЕ доверяю

  3. Доверяю ограниченно

  4. Полностью доверяю

  5. Абсолютно доверяю

Первые 2 говорят GPG не брать до внимания подписи этих ключей в проверке доверия. 3 уровень - это доверия 50/50, т.е, чтобы доверять ключу с такими подписями надо их несколько(по умолчанию 3). 4 - полное доверия, надо одна такая подпись. Абсолютное доверия - также как 4, но оно только на твоем ключе... Сказала бы очередная статья, но мы же лучше! Поэтому давайте разберем что такое абсолютное доверия.

Выше я назвал не уровни доверия к личности владельцев ключей(что было бы тупо), А УРОВНИ ДОВЕРИЯ К ПОДПИСЯМ ЛИЧНОСТИ ВЛАДЕЛЬЦЕВ ДРУГИХ КЛЮЧЕЙ. Было бы странно как-то доверять подписи другого ключа, если ты не доверяешь личности владельца ключа.

Разберём пример: у нас есть ключ Толи, но мы не уверены действительно ли это его ключ, поэтому мы смотрим на подписи, там Анна, Максим и Коля, подписям которых мы доверяем ограничено, мы смотрим на подписи их ключей и так далее пока не доходим до подписи, которой мы абсолютно доверяем.

Вы, наверное, увидели, что GPG буквально строит взвешенный, направленный граф(дерево, называйте как хотите), чтобы проверить личность ключа.

Поэтому не стоит ставить абсолютное доверия чужому ключу. Его могут скомпрометировать, отозвать и т.д. А Вы не успеете об этом узнать и будет печаль. На чужие ключи ставьте в большинстве случаев ограниченное доверия, тогда если украдут один, не смогут заставить Вас доверять другим. В этом решении есть еще один плюс - если один или два человека захотят сделать что-то не то с сетью, они просто не смогут этого сделать.

И так что делать нашим друзьям - Алисе и Бобу:

Они могут найти общих друзей(теория 7 рукопожатий), проверить их ключи, подписать их и попросить чтобы те подписали ключи Алисы и Боба, обменяться подписанными ключами. Дело в шляпе! Теперь никакая Ева им не страшна!

Давайте исправим нашу ошибку:

 gpg --gen-key
gpg (GnuPG) 2.4.8; Copyright (C) 2025 g10 Code GmbH
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
Замечание: "gpg --full-generate-key" вызывает полнофункциональный диалог создания ключа.
GnuPG должен составить идентификатор пользователя для идентификации ключа.
Ваше полное имя: Alice
Адрес электронной почты: Вы выбрали следующий идентификатор пользователя:    "Alice"
Сменить (N)Имя, (E)Адрес; (O)Принять/(Q)Выход? o
Необходимо получить много случайных чисел. Желательно, чтобы Вы
в процессе генерации выполняли какие-то другие действия (печать
на клавиатуре, движения мыши, обращения к дискам); это даст генератору
случайных чисел больше возможностей получить достаточное количество энтропии.
Необходимо получить много случайных чисел. Желательно, чтобы Вы
в процессе генерации выполняли какие-то другие действия (печать
на клавиатуре, движения мыши, обращения к дискам); это даст генератору
случайных чисел больше возможностей получить достаточное количество энтропии.
gpg: сертификат отзыва записан в '/home/david/.gnupg/openpgp-revocs.d/85CDE7C586469C88071699079B9A4D44CEEF73B6.rev'.
открытый и секретный ключи созданы и подписаны.
pub   ed25519 2025-08-28 [SC] [   годен до: 2028-08-27]      85CDE7C586469C88071699079B9A4D44CEEF73B6
uid                      Alice
sub   cv25519 2025-08-28 [E] [   годен до: 2028-08-27]
❯ gpg --edit-key daika100@habr.com
gpg (GnuPG) 2.4.8; Copyright (C) 2025 g10 Code GmbH
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
pub  rsa4096/EF1515E8722BAB9D          создан: 2025-08-25     годен до: 2026-08-25  назначение: SC       доверие: неизвестно достоверность: неизвестно
sub  rsa4096/0A5672421EFF6B2C          создан: 2025-08-25     годен до: 2026-08-25  назначение: E   [ неизвестно ] (1). David gpg> sign -u Alice
pub  rsa4096/EF1515E8722BAB9D          создан: 2025-08-25     годен до: 2026-08-25  назначение: SC       доверие: неизвестно достоверность: неизвестно Отпечаток первичного ключа: FB3A 670D F789 5412 4EB2  DF68 EF15 15E8 722B AB9D     David Срок действия данного ключа истекает 2026-08-25.
Вы уверены, что хотите подписать этот ключ
своим ключом "Alice" (9B9A4D44CEEF73B6)?
Действительно подписать? (y/N) y
gpg> save
❯ gpg --verify msg.asc
gpg: Подпись сделана Вт 26 авг 2025 22:22:53 EEST
gpg:                ключом RSA с идентификатором FB3A670DF78954124EB2DF68EF1515E8722BAB9D
gpg: проверка таблицы доверия
gpg: marginals needed: 3  completes needed: 1  trust model: pgp
gpg: глубина: 0  достоверных:   2  подписанных:   8  доверие: 0-, 0q, 0n, 0m, 0f, 2u
gpg: глубина: 1  достоверных:   8  подписанных:   3  доверие: 3-, 0q, 0n, 1m, 4f, 0u
gpg: глубина: 2  достоверных:   3  подписанных:   1  доверие: 3-, 0q, 0n, 0m, 0f, 0u
gpg: срок следующей проверки таблицы доверия 2026-06-08
gpg: Действительная подпись пользователя "David " [полное]

Готово! Ошибка исчезла. Мы подписали абсолютным ключом наш и мы ему доверяем.

Практика

На последок давайте правильно скачаем какой-то подписанный файл и проверим его. Я возьму wofi-power-menu:

❯ wget https://github.com/szaffarano/wofi-power-menu/releases/download/v0.3.1/wofi-power-menu-linux-x64
❯ wget https://github.com/szaffarano/wofi-power-menu/releases/download/v0.3.1/wofi-power-menu-linux-x64.asc
❯ gpg --recv-keys 42BE68F43D528467FC281E2E310FFE86A2E427BA
gpg: ключ 310FFE86A2E427BA: импортирован открытый ключ "Sebastián Zaffarano (Releases) "
gpg: Всего обработано: 1
gpg:                  импортировано: 1
❯ gpg -u daika100@habr.com --lsign-key 42BE68F43D528467FC281E2E310FFE86A2E427BA
pub  ed25519/310FFE86A2E427BA          создан: 2025-08-16     годен до: никогда     назначение: SC       доверие: неизвестно достоверность: неизвестно
[ неизвестно ] (1). Sebastián Zaffarano (Releases) pub  ed25519/310FFE86A2E427BA          создан: 2025-08-16     годен до: никогда     назначение: SC       доверие: неизвестно достоверность: неизвестно Отпечаток первичного ключа: 42BE 68F4 3D52 8467 FC28  1E2E 310F FE86 A2E4 27BA     Sebastián Zaffarano (Releases) Вы уверены, что хотите подписать этот ключ
своим ключом "David " (EF1515E8722BAB9D)?
Подпись будет помечена как неэкспортируемая.
Действительно подписать? (y/N) y
❯ gpg --verify wofi-power-menu-linux-x64.asc
gpg: предполагается, что подписанные данные находятся в 'wofi-power-menu-linux-x64'
gpg: Подпись сделана Сб 16 авг 2025 13:49:30 EEST
gpg:                ключом EDDSA с идентификатором 42BE68F43D528467FC281E2E310FFE86A2E427BA
gpg: проверка таблицы доверия
gpg: marginals needed: 3  completes needed: 1  trust model: pgp
gpg: глубина: 0  достоверных:   2  подписанных:   7  доверие: 0-, 0q, 0n, 0m, 0f, 2u
gpg: глубина: 1  достоверных:   7  подписанных:   3  доверие: 2-, 0q, 0n, 1m, 4f, 0u
gpg: глубина: 2  достоверных:   3  подписанных:   1  доверие: 3-, 0q, 0n, 0m, 0f, 0u
gpg: срок следующей проверки таблицы доверия 2026-06-08
gpg: Действительная подпись пользователя "Sebastián Zaffarano (Releases) " [полное]

Готово! Все целое! Заметьте перед подписанием я проверил все данные.

Заключение

Я рассказал о правильном использовании одной из возможностей GPG. Надеюсь Вы теперь будете правильно проверять подписи программ и не ставить абсолютное доверия ключам условного GitHub. Всем добра и мира!

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