Наверняка какждый из нас хотя бы раз в жизни создавали пару RSA-ключей, например для того, чтобы подключиться к GitHub без необходимости каждый раз вводить пароль. Обычно весь процесс занимает пару минут — следует лишь следовать простой инструкции.

Но как насчёт того, что конкретно происходит, когда вы генерируете эти ключи?

Знаете ли вы, что именно содержится в файле ~/.ssh/id_rsa? Почему ssh создаёт два файла и оба с разным форматом? Замечали ли вы, что один файл начинается с симоволов ssh-rsa, а другой — с -----BEGIN RSA PRIVATE KEY-----? Обращали ли вы внимание на то, что иногда в заголовке второго файла вместо слова RSA иногда написано BEGIN PRIVATE KEY?

Я считаю, каждый разработчик должен иметь хотя бы минимальное понимание о различных форматах RSA-ключей. И тем более это важно если вы хотите сделать карьеру в мире управления инфраструктурой.

Алгоритм RSA

С момента изобретения криптографии с открытым ключом были придуманы различные системы создания ключевой пары. Одной из первых является RSA, созданная тремя гениальными криптографами и датируемая 1977 годом. История RSA весьма интересна тем, что впервые эту схему изобрел английский математик Клиффорд Кокс, который, однако, был вынужден держать ее в секрете по требованию британской разведки, на которую он работал.

Напомню, что RSA — это не синоним криптографии с открытым ключом, а лишь одна из возможных реализаций. RSA по-прежнему, спустя более чем 40 лет после публикации, является одним из самых распространенных алгоритмов. В частности, он является стандартным алгоритмом, используемым для генерации пар ключей SSH, а учитывая, что сегодня у каждого разработчика есть свой открытый ключ добавленный на GitHub, BitBucket и другие подобные системы, можно с уверенностью сказать, что RSA достаточно широко распространен.

В этой статье я не буду рассказывать о внутреннем устройстве алгоритма RSA. Если вас интересуют подробности этого алгоритма, его математическая основа, вы можете найти множество источников как в Интернете, так и в учебниках. Теория, лежащая в основе RSA далека от того, чтобы называться тривиальной, тем не менее она определенно стоит того, чтобы потратить на нее время, если вы хотите серьезно заниматься математической частью криптографии.

В этой статье я рассмотрю два способа создания пар ключей RSA и форматы их хранения. Прикладная криптография, как и многие другие темы в компьютерных науках постоянно развивается и инструменты часто меняются. Узнать как сделать то или иное действие обычно не составляет труда (в этом помогает StackOverflow), но вот с что может вызвать затруднение — это получить четкое представление о том, что происходит за сценой.

Во всех примерах, приведенных в этой заметке, используется 2048-битный RSA-ключ, созданный специально для этой цели, поэтому все симовлы, которые вы видите, взяты из реального примера. После написания статьи этот ключ был уничтожен.

Формат PEM

Предлагаю начать разговор о парах ключей с формата их хранения. В настоящее время наиболее распространенным форматом хранения является PEM (Privacy-enhanced Electronic Mail). Как следует из названия, этот формат изначально создавался для шифрования электронной почты, но впоследствии стал общим форматом хранения криптографических данных, таких как ключи и сертификаты. Он описан в RFC 7468 ("Textual Encodings of PKIX, PKCS, and CMS Structures").

Пример закрытого ключа в формате PEM выглядит следующим образом

-----BEGIN PRIVATE KEY-----
MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCy9f0/nwkXESzk
L4v4ftZ24VJYvkQ/Nt6vsLab3iSWtJXqrRsBythCcbAU6W95OGxjbTSFFtp0poqM
cPuogocMR7QhjY9JGG3fcnJ7nYDCGRHD4zfG5Af/tHwvJ2ew0WTYoemvlfZIG/jZ
7fsuOQSyUpJoxGAlb6/QpnfSmJjxCx0VEoppWDn8CO3VhOgzVhWx0dcne+ZcUy3K
kt3HBQN0hosRfqkVSRTvkpK4RD8TaW5PrVDe1r2Q5ab37TO+Ls4xxt16QlPubNxW
eH3dHVzXdmFAItuH0DuyLyMoW1oxZ6+NrKu+pAAERxM303gejFzKDqXid5m1EOTv
k4xhyqYNAgMBAAECggEBALJCVQAKagOQGCczNTlRHk9MIbpDy7cr8KUQYNThcZCs
UKhxxXUDmGaW1838uA0HJu/i1226Vd/cBCXgZMx1OBADXGoPl6o3qznnxiFbweWV
Ex0MN4LloRITtZ9CoQZ/jPQ8U4mS1r79HeP2KTzhjswRc8Tn1t1zYq1zI+eiGLX/
sPJF63ljJ8yHST7dE0I07V87FKTE2SN0WX9kptPLLBDwzS1X6Z9YyNKPIEnRQzzE
vWdwF60b3RyDz7j7foyP3PC0+3fee4KFdJzt+/1oePf3kwBz8PQq3cuoOF1+0Fzf
yqKiunV2AXI6liAf7MwuZcZeFPZfHTTW7N/j+FQBgAECgYEA4dFjib9u/3rkT2Vx
Bu2ByBpItfs1b4PdSiKehlS9wDZxa72dRt/RSYEyVFBUlYrKXP2nCdl8yMap6SA9
Bfe51F5oWhml9YJn/LF/z1ArMs/tuUyupY7l9j66XzPQmUbIZSEyNEQQ09ZYdIvK
4lbySJbCqa2TQNPIOSZS2o7XNG0CgYEAyuFVybOkVGtfw89MyA1TnVMcQGusXtgo
GOl3tJb59hTO+xF547+/qyK8p/iOu4ybEyeucBEyQt/whmNwtsdngtvVDb4f7psz
Frmqx7q7fPoKnvJsPJds9i2o9B7+BlRY3HwcvKePsctP96pQ0RbOFkCVak6J6t9S
k/qhOiNJ9CECgYEAvDuTMk5tku54g6o2ZiTyir9GHtOwviz3+AUViTn4FdIMB1g+
UsbcqN3V+ywe5ayUdKFHbNFqz92x4k7qLyBJObocWAaLLTQvxBadSE02RRvHuC8w
YXbVP8cYCaWiWzICdzINrD2UnVBN2ZBxZOw+970btN6oIWCnxOOqKt7oip0CgYAp
Fekhp9enoPcL2HdcLBa6zZHzGdsWef/ky6MKV2jXhO9FuQxOKw7NwYMjIRsGsDrX
bjnNSC49jMxQ6uJwoYE85vgGiHI/B/8YoxEK0a4WaSytc7qnqqLOWADXL0+SSJKW
VCwdqHFZOCtBpKQpM80YhIu9s5oKjp9SiHcOJwdbAQKBgDq047hBqyNFFb8KjS5A
+26VOJcC2DRHTprYSRJNxsHTQnONTnUQJl32t0TrqkqIp5lTRr7vBH2wJM6LKk45
I7BWY4mUirC7sDGHl3DaFPRBiut1rpg0kSKi2VNRF7Bb75OKEhGjvm6IKVe8Kl8d
5cpQwm9C7go4OiorY0DVLho2
-----END PRIVATE KEY-----

В принципе, о том, что вы имеете дело с форматом PEM, можно судить по стандартным заголовкам и нижним колонтитулам, которые идентифицируют содержимое. Дефисы и два слова BEGIN и END присутствуют всегда, а вот часть PRIVATE KEY меняется в зависимости от содержимого, в т.ч. если PEM-файл содержит не ключ, а что--то другое, например, сертификат X.509 для SSL.

Формат PEM определяет, что тело содержимого (часть между заголовком и нижним колонтитулом) кодируется с помощью Base64.

Если закрытый ключ был зашифрован с помощью пароля, то заголовок и нижний колонтитул будут отличаться

-----BEGIN ENCRYPTED PRIVATE KEY-----
MIIFHzBJBgkqhkiG9w0BBQ0wPDAbBgkqhkiG9w0BBQwwDgQIf75rXIakuSICAggA
MB0GCWCGSAFlAwQBKgQQf8HMdJ9FZJjwHkMQjkNA3gSCBNClWB7cJ5f8ThrQtmoA
t2WQCvEWTY9nRYwaTnL1SmXyuMDFrX5CWEuVFh/Zj77KB9jhBJaHw2XtFXxF8bV7
F10u93ih/n0S5QwN9CSPDhRp2kD5lIWB8WVG+VgtncqDrAfJRmpuPmzpjMJBxE2r
MvWJG5beMCS25qD0mAxihtbriqFoCtEygQ7vsSfeQpaBQvT5pKLOVaVgwFTFTf+7
cgqB8/UKKmPXSM4GMJ9VNAvUx0mAxI9MnUFlBWimK76OAzdlO9Si99R8OiRRS10x
AO1AwWSDHGWpbckK0g9K7wLgAgOw8LLVUJh67o9Mfg58DP9Ca0ZdPPVo0C7oavBD
NFlUsKqmSfqfgOAm4qGJ7GB3KgWGFdz+yexNLRLN63hE6qACAuQ1oLmwoorE8toh
MhT3c6IxnVWlYNXJkkb5iV9e8E2X/xzibvwv+CJJ9ulCU8uS7gp0rjlCKFwt/8d4
g3Cef/JWn9nI9YwRLNShJeQOe8hZkkLXHefUhBa2o2++C5C6mgWvuYLK6a0zfCMY
WCqjKKvDQfuxwDbeM03jJ97Je6dXy7rtJvJd10vYvpIVtHnNSdg1evpSiaAmWt4C
X5/AzbHNvwTIEvILfOtYvxLB/RdWqr1/VXuH4dJF6AYtHfQHjXetmL/fDA86Bqf6
Eb+uDr+PPuH4qw1tfJBdTSOOJzhhPqdT4ERYnOvfNxTKzsKYZT+kWvWXe9zyO13W
C0eceVi4rBjKpKpKecKDgFJGZ1u7jS0OW3FDIOfm/osu9z25g5CVIpuWU3JquWib
GatHET9wIEg7LRqC/i65q6tCnd9azevKtiur1I0tuh05iwP5kZ8drIzaGdObuvK1
/pbEPnj1ZcRlAZ34jnG841xvf4vofrOE+hGTNF5HypOCvO/8Lms3aB6NletIvHBE
99ynQyF9TAgSAFAumOws+qnRcnfVOF5lzIEE2pmeMVMqi5s7TT4hlhOuCbyfEFU8
xOXxNazT+0o7urIYOc77vA1LsWrk+9dAfm43CbBZvYav/gMoBc5fsLgAUAm1lkt5
5Hjaf+iMIN0v7aEKDrNDOtyQr13YdyuEClzXxeMtlhU+QfErpQHvH0jE4gywEgz7
tvVGwrbiLgg0y537+kg0/rS3N0eI94GhY0q/nR/QFObbN0nmoIYVVSGtufJx1r9v
YEVZA7HZE9pjnun1ylE1/SoYc/816rjBUcW5CCbkMDIz1LsFPr2SkQeHTNzK3/9J
Kny1lerfA+TA/hUyZ1KJjxuao+rJkH2fJ25qs3r6NP+PPbq3sAl1TPGhMCnNaFdo
YQWDDwz26ZR2ywfsquqLXMwnIEeUI/hQTng9ZxLkJMY22rQSA9nsdvR8S1b0U8Qu
ViYEjCTMWF8HEFFO721MlkTgchzq6fiF+9ZydCpVUJWolcfw1OgUvvTSI7Eyhelb
7fc1fTVFeEMsHrtjpu8dg+IaCNraBzv5QZx6MYW7SSoTVp8mJoPnzYbsZs9nHJGX
iQOFmO/sIryOoeJlpOCGT55yU74yRXrBsYZyLz0P9K1FDQS6l9W33BqmF9vSXujs
kSByq8v1OU0IqidnMmZtTDSRlpQL/oadqQnsA6jiWyMznuUEU8tfgUALE4DKRq8P
wBLKVfMiwcWAbl121M2DCLj9/g==
-----END ENCRYPTED PRIVATE KEY-----

При использовании формата PEM для хранения криптографических ключей тело содержимого имеет формат, называемый PKCS #8. Изначально этот стандарт был создан частной компанией (RSA Laboratories), но затем стал стандартом де-факто и был описан в различных RFC, в частности, в RFC 5208 ("Public-Key Cryptography Standards (PKCS) #8: Private-Key Information Syntax Specification Version 1.2").

Для описания структуры данных формат PKCS #8 использует язык ASN.1 (Abstract Syntax Notation One), в то время как DER (Distinguished Encoding Rules) используется для сериализации этой структуры в двоичный формт. Это означает, что Base64-декодирование содержимого вернет некоторое двоичное значение, которое может быть обработано только парсером ASN.1.

Давайте кратко резюмируем сказанное в виде диаграммы

-----BEGIN label-----
+--------------------------- Base64 ---------------------------+
|                                                              |
| PKCS #8 content:                                             |
| ASN.1 language serialised with DER                           |
|                                                              |
+--------------------------------------------------------------+
-----END label-----

Обратите внимание, что в силу особенностей структуры ASN.1 тела ключей в формате PEM начинаются всегда с одних и тех же символов: MIG — для 1024-битных ключей, MII — для 2048 и 4096-битных.

OpenSSL и ASN.1

OpenSSL может напрямую декодировать ключ в формате PEM и показать лежащую в его основе структуру ASN.1 с помощью модуля asn1parse

$ openssl asn1parse -inform pem -in private.pem
    0:d=0  hl=4 l=1214 cons: SEQUENCE          
    4:d=1  hl=2 l=   1 prim: INTEGER           :00
    7:d=1  hl=2 l=  13 cons: SEQUENCE          
    9:d=2  hl=2 l=   9 prim: OBJECT            :rsaEncryption
   20:d=2  hl=2 l=   0 prim: NULL              
   22:d=1  hl=4 l=1192 prim: OCTET STRING      [HEX DUMP]:308204A40201000282010100B2F5FD3F9F0917112
   CE42F8BF87ED676E15258BE443F36DEAFB0B69BDE2496B495EAAD1B01CAD84271B014E96F79386C636D348516DA74A68
   A8C70FBA882870C47B4218D8F49186DDF72727B9D80C21911C3E337C6E407FFB47C2F2767B0D164D8A1E9AF95F6481BF
   8D9EDFB2E3904B2529268C460256FAFD0A677D29898F10B1D15128A695839FC08EDD584E8335615B1D1D7277BE65C532
   DCA92DDC7050374868B117EA9154914EF9292B8443F13696E4FAD50DED6BD90E5A6F7ED33BE2ECE31C6DD7A4253EE6CD
   C56787DDD1D5CD776614022DB87D03BB22F23285B5A3167AF8DACABBEA40004471337D3781E8C5CCA0EA5E27799B510E
   4EF938C61CAA60D02030100010282010100B24255000A6A03901827333539511E4F4C21BA43CBB72BF0A51060D4E1719
   0AC50A871C57503986696D7CDFCB80D0726EFE2D76DBA55DFDC0425E064CC753810035C6A0F97AA37AB39E7C6215BC1E
   595131D0C3782E5A11213B59F42A1067F8CF43C538992D6BEFD1DE3F6293CE18ECC1173C4E7D6DD7362AD7323E7A218B
   5FFB0F245EB796327CC87493EDD134234ED5F3B14A4C4D92374597F64A6D3CB2C10F0CD2D57E99F58C8D28F2049D1433
   CC4BD677017AD1BDD1C83CFB8FB7E8C8FDCF0B4FB77DE7B8285749CEDFBFD6878F7F7930073F0F42ADDCBA8385D7ED05
   CDFCAA2A2BA757601723A96201FECCC2E65C65E14F65F1D34D6ECDFE3F85401800102818100E1D16389BF6EFF7AE44F6
   57106ED81C81A48B5FB356F83DD4A229E8654BDC036716BBD9D46DFD1498132545054958ACA5CFDA709D97CC8C6A9E92
   03D05F7B9D45E685A19A5F58267FCB17FCF502B32CFEDB94CAEA58EE5F63EBA5F33D09946C8652132344410D3D658748
   BCAE256F24896C2A9AD9340D3C8392652DA8ED7346D02818100CAE155C9B3A4546B5FC3CF4CC80D539D531C406BAC5ED
   82818E977B496F9F614CEFB1179E3BFBFAB22BCA7F88EBB8C9B1327AE70113242DFF0866370B6C76782DBD50DBE1FEE9
   B3316B9AAC7BABB7CFA0A9EF26C3C976CF62DA8F41EFE065458DC7C1CBCA78FB1CB4FF7AA50D116CE1640956A4E89EAD
   F5293FAA13A2349F42102818100BC3B93324E6D92EE7883AA366624F28ABF461ED3B0BE2CF7F805158939F815D20C075
   83E52C6DCA8DDD5FB2C1EE5AC9474A1476CD16ACFDDB1E24EEA2F204939BA1C58068B2D342FC4169D484D36451BC7B82
   F306176D53FC71809A5A25B320277320DAC3D949D504DD9907164EC3EF7BD1BB4DEA82160A7C4E3AA2ADEE88A9D02818
   02915E921A7D7A7A0F70BD8775C2C16BACD91F319DB1679FFE4CBA30A5768D784EF45B90C4E2B0ECDC18323211B06B03
   AD76E39CD482E3D8CCC50EAE270A1813CE6F80688723F07FF18A3110AD1AE16692CAD73BAA7AAA2CE5800D72F4F92489
   296542C1DA87159382B41A4A42933CD18848BBDB39A0A8E9F5288770E27075B010281803AB4E3B841AB234515BF0A8D2
   E40FB6E95389702D834474E9AD849124DC6C1D342738D4E7510265DF6B744EBAA4A88A7995346BEEF047DB024CE8B2A4
   E3923B0566389948AB0BBB031879770DA14F4418AEB75AE98349122A2D9535117B05BEF938A1211A3BE6E882957BC2A5
   F1DE5CA50C26F42EE0A383A2A2B6340D52E1A36

То, что вы видите в приведённом фрагменте, и есть закрытый ключ в формате ASN.1. Помните, что DER используется только для перехода от текстового представления ASN.1 к двоичным данным, поэтому мы не увидим его, пока не декодируем содержимое Base64 в файл и не откроем его в двоичном редакторе.

Обратите внимание, что структура ASN.1 содержит описание типа объекта (в данном случае это rsaEncryption).

Следующим шагом мы декодируем поле OCTET STRING, которое и является собственно ключом. Обратите внимание на параметр -strparse, который указывает смещение равное 22.

$ openssl asn1parse -inform pem -in private.pem -strparse 22
    0:d=0  hl=4 l=1188 cons: SEQUENCE          
    4:d=1  hl=2 l=   1 prim: INTEGER           :00
    7:d=1  hl=4 l= 257 prim: INTEGER           :B2F5FD3F9F0917112CE42F8BF87ED676E15258BE443F36DEAFB
    0B69BDE2496B495EAAD1B01CAD84271B014E96F79386C636D348516DA74A68A8C70FBA882870C47B4218D8F49186DDF
    72727B9D80C21911C3E337C6E407FFB47C2F2767B0D164D8A1E9AF95F6481BF8D9EDFB2E3904B2529268C460256FAFD
    0A677D29898F10B1D15128A695839FC08EDD584E8335615B1D1D7277BE65C532DCA92DDC7050374868B117EA9154914
    EF9292B8443F13696E4FAD50DED6BD90E5A6F7ED33BE2ECE31C6DD7A4253EE6CDC56787DDD1D5CD776614022DB87D03
    BB22F23285B5A3167AF8DACABBEA40004471337D3781E8C5CCA0EA5E27799B510E4EF938C61CAA60D
  268:d=1  hl=2 l=   3 prim: INTEGER           :010001
  273:d=1  hl=4 l= 257 prim: INTEGER           :B24255000A6A03901827333539511E4F4C21BA43CBB72BF0A51
    060D4E17190AC50A871C57503986696D7CDFCB80D0726EFE2D76DBA55DFDC0425E064CC753810035C6A0F97AA37AB39
    E7C6215BC1E595131D0C3782E5A11213B59F42A1067F8CF43C538992D6BEFD1DE3F6293CE18ECC1173C4E7D6DD7362A
    D7323E7A218B5FFB0F245EB796327CC87493EDD134234ED5F3B14A4C4D92374597F64A6D3CB2C10F0CD2D57E99F58C8
    D28F2049D1433CC4BD677017AD1BDD1C83CFB8FB7E8C8FDCF0B4FB77DE7B8285749CEDFBFD6878F7F7930073F0F42AD
    DCBA8385D7ED05CDFCAA2A2BA757601723A96201FECCC2E65C65E14F65F1D34D6ECDFE3F854018001
  534:d=1  hl=3 l= 129 prim: INTEGER           :E1D16389BF6EFF7AE44F657106ED81C81A48B5FB356F83DD4A2
    29E8654BDC036716BBD9D46DFD1498132545054958ACA5CFDA709D97CC8C6A9E9203D05F7B9D45E685A19A5F58267FC
    B17FCF502B32CFEDB94CAEA58EE5F63EBA5F33D09946C8652132344410D3D658748BCAE256F24896C2A9AD9340D3C83
    92652DA8ED7346D
  666:d=1  hl=3 l= 129 prim: INTEGER           :CAE155C9B3A4546B5FC3CF4CC80D539D531C406BAC5ED82818E
    977B496F9F614CEFB1179E3BFBFAB22BCA7F88EBB8C9B1327AE70113242DFF0866370B6C76782DBD50DBE1FEE9B3316
    B9AAC7BABB7CFA0A9EF26C3C976CF62DA8F41EFE065458DC7C1CBCA78FB1CB4FF7AA50D116CE1640956A4E89EADF529
    3FAA13A2349F421
  798:d=1  hl=3 l= 129 prim: INTEGER           :BC3B93324E6D92EE7883AA366624F28ABF461ED3B0BE2CF7F80
    5158939F815D20C07583E52C6DCA8DDD5FB2C1EE5AC9474A1476CD16ACFDDB1E24EEA2F204939BA1C58068B2D342FC4
    169D484D36451BC7B82F306176D53FC71809A5A25B320277320DAC3D949D504DD9907164EC3EF7BD1BB4DEA82160A7C
    4E3AA2ADEE88A9D
  930:d=1  hl=3 l= 128 prim: INTEGER           :2915E921A7D7A7A0F70BD8775C2C16BACD91F319DB1679FFE4C
    BA30A5768D784EF45B90C4E2B0ECDC18323211B06B03AD76E39CD482E3D8CCC50EAE270A1813CE6F80688723F07FF18
    A3110AD1AE16692CAD73BAA7AAA2CE5800D72F4F92489296542C1DA87159382B41A4A42933CD18848BBDB39A0A8E9F5
    288770E27075B01
1061:d=1  hl=3 l= 128 prim: INTEGER           :3AB4E3B841AB234515BF0A8D2E40FB6E95389702D834474E9AD8
    49124DC6C1D342738D4E7510265DF6B744EBAA4A88A7995346BEEF047DB024CE8B2A4E3923B0566389948AB0BBB0318
    79770DA14F4418AEB75AE98349122A2D9535117B05BEF938A1211A3BE6E882957BC2A5F1DE5CA50C26F42EE0A383A2A
    2B6340D52E1A36

В RSA-ключе поля представляют собой конкретные компоненты алгоритма. Мы можем видеть (по порядку):

  • модуль n = pq

  • открытую экспоненту e

  • закрытую экспоненту d

  • два простых числа p и q

  • а также значения d_p, d_q и q_inv (для ускорения работы с использованием китайской теоремы об остатках)

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

$ openssl asn1parse -inform pem -in private-enc.pem
    0:d=0  hl=4 l=1311 cons: SEQUENCE          
    4:d=1  hl=2 l=  73 cons: SEQUENCE          
    6:d=2  hl=2 l=   9 prim: OBJECT            :PBES2
   17:d=2  hl=2 l=  60 cons: SEQUENCE          
   19:d=3  hl=2 l=  27 cons: SEQUENCE          
   21:d=4  hl=2 l=   9 prim: OBJECT            :PBKDF2
   32:d=4  hl=2 l=  14 cons: SEQUENCE          
   34:d=5  hl=2 l=   8 prim: OCTET STRING      [HEX DUMP]:7FBE6B5C86A4B922
   44:d=5  hl=2 l=   2 prim: INTEGER           :0800
   48:d=3  hl=2 l=  29 cons: SEQUENCE          
   50:d=4  hl=2 l=   9 prim: OBJECT            :aes-256-cbc
   61:d=4  hl=2 l=  16 prim: OCTET STRING      [HEX DUMP]:7FC1CC749F456498F01E43108E4340DE
   79:d=1  hl=4 l=1232 prim: OCTET STRING      [HEX DUMP]:A5581EDC2797FC4E1AD0B66A00B765900AF1164D8
   F67458C1A4E72F54A65F2B8C0C5AD7E42584B95161FD98FBECA07D8E1049687C365ED157C45F1B57B175D2EF778A1FE7
   D12E50C0DF4248F0E1469DA40F9948581F16546F9582D9DCA83AC07C9466A6E3E6CE98CC241C44DAB32F5891B96DE302
   4B6E6A0F4980C6286D6EB8AA1680AD132810EEFB127DE42968142F4F9A4A2CE55A560C054C54DFFBB720A81F3F50A2A6
   3D748CE06309F55340BD4C74980C48F4C9D41650568A62BBE8E0337653BD4A2F7D47C3A24514B5D3100ED40C164831C6
   5A96DC90AD20F4AEF02E00203B0F0B2D550987AEE8F4C7E0E7C0CFF426B465D3CF568D02EE86AF043345954B0AAA649F
   A9F80E026E2A189EC60772A058615DCFEC9EC4D2D12CDEB7844EAA00202E435A0B9B0A28AC4F2DA213214F773A2319D5
   5A560D5C99246F9895F5EF04D97FF1CE26EFC2FF82249F6E94253CB92EE0A74AE3942285C2DFFC77883709E7FF2569FD
   9C8F58C112CD4A125E40E7BC8599242D71DE7D48416B6A36FBE0B90BA9A05AFB982CAE9AD337C2318582AA328ABC341F
   BB1C036DE334DE327DEC97BA757CBBAED26F25DD74BD8BE9215B479CD49D8357AFA5289A0265ADE025F9FC0CDB1CDBF0
   4C812F20B7CEB58BF12C1FD1756AABD7F557B87E1D245E8062D1DF4078D77AD98BFDF0C0F3A06A7FA11BFAE0EBF8F3EE
   1F8AB0D6D7C905D4D238E2738613EA753E044589CEBDF3714CACEC298653FA45AF5977BDCF23B5DD60B479C7958B8AC1
   8CAA4AA4A79C283805246675BBB8D2D0E5B714320E7E6FE8B2EF73DB9839095229B9653726AB9689B19AB47113F70204
   83B2D1A82FE2EB9ABAB429DDF5ACDEBCAB62BABD48D2DBA1D398B03F9919F1DAC8CDA19D39BBAF2B5FE96C43E78F565C
   465019DF88E71BCE35C6F7F8BE87EB384FA1193345E47CA9382BCEFFC2E6B37681E8D95EB48BC7044F7DCA743217D4C0
   81200502E98EC2CFAA9D17277D5385E65CC8104DA999E31532A8B9B3B4D3E219613AE09BC9F10553CC4E5F135ACD3FB4
   A3BBAB21839CEFBBC0D4BB16AE4FBD7407E6E3709B059BD86AFFE032805CE5FB0B8005009B5964B79E478DA7FE88C20D
   D2FEDA10A0EB3433ADC90AF5DD8772B840A5CD7C5E32D96153E41F12BA501EF1F48C4E20CB0120CFBB6F546C2B6E22E0
   834CB9DFBFA4834FEB4B7374788F781A1634ABF9D1FD014E6DB3749E6A086155521ADB9F271D6BF6F60455903B1D913D
   A639EE9F5CA5135FD2A1873FF35EAB8C151C5B90826E4303233D4BB053EBD929107874CDCCADFFF492A7CB595EADF03E
   4C0FE15326752898F1B9AA3EAC9907D9F276E6AB37AFA34FF8F3DBAB7B009754CF1A13029CD6857686105830F0CF6E99
   476CB07ECAAEA8B5CCC2720479423F8504E783D6712E424C636DAB41203D9EC76F47C4B56F453C42E5626048C24CC585
   F0710514EEF6D4C9644E0721CEAE9F885FBD672742A555095A895C7F0D4E814BEF4D223B13285E95BEDF7357D3545784
   32C1EBB63A6EF1D83E21A08DADA073BF9419C7A3185BB492A13569F262683E7CD86EC66CF671C919789038598EFEC22B
   C8EA1E265A4E0864F9E7253BE32457AC1B186722F3D0FF4AD450D04BA97D5B7DC1AA617DBD25EE8EC912072ABCBF5394
   D08AA276732666D4C349196940BFE869DA909EC03A8E25B23339EE50453CB5F81400B1380CA46AF0FC012CA55F322C1C
   5806E5D76D4CD8308B8FDFE

OpenSSL и ключи RSA

Другим способом просмотра закрытого ключа в OpenSSL является использование модуля rsa. В то время как модуль asn1parse является общим синтаксическим анализатором ASN.1, модуль rsa распознаёт структуру ключа RSA и может правильно выводить имена полей

$ openssl rsa -in private.pem -noout -text
Private-Key: (2048 bit)
modulus:
    00:b2:f5:fd:3f:9f:09:17:11:2c:e4:2f:8b:f8:7e:
    d6:76:e1:52:58:be:44:3f:36:de:af:b0:b6:9b:de:
    24:96:b4:95:ea:ad:1b:01:ca:d8:42:71:b0:14:e9:
    6f:79:38:6c:63:6d:34:85:16:da:74:a6:8a:8c:70:
    fb:a8:82:87:0c:47:b4:21:8d:8f:49:18:6d:df:72:
    72:7b:9d:80:c2:19:11:c3:e3:37:c6:e4:07:ff:b4:
    7c:2f:27:67:b0:d1:64:d8:a1:e9:af:95:f6:48:1b:
    f8:d9:ed:fb:2e:39:04:b2:52:92:68:c4:60:25:6f:
    af:d0:a6:77:d2:98:98:f1:0b:1d:15:12:8a:69:58:
    39:fc:08:ed:d5:84:e8:33:56:15:b1:d1:d7:27:7b:
    e6:5c:53:2d:ca:92:dd:c7:05:03:74:86:8b:11:7e:
    a9:15:49:14:ef:92:92:b8:44:3f:13:69:6e:4f:ad:
    50:de:d6:bd:90:e5:a6:f7:ed:33:be:2e:ce:31:c6:
    dd:7a:42:53:ee:6c:dc:56:78:7d:dd:1d:5c:d7:76:
    61:40:22:db:87:d0:3b:b2:2f:23:28:5b:5a:31:67:
    af:8d:ac:ab:be:a4:00:04:47:13:37:d3:78:1e:8c:
    5c:ca:0e:a5:e2:77:99:b5:10:e4:ef:93:8c:61:ca:
    a6:0d
publicExponent: 65537 (0x10001)
privateExponent:
    00:b2:42:55:00:0a:6a:03:90:18:27:33:35:39:51:
    1e:4f:4c:21:ba:43:cb:b7:2b:f0:a5:10:60:d4:e1:
    71:90:ac:50:a8:71:c5:75:03:98:66:96:d7:cd:fc:
    b8:0d:07:26:ef:e2:d7:6d:ba:55:df:dc:04:25:e0:
    64:cc:75:38:10:03:5c:6a:0f:97:aa:37:ab:39:e7:
    c6:21:5b:c1:e5:95:13:1d:0c:37:82:e5:a1:12:13:
    b5:9f:42:a1:06:7f:8c:f4:3c:53:89:92:d6:be:fd:
    1d:e3:f6:29:3c:e1:8e:cc:11:73:c4:e7:d6:dd:73:
    62:ad:73:23:e7:a2:18:b5:ff:b0:f2:45:eb:79:63:
    27:cc:87:49:3e:dd:13:42:34:ed:5f:3b:14:a4:c4:
    d9:23:74:59:7f:64:a6:d3:cb:2c:10:f0:cd:2d:57:
    e9:9f:58:c8:d2:8f:20:49:d1:43:3c:c4:bd:67:70:
    17:ad:1b:dd:1c:83:cf:b8:fb:7e:8c:8f:dc:f0:b4:
    fb:77:de:7b:82:85:74:9c:ed:fb:fd:68:78:f7:f7:
    93:00:73:f0:f4:2a:dd:cb:a8:38:5d:7e:d0:5c:df:
    ca:a2:a2:ba:75:76:01:72:3a:96:20:1f:ec:cc:2e:
    65:c6:5e:14:f6:5f:1d:34:d6:ec:df:e3:f8:54:01:
    80:01
prime1:
    00:e1:d1:63:89:bf:6e:ff:7a:e4:4f:65:71:06:ed:
    81:c8:1a:48:b5:fb:35:6f:83:dd:4a:22:9e:86:54:
    bd:c0:36:71:6b:bd:9d:46:df:d1:49:81:32:54:50:
    54:95:8a:ca:5c:fd:a7:09:d9:7c:c8:c6:a9:e9:20:
    3d:05:f7:b9:d4:5e:68:5a:19:a5:f5:82:67:fc:b1:
    7f:cf:50:2b:32:cf:ed:b9:4c:ae:a5:8e:e5:f6:3e:
    ba:5f:33:d0:99:46:c8:65:21:32:34:44:10:d3:d6:
    58:74:8b:ca:e2:56:f2:48:96:c2:a9:ad:93:40:d3:
    c8:39:26:52:da:8e:d7:34:6d
prime2:
    00:ca:e1:55:c9:b3:a4:54:6b:5f:c3:cf:4c:c8:0d:
    53:9d:53:1c:40:6b:ac:5e:d8:28:18:e9:77:b4:96:
    f9:f6:14:ce:fb:11:79:e3:bf:bf:ab:22:bc:a7:f8:
    8e:bb:8c:9b:13:27:ae:70:11:32:42:df:f0:86:63:
    70:b6:c7:67:82:db:d5:0d:be:1f:ee:9b:33:16:b9:
    aa:c7:ba:bb:7c:fa:0a:9e:f2:6c:3c:97:6c:f6:2d:
    a8:f4:1e:fe:06:54:58:dc:7c:1c:bc:a7:8f:b1:cb:
    4f:f7:aa:50:d1:16:ce:16:40:95:6a:4e:89:ea:df:
    52:93:fa:a1:3a:23:49:f4:21
exponent1:
    00:bc:3b:93:32:4e:6d:92:ee:78:83:aa:36:66:24:
    f2:8a:bf:46:1e:d3:b0:be:2c:f7:f8:05:15:89:39:
    f8:15:d2:0c:07:58:3e:52:c6:dc:a8:dd:d5:fb:2c:
    1e:e5:ac:94:74:a1:47:6c:d1:6a:cf:dd:b1:e2:4e:
    ea:2f:20:49:39:ba:1c:58:06:8b:2d:34:2f:c4:16:
    9d:48:4d:36:45:1b:c7:b8:2f:30:61:76:d5:3f:c7:
    18:09:a5:a2:5b:32:02:77:32:0d:ac:3d:94:9d:50:
    4d:d9:90:71:64:ec:3e:f7:bd:1b:b4:de:a8:21:60:
    a7:c4:e3:aa:2a:de:e8:8a:9d
exponent2:
    29:15:e9:21:a7:d7:a7:a0:f7:0b:d8:77:5c:2c:16:
    ba:cd:91:f3:19:db:16:79:ff:e4:cb:a3:0a:57:68:
    d7:84:ef:45:b9:0c:4e:2b:0e:cd:c1:83:23:21:1b:
    06:b0:3a:d7:6e:39:cd:48:2e:3d:8c:cc:50:ea:e2:
    70:a1:81:3c:e6:f8:06:88:72:3f:07:ff:18:a3:11:
    0a:d1:ae:16:69:2c:ad:73:ba:a7:aa:a2:ce:58:00:
    d7:2f:4f:92:48:92:96:54:2c:1d:a8:71:59:38:2b:
    41:a4:a4:29:33:cd:18:84:8b:bd:b3:9a:0a:8e:9f:
    52:88:77:0e:27:07:5b:01
coefficient:
    3a:b4:e3:b8:41:ab:23:45:15:bf:0a:8d:2e:40:fb:
    6e:95:38:97:02:d8:34:47:4e:9a:d8:49:12:4d:c6:
    c1:d3:42:73:8d:4e:75:10:26:5d:f6:b7:44:eb:aa:
    4a:88:a7:99:53:46:be:ef:04:7d:b0:24:ce:8b:2a:
    4e:39:23:b0:56:63:89:94:8a:b0:bb:b0:31:87:97:
    70:da:14:f4:41:8a:eb:75:ae:98:34:91:22:a2:d9:
    53:51:17:b0:5b:ef:93:8a:12:11:a3:be:6e:88:29:
    57:bc:2a:5f:1d:e5:ca:50:c2:6f:42:ee:0a:38:3a:
    2a:2b:63:40:d5:2e:1a:36

Поля здесь те же самые, что и в структуре ASN.1, но здесь конкретные значения компонентов ключа представлены более наглядно. Сравние эти два представления и убедитесь, что значения полей совпадают.

Из интересного: попробуйте изучить исторические причины выбора 65537 в качестве общепринятой публичной экспоненты (см. секцию publicExponent).

PKCS #8 vs PKCS #1

Первая версия стандарта PKCS (PKCS #1) была специально приспособлена для ключа RSA. Его определение в формате ASN.1 можно найти в RFC 8017 ("PKCS #1: RSA Cryptography Specifications Version 2.2").

RSAPublicKey ::= SEQUENCE {
    modulus           INTEGER,  -- n
    publicExponent    INTEGER   -- e
}

RSAPrivateKey ::= SEQUENCE {
    version           Version,
    modulus           INTEGER,  -- n
    publicExponent    INTEGER,  -- e
    privateExponent   INTEGER,  -- d
    prime1            INTEGER,  -- p
    prime2            INTEGER,  -- q
    exponent1         INTEGER,  -- d mod (p-1)
    exponent2         INTEGER,  -- d mod (q-1)
    coefficient       INTEGER,  -- (inverse of q) mod p
    otherPrimeInfos   OtherPrimeInfos OPTIONAL
}

Впоследствии, с появлением новых алгоритмов, появилась потребности в описании их типов. Как следствие был разработан стандарт PKCS #8. Он позволяет содержать различные типы ключей и определяет специальное поле для идентификатора алгоритма. Его описание в формате ASN.1 можно найти в RFC 5958 ("Asymmetric Key Packages ").

OneAsymmetricKey ::= SEQUENCE {
     version                   Version,
     privateKeyAlgorithm       PrivateKeyAlgorithmIdentifier,
     privateKey                PrivateKey,
     attributes            [0] Attributes OPTIONAL,
     ...,
     [[2: publicKey        [1] PublicKey OPTIONAL ]],
     ...
   }

PrivateKey ::= OCTET STRING
                     -- Content varies based on type of key. The
                     -- algorithm identifier dictates the format of
                     -- the key.

Определение поля PrivateKey для алгоритма RSA аналогично используемому в PKCS #1.

Если в формате PEM используется PKCS #8, то его верхний и нижний колонтитулы имеют вид (и алгоритм определяется внутри тела согласно структуре)

-----BEGIN PRIVATE KEY-----
[...]
-----END PRIVATE KEY-----

Если же используется PKCS #1, то должна быть внешняя идентификация алгоритма, поэтому заголовок и колонтитул имеют следующий вид

-----BEGIN RSA PRIVATE KEY-----
[...]
-----END RSA PRIVATE KEY-----

Именно из-за структуры PKCS #8 мы разбирали (parsing) поле со смещением 22 ранее в тексте (параметр -strparse). Для разбора ключа PKCS #1 в формате PEM второй шаг не нужен.

Закрытый и открытый ключи

В алгоритме RSA открытый ключ вычисляется по модулю и открытой экспоненте, то есть мы всегда можем вывести открытый ключ из закрытого. В OpenSSL это легко сделать с помощью модуля rsa, создающего открытый ключ в формате PEM

$ openssl rsa -in private.pem -pubout
writing RSA key
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAsvX9P58JFxEs5C+L+H7W
duFSWL5EPzber7C2m94klrSV6q0bAcrYQnGwFOlveThsY200hRbadKaKjHD7qIKH
DEe0IY2PSRht33Jye52AwhkRw+M3xuQH/7R8LydnsNFk2KHpr5X2SBv42e37LjkE
slKSaMRgJW+v0KZ30piY8QsdFRKKaVg5/Ajt1YToM1YVsdHXJ3vmXFMtypLdxwUD
dIaLEX6pFUkU75KSuEQ/E2luT61Q3ta9kOWm9+0zvi7OMcbdekJT7mzcVnh93R1c
13ZhQCLbh9A7si8jKFtaMWevjayrvqQABEcTN9N4Hoxcyg6l4neZtRDk75OMYcqm
DQIDAQAB
-----END PUBLIC KEY-----

Вы можете выгрузить информацию, содержащуюся в открытом ключе, указав флаг -pubin

$ openssl rsa -in public.pem -noout -text -pubin
Public-Key: (2048 bit)
Modulus:
    00:b2:f5:fd:3f:9f:09:17:11:2c:e4:2f:8b:f8:7e:
    d6:76:e1:52:58:be:44:3f:36:de:af:b0:b6:9b:de:
    24:96:b4:95:ea:ad:1b:01:ca:d8:42:71:b0:14:e9:
    6f:79:38:6c:63:6d:34:85:16:da:74:a6:8a:8c:70:
    fb:a8:82:87:0c:47:b4:21:8d:8f:49:18:6d:df:72:
    72:7b:9d:80:c2:19:11:c3:e3:37:c6:e4:07:ff:b4:
    7c:2f:27:67:b0:d1:64:d8:a1:e9:af:95:f6:48:1b:
    f8:d9:ed:fb:2e:39:04:b2:52:92:68:c4:60:25:6f:
    af:d0:a6:77:d2:98:98:f1:0b:1d:15:12:8a:69:58:
    39:fc:08:ed:d5:84:e8:33:56:15:b1:d1:d7:27:7b:
    e6:5c:53:2d:ca:92:dd:c7:05:03:74:86:8b:11:7e:
    a9:15:49:14:ef:92:92:b8:44:3f:13:69:6e:4f:ad:
    50:de:d6:bd:90:e5:a6:f7:ed:33:be:2e:ce:31:c6:
    dd:7a:42:53:ee:6c:dc:56:78:7d:dd:1d:5c:d7:76:
    61:40:22:db:87:d0:3b:b2:2f:23:28:5b:5a:31:67:
    af:8d:ac:ab:be:a4:00:04:47:13:37:d3:78:1e:8c:
    5c:ca:0e:a5:e2:77:99:b5:10:e4:ef:93:8c:61:ca:
    a6:0d
Exponent: 65537 (0x10001)

Генерация ключевых пар с помощью OpenSSL

Сгенерировать закрытый ключ RSA можно с помощью OpenSSL

$ openssl genpkey -algorithm RSA -out private.pem \
  -pkeyopt rsa_keygen_bits:2048
......................................................................+++
..........+++ 

Утилита OpenSSL представляет собой набор модулей. В этом конкретном случае мы указываем модуль genpkey для генерации закрытого ключа. Опция -algorithm указывает, какой алгоритм мы хотим использовать для генерации ключа (в данном случае RSA), -out задает имя выходного файла, а -pkeyopt позволяет задать значение для определенных опций ключа. В данном случае это длина ключа RSA в битах.

Если вам нужен зашифрованный ключ, вы можете сгенерировать его, указав шифр (например, -aes-256-cbc)

$ openssl genpkey -algorithm RSA -out private-enc.pem \
  -aes-256-cbc -pkeyopt rsa_keygen_bits:2048
...........................+++
..........+++
Enter PEM pass phrase:
Verifying - Enter PEM pass phrase:

Список поддерживаемых шифров можно посмотреть с помощью openssl list-cipher-algorithms. В обоих случаях открытый ключ можно извлечь методом, описанным ранее. Закрытые ключи OpenSSL создаются с использованием PKCS #8, поэтому незашифрованные ключи будут иметь вид

-----BEGIN PRIVATE KEY-----
[...]
-----END PRIVATE KEY-----

а зашифрованные — в виде

-----BEGIN ENCRYPTED PRIVATE KEY-----
[...]
-----END ENCRYPTED PRIVATE KEY-----

Генерация пар ключей с помощью OpenSSH

Другим инструментом, с помощью которого можно сгенерировать пару ключей, является ssh-keygen — инструмент, входящий в состав пакета SSH и предназначенный для создания и управления ключами SSH. Поскольку ключи SSH являются стандартными асимметричными ключами, мы можем использовать этот инструмент для создания ключей для других целей.

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

ssh-keygen -m PEM -t rsa -b 2048 -f key
  • Опция -m задает формат ключа. По умолчанию OpenSSH использует собственный формат, указанный в RFC 4716 ("The Secure Shell (SSH) Public Key File Format").

  • Опция -t задает алгоритм генерации ключа (в данном случае RSA)

  • а опция -b — длину ключа в битах.

  • Опция -f задает имя выходного файла. Если она отсутствует, то ssh-keygen спросит имя файла и предложит сохранить его в файл по умолчанию ~/.ssh/id_rsa.

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

Утилита создаст два файла. Один из них — файл закрытого ключа, названный так, как было запрошено, а второй — файл открытого ключа, названный так же, как и закрытый, но с расширением .pub.

Значение PEM, указанное для опции -m, записывает закрытый ключ в формате PKCS #1, поэтому ключ будет иметь вид

-----BEGIN RSA PRIVATE KEY-----
[...]
-----END RSA PRIVATE KEY-----

Использование опции -m PKCS8 вместо этого использует PKCS #8, и ключ будет иметь вид

-----BEGIN PRIVATE KEY-----
[...]
-----END PRIVATE KEY-----

Формат открытого ключа OpenSSH

Открытый ключ, сохраняемый ssh-keygen, записывается в так называемом SSH-формате, который не является стандартом в мире криптографии. Его структура — ALGORITHM KEY COMMENT, где часть KEY формата закодирована в Base64.

Например

ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCy9f0/nwkXESzkL4v4ftZ24VJYvkQ/Nt6vsLab3iSWtJXqrRsBythCcbAU6W9
5OGxjbTSFFtp0poqMcPuogocMR7QhjY9JGG3fcnJ7nYDCGRHD4zfG5Af/tHwvJ2ew0WTYoemvlfZIG/jZ7fsuOQSyUpJoxGAlb6
/QpnfSmJjxCx0VEoppWDn8CO3VhOgzVhWx0dcne+ZcUy3Kkt3HBQN0hosRfqkVSRTvkpK4RD8TaW5PrVDe1r2Q5ab37TO+Ls4xx
t16QlPubNxWeH3dHVzXdmFAItuH0DuyLyMoW1oxZ6+NrKu+pAAERxM303gejFzKDqXid5m1EOTvk4xhyqYN user@host

Для того, чтобы вручную декодировать центральной части ключа можно использовать base64 и hexdump

$ cat key.pub | cut -d " " -f2 | \
  base64 -d | hexdump -ve '/1 "%02x "' -e '2/8 "\n"'
00 00 00 07 73 73 68 2d 72 73 61 00 00 00 03 01
00 01 00 00 01 01 00 b2 f5 fd 3f 9f 09 17 11 2c
e4 2f 8b f8 7e d6 76 e1 52 58 be 44 3f 36 de af
b0 b6 9b de 24 96 b4 95 ea ad 1b 01 ca d8 42 71
b0 14 e9 6f 79 38 6c 63 6d 34 85 16 da 74 a6 8a
8c 70 fb a8 82 87 0c 47 b4 21 8d 8f 49 18 6d df
72 72 7b 9d 80 c2 19 11 c3 e3 37 c6 e4 07 ff b4
7c 2f 27 67 b0 d1 64 d8 a1 e9 af 95 f6 48 1b f8
d9 ed fb 2e 39 04 b2 52 92 68 c4 60 25 6f af d0
a6 77 d2 98 98 f1 0b 1d 15 12 8a 69 58 39 fc 08
ed d5 84 e8 33 56 15 b1 d1 d7 27 7b e6 5c 53 2d
ca 92 dd c7 05 03 74 86 8b 11 7e a9 15 49 14 ef
92 92 b8 44 3f 13 69 6e 4f ad 50 de d6 bd 90 e5
a6 f7 ed 33 be 2e ce 31 c6 dd 7a 42 53 ee 6c dc
56 78 7d dd 1d 5c d7 76 61 40 22 db 87 d0 3b b2
2f 23 28 5b 5a 31 67 af 8d ac ab be a4 00 04 47
13 37 d3 78 1e 8c 5c ca 0e a5 e2 77 99 b5 10 e4
ef 93 8c 61 ca a6 0d

Структура этого двоичного файла довольно проста и описана в двух различных RFC. В разделе 6.6 RFC 4253 ("SSH Transport Layer Protocol") говорится, что

The "ssh-rsa" key format has the following specific encoding:

      string    "ssh-rsa"
      mpint     e
      mpint     n

а определение типов string и mpint можно найти в RFC 4251 ("SSH Protocol Architecture"), раздел 5

string

    [...] They are stored as a uint32 containing its length
    (number of bytes that follow) and zero (= empty string) or more
    bytes that are the value of the string.  Terminating null
    characters are not used. [...]

mpint

    Represents multiple precision integers in two's complement format,
    stored as a string, 8 bits per byte, MSB first. [...]

Это означает, что приведенная последовательность байтов интерпретируется как 4 байта длины (32 бита типа uint32), за которыми следует столько же байтов содержимого.

(4 bytes)   00 00 00 07          = 7
(7 bytes)   73 73 68 2d 72 73 61 = "ssh-rsa" (US-ASCII)
(4 bytes)   00 00 00 03          = 3
(3 bytes)   01 00 01             = 65537 (a common value for the RSA exponent)
(4 bytes)   00 00 01 01          = 257
(257 bytes) 00 b2 .. ca a6 0d    = The key modulus

Обратите внимание, что поскольку мы создали ключ длиной 2048 бит, то модуль должен составлять 256 байт. Вместо этого в данном ключе используется 257 байт, и байт 00 в качестве префикса. Это сделано, чтобы число не интерпретировалось как отрицательное (см "дополнение двойки").

Приведенная выше структура объясняет, почему все открытые SSH-ключи RSA начинаются с одних и тех же 12 символов AAAAB3NzaC1y. Эта строка, преобразованная в Base64, дает начальные 9 байт 00 00 00 07 73 73 68 2d 72 (символы Base64 не являются отображением исходных байт один к одному). Если используется стандартная экспонента 65537, то ключ начинается с AAAAB3NzaC1yc2EAAAADAQAB, что в кодировке дает первые 18 байт 00 00 00 07 73 73 68 2d 72 73 61 00 00 00 03 01 00 01.

Преобразование между форматом PEM и OpenSSH

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

PEM/PKCS#1 в PEM/PKCS#8. Это полезно для преобразования закрытых ключей OpenSSH в более новый формат

openssl pkcs8 -topk8 -inform PEM -outform PEM -in pkcs1.pem -out pkcs8.pem

Открытый ключ OpenSSH в PEM/PKCS#. Для преобразования открытых ключей OpenSSH в формат PEM с использованием PKCS #8 (выводится в stdout)

ssh-keygen -e -f public.pub -m PKCS8

Это легко запомнить, поскольку -e означает экспорт. Обратите внимание, что для преобразования ключа в формат PEM, использующий PKCS #1, можно также использовать -m PEM.

PEM/PKCS#8 в открытый ключ OpenSSH. Это пригодится, если ключевую пару созданную в другой системе, необходимо использовать для SSH

ssh-keygen -i -f public.pem -m PKCS8

Это легко запомнить, поскольку -i означает импорт. Как и при экспорте ключа, импортировать ключ PEM/PKCS #1 можно с помощью команды -m PEM.

Чтение ключей RSA в Python

В Python вы можете использовать пакет pycrypto для доступа к PEM-файлу, содержащему RSA-ключ, с помощью функции RSA.importKey. Теперь надеюсь, документация, будет чуть более понятной.

externKey (string) - Импортируемый RSA-ключ, закодированный в виде строки.

Открытый ключ RSA может быть представлен в любом из следующих форматов:
    * X.509 subjectPublicKeyInfo DER SEQUENCE (двоичное или PEM-кодирование)
    * PKCS#1 RSAPublicKey DER SEQUENCE (двоичная или PEM-кодировка)
    * OpenSSH (только текстовый открытый ключ)

Закрытый ключ RSA может быть представлен в любом из следующих форматов:
    * PKCS#1 RSAPrivateKey DER SEQUENCE (двоичная или PEM-кодировка)
    * PKCS#8 PrivateKeyInfo DER SEQUENCE (двоичная или PEM-кодировка)
    * OpenSSH (только текстовый открытый ключ)

Подробнее о кодировке PEM см. RFC1421/RFC1423.

В случае PEM-кодирования закрытый ключ может быть зашифрован с помощью DES или 3TDES
в соответствии с определенной фразой. Поддерживаются только OpenSSL-совместимые парольные фразы
поддерживаются.

На практике с файлом private.pem можно поступить следующим образом

from Crypto.PublicKey import RSA

f = open('private.pem', 'r')
key = RSA.importKey(f.read())

а переменная key будет содержать экземпляр _RSAobj. Этот экземпляр содержит параметры RSA в качестве атрибутов, как указано в документации

modulus = key.n
public_exponent = key.e
private_exponent = key.d
first_prime_number = key.p
second_prime_number = key.q
q_inv_crt = key.u

Заключение

Я постоянно встречаю на StackOverflow (и на других форумах) сообщения пользователей, которые путаются в ключах RSA, в выводах различных инструментов и в тонких, но важных различиях между форматами, поэтому я надеюсь, что эта заметка помогла вам лучше разобраться в этом вопросе.

Ресурсы

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


  1. Evengard
    24.10.2023 09:57
    +4

    А ещё для чтения ASN.1 кодировки есть вот такой вот очень удобный сайтик: https://lapo.it/asn1js/ - в своё время сильно помог.

    А ещё в том же ASN.1 хранятся криптографические сертификаты. Те самые, которые, например, сайты верифицируют.