diff --git a/content/posts/2023/c/sockets.md b/content/posts/2023/c/sockets.md new file mode 100644 index 0000000..03f4134 --- /dev/null +++ b/content/posts/2023/c/sockets.md @@ -0,0 +1,516 @@ +--- +title: "↔️ Программирование сокетов в Linux" +date: 2023-06-18T17:52:44+03:00 +draft: true +tags: [c, development, linux] +--- + +> Автор: Александр Шаргин +> Опубликовано: 16.05.2001 +> Исправлено: 04.02.2006 +> Версия текста: 1.1 +> Оригинальная страница: https://rsdn.org/article/unix/sockets.xml + +## Введение + +**Socket API** был впервые реализован в операционной системе Berkley UNIX. +Сейчас этот программный интерфейс доступен практически в любой модификации Unix, +в том числе в Linux. Хотя все реализации чем-то отличаются друг от друга, +основной набор функций в них совпадает. +Изначально сокеты использовались в программах на C/C++, +но в настоящее время средства для работы с ними предоставляют многие языки (Perl, Java и др.). + +Сокеты предоставляют весьма мощный и гибкий механизм межпроцессного взаимодействия (IPC). +Они могут использоваться для организации взаимодействия программ на одном компьютере, +по локальной сети или через Internet, что позволяет вам создавать распределённые приложения +различной сложности. Кроме того, с их помощью можно организовать взаимодействие с программами, +работающими под управлением других операционных систем. +Например, под Windows существует интерфейс Window Sockets, +спроектированный на основе socket API. Ниже мы увидим, насколько легко можно +адаптировать существующую Unix-программу для работы под Windows. + +Сокеты поддерживают многие стандартные сетевые протоколы +(конкретный их список зависит от реализации) +и предоставляют унифицированный интерфейс для работы с ними. +Наиболее часто сокеты используются для работы в IP-сетях. +В этом случае их можно использовать для взаимодействия приложений +не только по специально разработанным, но и по стандартным протоколам - +HTTP, FTP, Telnet и т. д. Например, вы можете написать собственный Web-браузер или Web-сервер, +способный обслуживать одновременно множество клиентов. + +Как видим, сокеты - весьма мощное и удобное средство для сетевого программирования. +В этой статье я покажу, как ими пользоваться. Начав с понятия сокета +и самых основных функций для работы с ним, мы постепенно перейдём +к обсуждению более сложных тем. В частности, мы рассмотрим использование низкоуровневых сокетов, +различные способы организации параллельного обслуживания клиентов, +использование стандартных протоколов Internet и взаимодействие с программами, +работающими под управлением операционной системы Microsoft Windows. + +**ПРИМЕЧАНИЕ** + +Большая часть материала, изложенного в статье, применимо ко всему семейству ОС Unix. +Тем не менее, все приводимые далее факты и демонстрационные программы проверялись +только под Linux, поэтому название этой ОС и вынесено в заголовок статьи. + +## Основы socket API + +### Понятие сокета +Сокет (socket) - это конечная точка сетевых коммуникаций. +Он является чем-то вроде "портала", через которое можно отправлять байты во внешний мир. +Приложение просто пишет данные в сокет; их дальнейшая буферизация, +отправка и транспортировка осуществляется используемым стеком протоколов и сетевой аппаратурой. +Чтение данных из сокета происходит аналогичным образом. + +В программе сокет идентифицируется дескриптором - это просто переменная типа `int`. +Программа получает дескриптор от операционной системы при создании сокета, +а затем передаёт его сервисам socket API для указания сокета, +над которым необходимо выполнить то или иное действие. + +### Атрибуты сокета +С каждым сокет связываются три атрибута: `домен`, `тип` и `протокол`. +Эти атрибуты задаются при создании сокета и остаются неизменными +на протяжении всего времени его существования. +Для создания сокета используется функция `socket`, имеющая следующий прототип. + +```c +#include +#include + +int socket(int domain, int type, int protocol); +``` + +Домен определяет пространство адресов, в котором располагается сокет, +и множество протоколов, которые используются для передачи данных. +Чаще других используются домены Unix и Internet, задаваемые константами +`AF_UNIX` и `AF_INET` соответственно +(префикс AF означает "address family" - "семейство адресов"). +При задании `AF_UNIX` для передачи данных используется файловая система ввода/вывода Unix. +В этом случае сокеты используются для межпроцессного взаимодействия +на одном компьютере и не годятся для работы по сети. +Константа `AF_INET` соответствует Internet-домену. +Сокеты, размещённые в этом домене, могут использоваться для работы в любой IP-сети. +Существуют и другие домены (`AF_IPX` для протоколов Novell, +`AF_INET6` для новой модификации протокола IP - IPv6 и т. д.), +но в этой статье мы не будем их рассматривать. + +Тип сокета определяет способ передачи данных по сети. Чаще других применяются: + +- `SOCK_STREAM`. Передача потока данных с предварительной установкой соединения. +Обеспечивается надёжный канал передачи данных, при котором фрагменты отправленного блока +не теряются, не переупорядочиваются и не дублируются. +Поскольку этот тип сокетов является самым распространённым, +до конца раздела мы будем говорить только о нём. +Остальным типам будут посвящены отдельные разделы. +- `SOCK_DGRAM`. Передача данных в виде отдельных сообщений (датаграмм). +Предварительная установка соединения не требуется. Обмен данными происходит быстрее, +но является ненадёжным: сообщения могут теряться в пути, дублироваться и переупорядочиваться. +Допускается передача сообщения нескольким получателям (multicasting) +и широковещательная передача (broadcasting). +- `SOCK_RAW`. Этот тип присваивается низкоуровневым (т. н. "сырым") сокетам. +Их отличие от обычных сокетов состоит в том, что с их помощью программа может +взять на себя формирование некоторых заголовков, добавляемых к сообщению. + +Обратите внимание, что не все домены допускают задание произвольного типа сокета. +Например, совместно с доменом Unix используется только тип `SOCK_STREAM`. +С другой стороны, для Internet-домена можно задавать любой из перечисленных типов. +В этом случае для реализации `SOCK_STREAM` используется протокол TCP, +для реализации `SOCK_DGRAM` - протокол UDP, +а тип `SOCK_RAW` используется для низкоуровневой работы с протоколами IP, ICMP и т. д. + +Наконец, последний атрибут определяет протокол, используемый для передачи данных. +Как мы только что видели, часто протокол однозначно определяется по домену и типу сокета. +В этом случае в качестве третьего параметра функции `socket` можно передать 0, +что соответствует протоколу по умолчанию. Тем не менее, иногда +(например, при работе с низкоуровневыми сокетами) требуется задать протокол явно. +Числовые идентификаторы протоколов зависят от выбранного домена; +их можно найти в документации. + +### Адреса + +Прежде чем передавать данные через сокет, его необходимо связать с адресом в выбранном домене +(эту процедуру называют именованием сокета). Иногда связывание осуществляется неявно +(внутри функций `connect` и `accept`), но выполнять его необходимо во всех случаях. +Вид адреса зависит от выбранного вами домена. +В Unix-домене это текстовая строка - имя файла, через который происходит обмен данными. +В Internet-домене адрес задаётся комбинацией IP-адреса и 16-битного номера порта. +IP-адрес определяет хост в сети, а порт - конкретный сокет на этом хосте. +Протоколы TCP и UDP используют различные пространства портов. + +Для явного связывания сокета с некоторым адресом используется функция `bind`. +Её прототип имеет вид: + +```c +#include +#include + +int bind(int sockfd, struct sockaddr *addr, int addrlen); +``` + +В качестве первого параметра передаётся дескриптор сокета, +который мы хотим привязать к заданному адресу. +Второй параметр, `addr`, содержит указатель на структуру с адресом, +а третий - длину этой структуры. Посмотрим, что она собой представляет. + +```c +struct sockaddr { + unsigned short sa_family; // Семейство адресов, AF_xxx + char sa_data[14]; // 14 байтов для хранения адреса +}; +``` + +Поле `sa_family` содержит идентификатор домена, тот же, что и первый параметр функции `socket`. +В зависимости от значения этого поля по-разному интерпретируется содержимое массива `sa_data`. +Разумеется, работать с этим массивом напрямую не очень удобно, +поэтому вы можете использовать вместо `sockaddr` одну из альтернативных структур вида +`sockaddr_XX` (`XX` - суффикс, обозначающий домен: `un` - Unix, `in` - Internet и т. д.). +При передаче в функцию `bind` указатель на эту структуру приводится к указателю на `sockaddr`. +Рассмотрим для примера структуру `sockaddr_in`. + +```c +struct sockaddr_in { + short int sin_family; // Семейство адресов + unsigned short int sin_port; // Номер порта + struct in_addr sin_addr; // IP-адрес + unsigned char sin_zero[8]; // "Дополнение" до размера структуры sockaddr +}; +``` + +Здесь поле `sin_family` соответствует полю `sa_family` в `sockaddr`, +в `sin_port` записывается номер порта, а в `sin_addr` - IP-адрес хоста. +Поле `sin_addr` само является структурой, которая имеет вид: + +```c +struct in_addr { + unsigned long s_addr; +}; +``` + +Зачем понадобилось заключать всего одно поле в структуру? +Дело в том, что раньше `in_addr` представляла собой объединение (union), +содержащее гораздо большее число полей. Сейчас, когда в ней осталось всего одно поле, +она продолжает использоваться для обратной совместимости. + +И ещё одно важное замечание. Существует два порядка хранения байтов в слове и двойном слове. +Один из них называется порядком хоста (host byte order), +другой - сетевым порядком (network byte order) хранения байтов. +При указании IP-адреса и номера порта необходимо преобразовать число +из порядка хоста в сетевой. Для этого используются функции +`htons` (Host TO Network Short) и `htonl` (Host TO Network Long). +Обратное преобразование выполняют функции `ntohs` и `ntohl`. + +**ПРИМЕЧАНИЕ** + +На некоторых машинах (к PC это не относится) порядок хоста и сетевой порядок хранения байтов +совпадают. Тем не менее, функции преобразования лучше применять и там, +поскольку это улучшит переносимость программы. +Это никак не скажется на производительности, так как препроцессор сам уберёт +все "лишние" вызовы этих функций, оставив их только там, +где преобразование действительно необходимо. + +### Установка соединения (сервер) + +Установка соединения на стороне сервера состоит из четырёх этапов, +ни один из которых не может быть опущен. Сначала сокет создаётся и привязывается +к локальному адресу. Если компьютер имеет несколько сетевых интерфейсов +с различными IP-адресами, вы можете принимать соединения только с одного из них, +передав его адрес функции `bind`. Если же вы готовы соединяться с клиентами +через любой интерфейс, задайте в качестве адреса константу `INADDR_ANY`. +Что касается номера порта, вы можете задать конкретный номер +или 0 (в этом случае система сама выберет произвольный неиспользуемый +в данный момент номер порта). + +На следующем шаге создаётся очередь запросов на соединение. +При этом сокет переводится в режим ожидания запросов со стороны клиентов. +Всё это выполняет функция `listen`. + +```c +int listen(int sockfd, int backlog); +``` + +Первый параметр - дескриптор сокета, а второй задаёт размер очереди запросов. +Каждый раз, когда очередной клиент пытается соединиться с сервером, +его запрос ставится в очередь, так как сервер может быть занят обработкой других запросов. +Если очередь заполнена, все последующие запросы будут игнорироваться. +Когда сервер готов обслужить очередной запрос, он использует функцию `accept`. + +```c +#include + +int accept(int sockfd, void *addr, int *addrlen); +``` + +Функция `accept` создаёт для общения с клиентом новый сокет и возвращает его дескриптор. +Параметр `sockfd` задаёт слушающий сокет. После вызова он остаётся +в слушающем состоянии и может принимать другие соединения. +В структуру, на которую ссылается `addr`, записывается адрес сокета клиента, +который установил соединение с сервером. В переменную, адресуемую указателем `addrlen`, +изначально записывается размер структуры; +функция `accept` записывает туда длину, которая реально была использована. +Если вас не интересует адрес клиента, вы можете просто передать `NULL` +в качестве второго и третьего параметров. + +Обратите внимание, что полученный от `accept` новый сокет связан с тем же самым адресом, +что и слушающий сокет. Сначала это может показаться странным. +Но дело в том, что адрес TCP-сокета не обязан быть уникальным в Internet-домене. +Уникальными должны быть только соединения, для идентификации которых используются +два адреса сокетов, между которыми происходит обмен данными. + +### Установка соединения (клиент) + +На стороне клиента для установления соединения используется функция `connect`, +которая имеет следующий прототип. + +```c +#include +#include + +int connect(int sockfd, struct sockaddr *serv_addr, int addrlen); +``` + +Здесь `sockfd` - сокет, который будет использоваться для обмена данными с сервером, +`serv_addr` содержит указатель на структуру с адресом сервера, +а `addrlen` - длину этой структуры. Обычно сокет не требуется предварительно привязывать +к локальному адресу, так как функция `connect` сделает это за вас, +подобрав подходящий свободный порт. Вы можете принудительно назначить +клиентскому сокету некоторый номер порта, используя `bind` перед вызовом `connect`. +Делать это следует в случае, когда сервер соединяется с только с клиентами, +использующими определённый порт (примерами таких серверов являются `rlogind` и `rshd`). +В остальных случаях проще и надёжнее предоставить системе выбрать порт за вас. + +### Обмен данными + +После того как соединение установлено, можно начинать обмен данными. +Для этого используются функции `send` и `recv`. +В Unix для работы с сокетами можно использовать также файловые функции `read` и `write`, +но они обладают меньшими возможностями, а кроме того не будут работать на других платформах +(например, под Windows), поэтому я не рекомендую ими пользоваться. + +Функция `send` используется для отправки данных и имеет следующий прототип. + +```c +int send(int sockfd, const void *msg, int len, int flags); +``` + +Здесь `sockfd` - это, как всегда, дескриптор сокета, через который мы отправляем данные, +`msg` - указатель на буфер с данными, +`len` - длина буфера в байтах, +а `flags` - набор битовых флагов, управляющих работой функции +(если флаги не используются, передайте функции 0). +Вот некоторые из них (полный список можно найти в документации): + +- `MSG_OOB`. Предписывает отправить данные как срочные (out of band data, OOB). +Концепция срочных данных позволяет иметь два параллельных канала данных в одном соединении. +Иногда это бывает удобно. Например, Telnet использует срочные данные для передачи команд типа +Ctrl+C. В настоящее время использовать их не рекомендуется из-за проблем с совместимостью +(существует два разных стандарта их использования, описанные в RFC793 и RFC1122). +Безопаснее просто создать для срочных данных отдельное соединение. +- `MSG_DONTROUTE`. Запрещает маршрутизацию пакетов. +Нижележащие транспортные слои могут проигнорировать этот флаг. + +Функция `send` возвращает число байтов, которое на самом деле было отправлено +(или -1 в случае ошибки). Это число может быть меньше указанного размера буфера. +Если вы хотите отправить весь буфер целиком, +вам придётся написать свою функцию и вызывать в ней `send`, +пока все данные не будут отправлены. Она может выглядеть примерно так. + +```c +int sendall(int s, char *buf, int len, int flags) +{ + int total = 0; + int n; + + while(total < len) + { + n = send(s, buf+total, len-total, flags); + if(n == -1) { break; } + total += n; + } + + return (n==-1 ? -1 : total); +} +``` + +Использование `sendall` ничем не отличается от использования `send`, +но она отправляет весь буфер с данными целиком. + +Для чтения данных из сокета используется функция `recv`. + +```c +int recv(int sockfd, void *buf, int len, int flags); +``` + +В целом её использование аналогично `send`. +Она точно так же принимает дескриптор сокета, указатель на буфер и набор флагов. +Флаг `MSG_OOB` используется для приёма срочных данных, +а `MSG_PEEK` позволяет "подсмотреть" данные, полученные от удалённого хоста, +не удаляя их из системного буфера +(это означает, что при следующем обращении к `recv` вы получите те же самые данные). +Полный список флагов можно найти в документации. +По аналогии с `send` функция `recv` возвращает количество прочитанных байтов, +которое может быть меньше размера буфера. +Вы без труда сможете написать собственную функцию `recvall`, заполняющую буфер целиком. +Существует ещё один особый случай, при котором `recv` возвращает 0. +Это означает, что соединение было разорвано. + +### Закрытие сокета + +Закончив обмен данными, закройте сокет с помощью функции `close`. +Это приведёт к разрыву соединения. + +```c +#include + +int close(int fd); +``` + +Вы также можете запретить передачу данных в каком-то одном направлении, используя `shutdown`. + +```c +int shutdown(int sockfd, int how); +``` + +Параметр `how` может принимать одно из следующих значений: + +- 0 - запретить чтение из сокета. +- 1 - запретить запись в сокет. +- 2 - запретить и то и другое. + +Хотя после вызова `shutdown` с параметром `how`, равным 2, +вы больше не сможете использовать сокет для обмена данными, +вам всё равно потребуется вызвать `close`, +чтобы освободить связанные с ним системные ресурсы. + +### Обработка ошибок + +До сих пор я ни слова не сказал об ошибках, +которые могут происходить (и часто происходят) в процессе работы с сокетами. +Так вот: если что-то пошло не так, все рассмотренные нами функции возвращают -1, +записывая в глобальную переменную `errno` код ошибки. +Соответственно, вы можете проанализировать значение этой переменной +и предпринять действия по восстановлению нормальной работы программы, +не прерывая её выполнения. А можете просто выдать диагностическое сообщение +(для этого удобно использовать функцию `perror`), +а затем завершить программу с помощью `exit`. +Именно так я буду поступать в демонстрационных примерах. + +### Отладка программ + +Начинающие программисты часто спрашивают, как можно отлаживать сетевую программу, +если под рукой нет сети. Оказывается, можно обойтись и без неё. +Достаточно запустить клиента и сервера на одной машине, +а затем использовать для соединения адрес интерфейса внутренней петли (loopback interface). +В программе ему соответствует константа `INADDR_LOOPBACK` +(не забудьте применять к ней функцию `htonl!`). +Пакеты, направляемые по этому адресу, в сеть не попадают. +Вместо этого они передаются стеку протоколов TCP/IP как только что принятые. +Таким образом моделируется наличие виртуальной сети, +в которой вы можете отлаживать ваши сетевые приложения. + +Для простоты я буду использовать в демонстрационных примерах интерфейс внутренней петли. + +### Эхо-клиент и эхо-сервер + +Теперь, когда мы изучили основные функции для работы с сокетами, +самое время посмотреть, как они используются на практике. +Для этого я написал две небольшие демонстрационные программы. +Эхо-клиент посылает сообщение **«Hello there!»** и выводит на экран ответ сервера. +Его код приведён в **листинге 1**. +Эхо-сервер читает всё, что передаёт ему клиент, +а затем просто отправляет полученные данные обратно. +Его код содержится в **листинге 2**. + +**Листинг 1. Эхо-клиент.** + +```c +#include +#include +#include + +char message[] = "Hello there!\n"; +char buf[sizeof(message)]; + +int main() +{ + int sock; + struct sockaddr_in addr; + + sock = socket(AF_INET, SOCK_STREAM, 0); + if(sock < 0) + { + perror("socket"); + exit(1); + } + + addr.sin_family = AF_INET; + addr.sin_port = htons(3425); // или любой другой порт... + addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK); + if(connect(sock, (struct sockaddr *)&addr, sizeof(addr)) < 0) + { + perror("connect"); + exit(2); + } + + send(sock, message, sizeof(message), 0); + recv(sock, buf, sizeof(message), 0); + + printf(buf); + close(sock); + + return 0; +} +``` + +**Листинг 2. Эхо-сервер.** + +```c +#include +#include +#include + +int main() +{ + int sock, listener; + struct sockaddr_in addr; + char buf[1024]; + int bytes_read; + + listener = socket(AF_INET, SOCK_STREAM, 0); + if(listener < 0) + { + perror("socket"); + exit(1); + } + + addr.sin_family = AF_INET; + addr.sin_port = htons(3425); + addr.sin_addr.s_addr = htonl(INADDR_ANY); + if(bind(listener, (struct sockaddr *)&addr, sizeof(addr)) < 0) + { + perror("bind"); + exit(2); + } + + listen(listener, 1); + + while(1) + { + sock = accept(listener, NULL, NULL); + if(sock < 0) + { + perror("accept"); + exit(3); + } + + while(1) + { + bytes_read = recv(sock, buf, 1024, 0); + if(bytes_read <= 0) break; + send(sock, buf, bytes_read, 0); + } + + close(sock); + } + + return 0; +} +``` diff --git a/content/posts/2023/privacy/gpg.md b/content/posts/2023/privacy/gpg.md new file mode 100644 index 0000000..63adfff --- /dev/null +++ b/content/posts/2023/privacy/gpg.md @@ -0,0 +1,134 @@ +--- +title: "🔐 GnuPG || GPG" +date: 2023-04-05T12:05:35+03:00 +draft: false +tags: [gpg, privacy, tips] +--- + +## GnuPG + +GnuPG — полная и свободная реализация [OpenPGP](https://openpgp.org/about/) стандарта, +определенного в [RFC4880](https://tools.ietf.org/html/rfc4880) (также известного, как PGP). +GnuPG позволяет вам шифровать и подписывать данные и сообщения. +Он оснащен универсальной системой управления ключами, +а также модулями доступа для всех типов открытых ключей. +GnuPG, также известный как GPG, +это инструмент командной строки с возможностью легкой интеграции с другими приложениями. +Доступен богатый выбор пользовательских приложений и библиотек. +Также 2 версия GnuPG поддерживает S/MIME и Secure Shell (ssh). + +## Расположение каталогов + +`$GNUPGHOME` используется GnuPG для определения каталога, +в котором хранятся конфигурационные файлы. +По умолчанию `$GNUPGHOME` не назначена и вместо этого используется `$HOME`; +таким образом, вы найдете каталог `~/.gnupg` сразу после установки. + +Чтобы изменить стандартное расположение, выполните `gpg --homedir путь/к/файлу` +или установите переменную окружения `GNUPGHOME`. + +## Файлы конфигурации + +Файлы конфигурации по умолчанию `~/.gnupg/gpg.conf`. +По умолчанию разрешения доступа каталога gnupg установлены в `700`, +а файлов, которые он содержит - `600`. +Только владелец каталога имеет разрешение на просмотр содержимого, +радактирование и доступ к файлам. В целях безопасности, +эти разрешения не должны быть изменены. +В случае, если этот каталог или любые файлы внутри не следуют данной мере безопасности, +вы получите предупреждение о наличии небезопасных файлов и разрешений домашнего каталога. + +## Создание пары ключей + +Чтобы создать пару ключей, необходимо ввести команду: +```sh +gpg --full-gen-key +``` + +ℹ️ Используйте опцию `--expert`, чтобы выбрать другие шифры, +такие как [ECC](https://en.wikipedia.org/wiki/Elliptic_curve_cryptography). + +ℹ️ Более простая опция `--gen-key` использует параметры по умолчанию для шифра, +размера и срока действия ключа и запрашивает только имя и адрес электронной почты. + +## Экспорт открытого ключа + +Основное назначение GnuPG — обеспечение конфиденциальности обмена сообщениями +с помощью криптографии с открытым ключом. +С его помощью каждый пользователь распространяет открытый ключ своей связки ключей, +который может быть использован другими пользователями для шифрования сообщений пользователю. +Закрытый ключ всегда должен оставаться в тайне, иначе конфиденциальность будет нарушена. + +Таким образом, чтобы другие могли отправлять вам зашифрованные сообщения, +им нужен ваш открытый ключ. + +Чтобы сгенерировать ASCII-версию открытого ключа пользователя в файл `public.key` +(например, для отправки по электронной почте): + +```sh +gpg --export --armor --output public.key user-id +``` + +### Импорт открытого ключа + +Чтобы зашифровать сообщения другим людям, а также проверить их подписи, +вам нужен их открытый ключ. Чтобы импортировать открытый ключ из файла public.key в свой список открытых ключей, выполните команду: + +```sh +gpg --import public.key +``` + +## Резервное копирование закрытого ключа + +Чтобы создать резервную копию вашего закрытого ключа, выполните: + +```sh +gpg --export-secret-keys --armor --output privkey.asc user-id +``` + +Обратите внимание, что вышеуказанная команда требует ввода пароля от ключа. +В противном случае любой, кто получит доступ к экспортированному файлу, +сможет шифровать и подписывать документы, как если бы он был вами, +без необходимости знать пароль. + +⚠️ **Важно:** +Пароль — обычно самое слабое звено в защите закрытого ключа. +Поместите закрытый ключ в безопасное место на другой системе или на другом устройстве, +например, в заблокированный контейнер или на зашифрованный диск. +Это единственное средство защиты, которое поможет +вам восстановить контроль над списком ваших ключей в случае, +например, поломки диска, кражи или ещё чего-нибудь похуже. +Этот способ резервного копирования ключей имеет некоторые ограничения по безопасности. +Более безопасный способ резервного копирования и импорта ключей с помощью gpg описан +[здесь](https://web.archive.org/web/20210803213236/https://habd.as/post/moving-gpg-keys-privately/). + +## Импорт закрытого ключа из резервной копии: + +```sh +gpg --import privkey.asc +``` + +ℹ️ Совет: [Paperkey](https://www.jabberwocky.com/software/paperkey/) позволяет экспортировать ключ в виде простого текста +или машиночитаемого штрих-кода, которые можно отпечатать на бумаге. + +## Резервное копирование сертификата отзыва + +Сертификаты отзыва автоматически генерируются для вновь создаваемых ключей. +По умолчанию они находятся в `~/.gnupg/openpgp-revocs.d/`. +Имя файла сертификата — это отпечаток ключа, который он отзывает. +Сертификаты отзыва также можно сгенерированы вручную с помощью следующей команды: + +```sh +gpg --gen-revoke --armor --output revcert.asc user-id +``` + +Этот сертификат используется, чтобы выполнить отзыв ключа в случае, +если он оказался потерян или скомпрометирован. +Резервная копия будет полезна, если у вас больше нет доступа к закрытому ключу, +из-за чего вы не можете сгенерировать +новый сертификат отзыва с помощью приведённой выше команды. +Он достаточно короткий, чтобы его можно было распечатать и набрать от руки при необходимости. + +⚠️ **Важно:** Любой человек, имеющий доступ к сертификату отзыва, +может публично отозвать ключ, и это действие нельзя отменить. +Защищайте свой сертификат отзыва так же, как вы защищаете свой закрытый ключ.