Compare commits

..

6 Commits

Author SHA1 Message Date
brent saner
a369b9b5b7 fix rsa v1 plain example 2025-09-02 21:33:25 -04:00
brent saner
84838ce0c6 clean up the CRT notes 2025-09-02 00:23:38 -04:00
brent saner
ce2081c811 provide full name for Chinese Remainder Theorem 2025-09-01 23:58:23 -04:00
brent saner
1de61a888d use named links for each ident 2025-09-01 13:47:05 -04:00
4b1cfd0c50 releasing key guide under CC 4.0 BY-SA 2023-09-04 01:40:39 -04:00
b38739f960 Sooo the golang ChaCha20-Poly1305 is broken.
By design. It does not allow for the OpenSSH variant (https://github.com/golang/go/issues/36646).

So I'll need to split out that package (and their internal poly1305 package) and maintain an internal variant of it. Ugh.
2022-04-29 16:31:13 -04:00
33 changed files with 567 additions and 396 deletions

View File

@@ -6,3 +6,4 @@ set -e
cd "${PWD}/_ref"
asciidoctor -o ./KEY_GUIDE.html ./KEY_GUIDE.adoc
git add KEY_GUIDE.html
echo "Generated KEY_GUIDE.html"

3
.gitignore vendored
View File

@@ -30,6 +30,9 @@
*.test
!*test.go
# Junk for figuring stuff out
/poc/
# Output of the go coverage tool, specifically when used with LiteIDE
*.out

View File

@@ -3,3 +3,5 @@
OpenSSH key parsing, generation, etc. library for Golang.
Supports newer "proprietary" key type formatting ("OpenSSH v1").
**WORK IN PROGRESS**

View File

@@ -15,17 +15,28 @@ Last updated {localdatetime}
:source-highlighter: rouge
:docinfo: shared
////
This work is licensed under the Creative Commons Attribution-ShareAlike 4.0 International License.
To view a copy of this license, visit
http://creativecommons.org/licenses/by-sa/4.0/.
////
[id="why"]
== Purpose
This document attempts to present a much more detailed, thorough, and easily-understood form of the key formats used by OpenSSH. The extent of those formats' canonical documentation is https://github.com/openssh/openssh-portable/blob/master/PROTOCOL.key[the OpenSSH source tree's `PROTOCOL.key`^], which is a little lacking.
[id="intro"]
== Basic Introduction
=== Legacy
==== Private Keys
[id="intro_legc"]
=== Legacy
[id="intro_legc_priv"]
==== Private Keys
In OpenSSH pre-7.8, private keys are stored in their respective PEM encodingfootnote:[https://datatracker.ietf.org/doc/html/rfc7468] with no modification. These legacy private keys should be entirely usable by OpenSSL/LibreSSL/GnuTLS etc. natively with no conversion necessary.
[id="intro_legc_pub"]
==== Public Keys
Each public key *file* (`*.pub`) is written out in the following format:
A B C
@@ -38,13 +49,14 @@ C:: The key's comment
The structures specified in the breakdowns later in this document describe the _decoded_ version of *B* *_only_*. They are specific to each keytype and format version starting with item `2.0`.
[id="intro_v1"]
=== New "v1" Format
==== Private Keys
[id="intro_v1_priv"]
==== Private Keys
Private key structures have been retooled in the "v1" format. In recent OpenSSH versions, all new keys use the v1 format. They no longer are in straight PEM-compatible format.
Refer to https://github.com/openssh/openssh-portable/blob/master/PROTOCOL.key[`PROTOCOL.key`^] for a (very) general description, or each key's specific breakdown for more detailed information.
Refer to https://anongit.mindrot.org/openssh.git/tree/PROTOCOL.key[`PROTOCOL.key`^] (https://github.com/openssh/openssh-portable/blob/master/PROTOCOL.key[GitHub mirror^]) for a (very) general description, or each key type's specific breakdown in this document for more detailed information.
The v1 format offers several benefits over the legacy format, including:
@@ -53,12 +65,26 @@ The v1 format offers several benefits over the legacy format, including:
* embedded public key (no need to derive from the private key)
* "checksumming" to confirm proper decryption for encrypted keys
[id="intro_v1_pub"]
==== Public Keys
All public keys in v1 continue to use the same packed binary format as <<intro_legc_pub, the legacy format>>.
All public keys in v1 continue to use the same packed binary format as <<public_keys, the legacy format>>.
[id="bkdn"]
== Keytype-Specific Breakdowns
include::rsa/main.adoc[]
include::ed25519/main.adoc[]
[id="moar"]
== Further Information
++++
<a rel="license" href="http://creativecommons.org/licenses/by-sa/4.0/">
<img alt="Creative Commons License" style="border-width:0" src="https://i.creativecommons.org/l/by-sa/4.0/88x31.png" />
</a><br />
<span xmlns:dct="http://purl.org/dc/terms/" href="http://purl.org/dc/dcmitype/Text" property="dct:title" rel="dct:type">OpenSSH Key Structure Guide</span> by <a xmlns:cc="http://creativecommons.org/ns#" href="https://r00t2.io/" property="cc:attributionName" rel="cc:attributionURL">Brent Saner</a> is licensed under a <a rel="license" href="http://creativecommons.org/licenses/by-sa/4.0/">Creative Commons Attribution-ShareAlike 4.0 International License</a>.<br />
<br />
++++
You are free to use, distribute, modify, redistribute, use for commercial purposes, etc. with very few restrictions; please see http://creativecommons.org/licenses/by-sa/4.0/[the license summary^] and https://creativecommons.org/licenses/by-sa/4.0/legalcode[full license^] for further details.

View File

@@ -4,7 +4,7 @@
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="generator" content="Asciidoctor 2.0.17">
<meta name="generator" content="Asciidoctor 2.0.23">
<meta name="author" content="brent saner &lt;bts@square-r00t.net&gt;, https://r00t2.io">
<title>OpenSSH Key Structure Guide</title>
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Open+Sans:300,300italic,400,400italic,600,600italic%7CNoto+Serif:400,400italic,700,700italic%7CDroid+Sans+Mono:400,700">
@@ -85,10 +85,10 @@ code{font-family:"Droid Sans Mono","DejaVu Sans Mono",monospace;font-weight:400;
ul,ol,dl{line-height:1.6;margin-bottom:1.25em;list-style-position:outside;font-family:inherit}
ul,ol{margin-left:1.5em}
ul li ul,ul li ol{margin-left:1.25em;margin-bottom:0}
ul.square li ul,ul.circle li ul,ul.disc li ul{list-style:inherit}
ul.square{list-style-type:square}
ul.circle{list-style-type:circle}
ul.disc{list-style-type:disc}
ul.square{list-style-type:square}
ul.circle ul:not([class]),ul.disc ul:not([class]),ul.square ul:not([class]){list-style:inherit}
ol li ul,ol li ol{margin-left:1.25em;margin-bottom:0}
dl dt{margin-bottom:.3125em;font-weight:bold}
dl dd{margin-bottom:1.25em}
@@ -141,7 +141,7 @@ p a>code:hover{color:rgba(0,0,0,.9)}
#content::before{content:none}
#header>h1:first-child{color:rgba(0,0,0,.85);margin-top:2.25rem;margin-bottom:0}
#header>h1:first-child+#toc{margin-top:8px;border-top:1px solid #dddddf}
#header>h1:only-child,body.toc2 #header>h1:nth-last-child(2){border-bottom:1px solid #dddddf;padding-bottom:8px}
#header>h1:only-child{border-bottom:1px solid #dddddf;padding-bottom:8px}
#header .details{border-bottom:1px solid #dddddf;line-height:1.45;padding-top:.25em;padding-bottom:.25em;padding-left:.25em;color:rgba(0,0,0,.6);display:flex;flex-flow:row wrap}
#header .details span:first-child{margin-left:-.125em}
#header .details span.email a{color:rgba(0,0,0,.85)}
@@ -163,6 +163,7 @@ p a>code:hover{color:rgba(0,0,0,.9)}
#toctitle{color:#7a2518;font-size:1.2em}
@media screen and (min-width:768px){#toctitle{font-size:1.375em}
body.toc2{padding-left:15em;padding-right:0}
body.toc2 #header>h1:nth-last-child(2){border-bottom:1px solid #dddddf;padding-bottom:8px}
#toc.toc2{margin-top:0!important;background:#f8f8f7;position:fixed;width:15em;left:0;top:0;border-right:1px solid #e7e7e9;border-top-width:0!important;border-bottom-width:0!important;z-index:1000;padding:1.25em 1em;height:100%;overflow:auto}
#toc.toc2 #toctitle{margin-top:0;margin-bottom:.8rem;font-size:1.2em}
#toc.toc2>ul{font-size:.9em;margin-bottom:0}
@@ -209,13 +210,10 @@ table.tableblock.fit-content>caption.title{white-space:nowrap;width:0}
.admonitionblock>table td.content{padding-left:1.125em;padding-right:1.25em;border-left:1px solid #dddddf;color:rgba(0,0,0,.6);word-wrap:anywhere}
.admonitionblock>table td.content>:last-child>:last-child{margin-bottom:0}
.exampleblock>.content{border:1px solid #e6e6e6;margin-bottom:1.25em;padding:1.25em;background:#fff;border-radius:4px}
.exampleblock>.content>:first-child{margin-top:0}
.exampleblock>.content>:last-child{margin-bottom:0}
.sidebarblock{border:1px solid #dbdbd6;margin-bottom:1.25em;padding:1.25em;background:#f3f3f2;border-radius:4px}
.sidebarblock>:first-child{margin-top:0}
.sidebarblock>:last-child{margin-bottom:0}
.sidebarblock>.content>.title{color:#7a2518;margin-top:0;text-align:center}
.exampleblock>.content>:last-child>:last-child,.exampleblock>.content .olist>ol>li:last-child>:last-child,.exampleblock>.content .ulist>ul>li:last-child>:last-child,.exampleblock>.content .qlist>ol>li:last-child>:last-child,.sidebarblock>.content>:last-child>:last-child,.sidebarblock>.content .olist>ol>li:last-child>:last-child,.sidebarblock>.content .ulist>ul>li:last-child>:last-child,.sidebarblock>.content .qlist>ol>li:last-child>:last-child{margin-bottom:0}
.exampleblock>.content>:first-child,.sidebarblock>.content>:first-child{margin-top:0}
.exampleblock>.content>:last-child,.exampleblock>.content>:last-child>:last-child,.exampleblock>.content .olist>ol>li:last-child>:last-child,.exampleblock>.content .ulist>ul>li:last-child>:last-child,.exampleblock>.content .qlist>ol>li:last-child>:last-child,.sidebarblock>.content>:last-child,.sidebarblock>.content>:last-child>:last-child,.sidebarblock>.content .olist>ol>li:last-child>:last-child,.sidebarblock>.content .ulist>ul>li:last-child>:last-child,.sidebarblock>.content .qlist>ol>li:last-child>:last-child{margin-bottom:0}
.literalblock pre,.listingblock>.content>pre{border-radius:4px;overflow-x:auto;padding:1em;font-size:.8125em}
@media screen and (min-width:768px){.literalblock pre,.listingblock>.content>pre{font-size:.90625em}}
@media screen and (min-width:1280px){.literalblock pre,.listingblock>.content>pre{font-size:1em}}
@@ -331,7 +329,7 @@ a.image{text-decoration:none;display:inline-block}
a.image object{pointer-events:none}
sup.footnote,sup.footnoteref{font-size:.875em;position:static;vertical-align:super}
sup.footnote a,sup.footnoteref a{text-decoration:none}
sup.footnote a:active,sup.footnoteref a:active{text-decoration:underline}
sup.footnote a:active,sup.footnoteref a:active,#footnotes .footnote a:first-of-type:active{text-decoration:underline}
#footnotes{padding-top:.75em;padding-bottom:.75em;margin-bottom:.625em}
#footnotes hr{width:20%;min-width:6.25em;margin:-.25em 0 .75em;border-width:1px 0 0}
#footnotes .footnote{padding:0 .375em 0 .225em;line-height:1.3334;font-size:.875em;margin-left:1.2em;margin-bottom:.2em}
@@ -394,7 +392,7 @@ b.conum *{color:inherit!important}
dt,th.tableblock,td.content,div.footnote{text-rendering:optimizeLegibility}
h1,h2,p,td.content,span.alt,summary{letter-spacing:-.01em}
p strong,td.content strong,div.footnote strong{letter-spacing:-.005em}
p,blockquote,dt,td.content,span.alt,summary{font-size:1.0625rem}
p,blockquote,dt,td.content,td.hdlist1,span.alt,summary{font-size:1.0625rem}
p{margin-bottom:1.25rem}
.sidebarblock p,.sidebarblock dt,.sidebarblock td.content,p.tableblock{font-size:1em}
.exampleblock>.content{background:#fffef7;border-color:#e0e0dc;box-shadow:0 1px 4px #e0e0dc}
@@ -439,217 +437,120 @@ body.book #toc,body.book #preamble,body.book h1.sect0,body.book .sect1>h2{page-b
<style>
pre.rouge table td { padding: 5px; }
pre.rouge table pre { margin: 0; }
pre.rouge .cm {
color: #999988;
font-style: italic;
pre.rouge, pre.rouge .w {
color: #24292f;
background-color: #f6f8fa;
}
pre.rouge .cp {
color: #999999;
font-weight: bold;
}
pre.rouge .c1 {
color: #999988;
font-style: italic;
}
pre.rouge .cs {
color: #999999;
font-weight: bold;
font-style: italic;
}
pre.rouge .c, pre.rouge .ch, pre.rouge .cd, pre.rouge .cpf {
color: #999988;
font-style: italic;
}
pre.rouge .err {
color: #a61717;
background-color: #e3d2d2;
}
pre.rouge .gd {
color: #000000;
background-color: #ffdddd;
}
pre.rouge .ge {
color: #000000;
font-style: italic;
pre.rouge .k, pre.rouge .kd, pre.rouge .kn, pre.rouge .kp, pre.rouge .kr, pre.rouge .kt, pre.rouge .kv {
color: #cf222e;
}
pre.rouge .gr {
color: #aa0000;
color: #f6f8fa;
}
pre.rouge .gh {
color: #999999;
pre.rouge .gd {
color: #82071e;
background-color: #ffebe9;
}
pre.rouge .nb {
color: #953800;
}
pre.rouge .nc {
color: #953800;
}
pre.rouge .no {
color: #953800;
}
pre.rouge .nn {
color: #953800;
}
pre.rouge .sr {
color: #116329;
}
pre.rouge .na {
color: #116329;
}
pre.rouge .nt {
color: #116329;
}
pre.rouge .gi {
color: #000000;
background-color: #ddffdd;
color: #116329;
background-color: #dafbe1;
}
pre.rouge .go {
color: #888888;
pre.rouge .ges {
font-weight: bold;
font-style: italic;
}
pre.rouge .gp {
color: #555555;
pre.rouge .kc {
color: #0550ae;
}
pre.rouge .gs {
pre.rouge .l, pre.rouge .ld, pre.rouge .m, pre.rouge .mb, pre.rouge .mf, pre.rouge .mh, pre.rouge .mi, pre.rouge .il, pre.rouge .mo, pre.rouge .mx {
color: #0550ae;
}
pre.rouge .sb {
color: #0550ae;
}
pre.rouge .bp {
color: #0550ae;
}
pre.rouge .ne {
color: #0550ae;
}
pre.rouge .nl {
color: #0550ae;
}
pre.rouge .py {
color: #0550ae;
}
pre.rouge .nv, pre.rouge .vc, pre.rouge .vg, pre.rouge .vi, pre.rouge .vm {
color: #0550ae;
}
pre.rouge .o, pre.rouge .ow {
color: #0550ae;
}
pre.rouge .gh {
color: #0550ae;
font-weight: bold;
}
pre.rouge .gu {
color: #aaaaaa;
}
pre.rouge .gt {
color: #aa0000;
}
pre.rouge .kc {
color: #000000;
color: #0550ae;
font-weight: bold;
}
pre.rouge .kd {
color: #000000;
font-weight: bold;
}
pre.rouge .kn {
color: #000000;
font-weight: bold;
}
pre.rouge .kp {
color: #000000;
font-weight: bold;
}
pre.rouge .kr {
color: #000000;
font-weight: bold;
}
pre.rouge .kt {
color: #445588;
font-weight: bold;
}
pre.rouge .k, pre.rouge .kv {
color: #000000;
font-weight: bold;
}
pre.rouge .mf {
color: #009999;
}
pre.rouge .mh {
color: #009999;
}
pre.rouge .il {
color: #009999;
}
pre.rouge .mi {
color: #009999;
}
pre.rouge .mo {
color: #009999;
}
pre.rouge .m, pre.rouge .mb, pre.rouge .mx {
color: #009999;
}
pre.rouge .sa {
color: #000000;
font-weight: bold;
}
pre.rouge .sb {
color: #d14;
}
pre.rouge .sc {
color: #d14;
}
pre.rouge .sd {
color: #d14;
}
pre.rouge .s2 {
color: #d14;
}
pre.rouge .se {
color: #d14;
}
pre.rouge .sh {
color: #d14;
}
pre.rouge .si {
color: #d14;
}
pre.rouge .sx {
color: #d14;
}
pre.rouge .sr {
color: #009926;
}
pre.rouge .s1 {
color: #d14;
}
pre.rouge .ss {
color: #990073;
}
pre.rouge .s, pre.rouge .dl {
color: #d14;
}
pre.rouge .na {
color: #008080;
}
pre.rouge .bp {
color: #999999;
}
pre.rouge .nb {
color: #0086B3;
}
pre.rouge .nc {
color: #445588;
font-weight: bold;
}
pre.rouge .no {
color: #008080;
pre.rouge .s, pre.rouge .sa, pre.rouge .sc, pre.rouge .dl, pre.rouge .sd, pre.rouge .s2, pre.rouge .se, pre.rouge .sh, pre.rouge .sx, pre.rouge .s1, pre.rouge .ss {
color: #0a3069;
}
pre.rouge .nd {
color: #3c5d5d;
font-weight: bold;
}
pre.rouge .ni {
color: #800080;
}
pre.rouge .ne {
color: #990000;
font-weight: bold;
color: #8250df;
}
pre.rouge .nf, pre.rouge .fm {
color: #990000;
color: #8250df;
}
pre.rouge .err {
color: #f6f8fa;
background-color: #82071e;
}
pre.rouge .c, pre.rouge .ch, pre.rouge .cd, pre.rouge .cm, pre.rouge .cp, pre.rouge .cpf, pre.rouge .c1, pre.rouge .cs {
color: #6e7781;
}
pre.rouge .gl {
color: #6e7781;
}
pre.rouge .gt {
color: #6e7781;
}
pre.rouge .ni {
color: #24292f;
}
pre.rouge .si {
color: #24292f;
}
pre.rouge .ge {
color: #24292f;
font-style: italic;
}
pre.rouge .gs {
color: #24292f;
font-weight: bold;
}
pre.rouge .nl {
color: #990000;
font-weight: bold;
}
pre.rouge .nn {
color: #555555;
}
pre.rouge .nt {
color: #000080;
}
pre.rouge .vc {
color: #008080;
}
pre.rouge .vg {
color: #008080;
}
pre.rouge .vi {
color: #008080;
}
pre.rouge .nv, pre.rouge .vm {
color: #008080;
}
pre.rouge .ow {
color: #000000;
font-weight: bold;
}
pre.rouge .o {
color: #000000;
font-weight: bold;
}
pre.rouge .w {
color: #bbbbbb;
}
pre.rouge {
background-color: #f8f8f8;
}
</style>
<!-- https://stackoverflow.com/a/34481639 -->
<!-- Generate a nice TOC -->
@@ -734,89 +635,89 @@ pre.rouge {
<h1>OpenSSH Key Structure Guide</h1>
<div class="details">
<span id="author" class="author">brent saner &lt;bts@square-r00t.net&gt;, https://r00t2.io</span><br>
<span id="revdate">Last updated 2022-04-29 04:09:49 -0400</span>
<span id="revdate">Last updated 2025-09-02 21:33:25 -0400</span>
</div>
<div id="toc" class="toc2">
<div id="toctitle">Table of Contents</div>
<ul class="sectlevel1">
<li><a href="#purpose">1. Purpose</a></li>
<li><a href="#basic_introduction">2. Basic Introduction</a>
<li><a href="#why">1. Purpose</a></li>
<li><a href="#intro">2. Basic Introduction</a>
<ul class="sectlevel2">
<li><a href="#legacy">2.1. Legacy</a>
<li><a href="#intro_legc">2.1. Legacy</a>
<ul class="sectlevel3">
<li><a href="#private_keys">2.1.1. Private Keys</a></li>
<li><a href="#public_keys">2.1.2. Public Keys</a></li>
<li><a href="#intro_legc_priv">2.1.1. Private Keys</a></li>
<li><a href="#intro_legc_pub">2.1.2. Public Keys</a></li>
</ul>
</li>
<li><a href="#new_v1_format">2.2. New "v1" Format</a>
<li><a href="#intro_v1">2.2. New "v1" Format</a>
<ul class="sectlevel3">
<li><a href="#private_keys_2">2.2.1. Private Keys</a></li>
<li><a href="#public_keys_2">2.2.2. Public Keys</a></li>
<li><a href="#intro_v1_priv">2.2.1. Private Keys</a></li>
<li><a href="#intro_v1_pub">2.2.2. Public Keys</a></li>
</ul>
</li>
</ul>
</li>
<li><a href="#keytype_specific_breakdowns">3. Keytype-Specific Breakdowns</a>
<li><a href="#bkdn">3. Keytype-Specific Breakdowns</a>
<ul class="sectlevel2">
<li><a href="#rsa">3.1. RSA</a>
<li><a href="#bkdn_rsa">3.1. RSA</a>
<ul class="sectlevel3">
<li><a href="#public">3.1.1. Public</a>
<li><a href="#bkdn_rsa_pub">3.1.1. Public</a>
<ul class="sectlevel4">
<li><a href="#structure">3.1.1.1. Structure</a></li>
<li><a href="#example">3.1.1.2. Example</a></li>
<li><a href="#bkdn_rsa_pub_struct">3.1.1.1. Structure</a></li>
<li><a href="#bkdn_rsa_pub_ex">3.1.1.2. Example</a></li>
</ul>
</li>
<li><a href="#private">3.1.2. Private</a>
<li><a href="#bkdn_rsa_priv">3.1.2. Private</a>
<ul class="sectlevel4">
<li><a href="#legacy_plain">3.1.2.1. Legacy (Plain)</a>
<li><a href="#bkdn_rsa_priv_legc_plain">3.1.2.1. Legacy (Plain)</a>
<ul class="sectlevel5">
<li><a href="#struct_rsa_plain_legacy">3.1.2.1.1. Structure</a></li>
<li><a href="#bytes_rsa_plain_legacy">3.1.2.1.2. Example</a></li>
<li><a href="#bkdn_rsa_priv_legc_plain_struct">3.1.2.1.1. Structure</a></li>
<li><a href="#bkdn_rsa_priv_legc_plain_ex">3.1.2.1.2. Example</a></li>
</ul>
</li>
<li><a href="#legacy_encrypted">3.1.2.2. Legacy (Encrypted)</a>
<li><a href="#bkdn_rsa_priv_legc_crypt">3.1.2.2. Legacy (Encrypted)</a>
<ul class="sectlevel5">
<li><a href="#struct_rsa_crypt_legacy">3.1.2.2.1. Structure</a></li>
<li><a href="#bytes_rsa_crypt_legacy">3.1.2.2.2. Example</a></li>
<li><a href="#bkdn_rsa_priv_legc_crypt_struct">3.1.2.2.1. Structure</a></li>
<li><a href="#bkdn_rsa_priv_legc_crypt_ex">3.1.2.2.2. Example</a></li>
</ul>
</li>
<li><a href="#v1_plain">3.1.2.3. v1 (Plain)</a>
<li><a href="#bkdn_rsa_priv_v1_plain">3.1.2.3. v1 (Plain)</a>
<ul class="sectlevel5">
<li><a href="#struct_rsa_plain">3.1.2.3.1. Structure</a></li>
<li><a href="#bytes_rsa_plain">3.1.2.3.2. Example</a></li>
<li><a href="#bkdn_rsa_priv_v1_plain_struct">3.1.2.3.1. Structure</a></li>
<li><a href="#bkdn_rsa_priv_v1_plain_ex">3.1.2.3.2. Example</a></li>
</ul>
</li>
<li><a href="#v1_encrypted">3.1.2.4. v1 (Encrypted)</a>
<li><a href="#bkdn_rsa_priv_v1_crypt">3.1.2.4. v1 (Encrypted)</a>
<ul class="sectlevel5">
<li><a href="#struct_rsa_crypt">3.1.2.4.1. Structure</a></li>
<li><a href="#bytes_rsa_crypt">3.1.2.4.2. Example</a></li>
<li><a href="#bkdn_rsa_priv_v1_crypt_struct">3.1.2.4.1. Structure</a></li>
<li><a href="#bkdn_rsa_priv_v1_crypt_ex">3.1.2.4.2. Example</a></li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
<li><a href="#ed25519">3.2. ED25519</a>
<li><a href="#bkdn_ed25519">3.2. ED25519</a>
<ul class="sectlevel3">
<li><a href="#public_2">3.2.1. Public</a>
<li><a href="#bkdn_ed25519_pub">3.2.1. Public</a>
<ul class="sectlevel4">
<li><a href="#structure_2">3.2.1.1. Structure</a></li>
<li><a href="#example_2">3.2.1.2. Example</a></li>
<li><a href="#bkdn_ed25519_pub_struct">3.2.1.1. Structure</a></li>
<li><a href="#bkdn_ed25519_pub_ex">3.2.1.2. Example</a></li>
</ul>
</li>
<li><a href="#private_2">3.2.2. Private</a>
<li><a href="#bkdn_ed25519_priv">3.2.2. Private</a>
<ul class="sectlevel4">
<li><a href="#legacy_2">3.2.2.1. Legacy</a></li>
<li><a href="#v1_plain_2">3.2.2.2. v1 (Plain)</a>
<li><a href="#bkdn_ed25519_priv_legc">3.2.2.1. Legacy</a></li>
<li><a href="#bkdn_ed25519_priv_v1_plain">3.2.2.2. v1 (Plain)</a>
<ul class="sectlevel5">
<li><a href="#struct_ed25519_plain">3.2.2.2.1. Structure</a></li>
<li><a href="#bytes_ed25519_plain">3.2.2.2.2. Example</a></li>
<li><a href="#bkdn_ed25519_priv_v1_plain_struct">3.2.2.2.1. Structure</a></li>
<li><a href="#bkdn_ed25519_priv_v1_plain_ex">3.2.2.2.2. Example</a></li>
</ul>
</li>
<li><a href="#v1_encrypted_2">3.2.2.3. v1 (Encrypted)</a>
<li><a href="#bkdn_ed25519_priv_v1_crypt">3.2.2.3. v1 (Encrypted)</a>
<ul class="sectlevel5">
<li><a href="#struct_ed25519_crypt">3.2.2.3.1. Structure</a></li>
<li><a href="#bytes_ed25519_crypt">3.2.2.3.2. Example</a></li>
<li><a href="#bkdn_ed25519_priv_v1_crypt_struct">3.2.2.3.1. Structure</a></li>
<li><a href="#bkdn_ed25519_priv_v1_crypt_ex">3.2.2.3.2. Example</a></li>
</ul>
</li>
</ul>
@@ -825,12 +726,13 @@ pre.rouge {
</li>
</ul>
</li>
<li><a href="#moar">4. Further Information</a></li>
</ul>
</div>
</div>
<div id="content">
<div class="sect1">
<h2 id="purpose"><a class="link" href="#purpose">1. Purpose</a></h2>
<h2 id="why"><a class="link" href="#why">1. Purpose</a></h2>
<div class="sectionbody">
<div class="paragraph">
<p>This document attempts to present a much more detailed, thorough, and easily-understood form of the key formats used by OpenSSH. The extent of those formats' canonical documentation is <a href="https://github.com/openssh/openssh-portable/blob/master/PROTOCOL.key" target="_blank" rel="noopener">the OpenSSH source tree&#8217;s <code>PROTOCOL.key</code></a>, which is a little lacking.</p>
@@ -838,18 +740,18 @@ pre.rouge {
</div>
</div>
<div class="sect1">
<h2 id="basic_introduction"><a class="link" href="#basic_introduction">2. Basic Introduction</a></h2>
<h2 id="intro"><a class="link" href="#intro">2. Basic Introduction</a></h2>
<div class="sectionbody">
<div class="sect2">
<h3 id="legacy"><a class="link" href="#legacy">2.1. Legacy</a></h3>
<h3 id="intro_legc"><a class="link" href="#intro_legc">2.1. Legacy</a></h3>
<div class="sect3">
<h4 id="private_keys"><a class="link" href="#private_keys">2.1.1. Private Keys</a></h4>
<h4 id="intro_legc_priv"><a class="link" href="#intro_legc_priv">2.1.1. Private Keys</a></h4>
<div class="paragraph">
<p>In OpenSSH pre-7.8, private keys are stored in their respective PEM encoding<sup class="footnote">[<a id="_footnoteref_1" class="footnote" href="#_footnotedef_1" title="View footnote.">1</a>]</sup> with no modification. These legacy private keys should be entirely usable by OpenSSL/LibreSSL/GnuTLS etc. natively with no conversion necessary.</p>
</div>
</div>
<div class="sect3">
<h4 id="public_keys"><a class="link" href="#public_keys">2.1.2. Public Keys</a></h4>
<h4 id="intro_legc_pub"><a class="link" href="#intro_legc_pub">2.1.2. Public Keys</a></h4>
<div class="paragraph">
<p>Each public key <strong>file</strong> (<code>*.pub</code>) is written out in the following format:</p>
</div>
@@ -883,14 +785,14 @@ pre.rouge {
</div>
</div>
<div class="sect2">
<h3 id="new_v1_format"><a class="link" href="#new_v1_format">2.2. New "v1" Format</a></h3>
<h3 id="intro_v1"><a class="link" href="#intro_v1">2.2. New "v1" Format</a></h3>
<div class="sect3">
<h4 id="private_keys_2"><a class="link" href="#private_keys_2">2.2.1. Private Keys</a></h4>
<h4 id="intro_v1_priv"><a class="link" href="#intro_v1_priv">2.2.1. Private Keys</a></h4>
<div class="paragraph">
<p>Private key structures have been retooled in the "v1" format. In recent OpenSSH versions, all new keys use the v1 format. They no longer are in straight PEM-compatible format.</p>
</div>
<div class="paragraph">
<p>Refer to <a href="https://github.com/openssh/openssh-portable/blob/master/PROTOCOL.key" target="_blank" rel="noopener"><code>PROTOCOL.key</code></a> for a (very) general description, or each key&#8217;s specific breakdown for more detailed information.</p>
<p>Refer to <a href="https://anongit.mindrot.org/openssh.git/tree/PROTOCOL.key" target="_blank" rel="noopener"><code>PROTOCOL.key</code></a> (<a href="https://github.com/openssh/openssh-portable/blob/master/PROTOCOL.key" target="_blank" rel="noopener">GitHub mirror</a>) for a (very) general description, or each key type&#8217;s specific breakdown in this document for more detailed information.</p>
</div>
<div class="paragraph">
<p>The v1 format offers several benefits over the legacy format, including:</p>
@@ -913,19 +815,19 @@ pre.rouge {
</div>
</div>
<div class="sect3">
<h4 id="public_keys_2"><a class="link" href="#public_keys_2">2.2.2. Public Keys</a></h4>
<h4 id="intro_v1_pub"><a class="link" href="#intro_v1_pub">2.2.2. Public Keys</a></h4>
<div class="paragraph">
<p>All public keys in v1 continue to use the same packed binary format as <a href="#public_keys">the legacy format</a>.</p>
<p>All public keys in v1 continue to use the same packed binary format as <a href="#intro_legc_pub">the legacy format</a>.</p>
</div>
</div>
</div>
</div>
</div>
<div class="sect1">
<h2 id="keytype_specific_breakdowns"><a class="link" href="#keytype_specific_breakdowns">3. Keytype-Specific Breakdowns</a></h2>
<h2 id="bkdn"><a class="link" href="#bkdn">3. Keytype-Specific Breakdowns</a></h2>
<div class="sectionbody">
<div class="sect2">
<h3 id="rsa"><a class="link" href="#rsa">3.1. RSA</a></h3>
<h3 id="bkdn_rsa"><a class="link" href="#bkdn_rsa">3.1. RSA</a></h3>
<div class="paragraph">
<p>RSA<sup class="footnote">[<a id="_footnoteref_3" class="footnote" href="#_footnotedef_3" title="View footnote.">3</a>]</sup> is a widely-supported PKI system. It is ubiquitous, but it is recommended to use newer systems (e.g. ED25519) for OpenSSH if all clients and destinations support it.</p>
</div>
@@ -936,9 +838,9 @@ pre.rouge {
<p>It is <strong>highly</strong> recommended to use 4096-bit RSA if using RSA keys.</p>
</div>
<div class="sect3">
<h4 id="public"><a class="link" href="#public">3.1.1. Public</a></h4>
<h4 id="bkdn_rsa_pub"><a class="link" href="#bkdn_rsa_pub">3.1.1. Public</a></h4>
<div class="sect4">
<h5 id="structure"><a class="link" href="#structure">3.1.1.1. Structure</a></h5>
<h5 id="bkdn_rsa_pub_struct"><a class="link" href="#bkdn_rsa_pub_struct">3.1.1.1. Structure</a></h5>
<div class="paragraph">
<p>Public keys are stored in the following structure:</p>
</div>
@@ -962,7 +864,7 @@ pre.rouge {
</div>
</div>
<div class="sect4">
<h5 id="example"><a class="link" href="#example">3.1.1.2. Example</a></h5>
<h5 id="bkdn_rsa_pub_ex"><a class="link" href="#bkdn_rsa_pub_ex">3.1.1.2. Example</a></h5>
<div class="listingblock">
<div class="title"><code>.pub</code> format</div>
<div class="content">
@@ -1024,17 +926,17 @@ pre.rouge {
</div>
</div>
<div class="sect3">
<h4 id="private"><a class="link" href="#private">3.1.2. Private</a></h4>
<h4 id="bkdn_rsa_priv"><a class="link" href="#bkdn_rsa_priv">3.1.2. Private</a></h4>
<div class="sect4">
<h5 id="legacy_plain"><a class="link" href="#legacy_plain">3.1.2.1. Legacy (Plain)</a></h5>
<h5 id="bkdn_rsa_priv_legc_plain"><a class="link" href="#bkdn_rsa_priv_legc_plain">3.1.2.1. Legacy (Plain)</a></h5>
<div class="sect5">
<h6 id="struct_rsa_plain_legacy"><a class="link" href="#struct_rsa_plain_legacy">3.1.2.1.1. Structure</a></h6>
<h6 id="bkdn_rsa_priv_legc_plain_struct"><a class="link" href="#bkdn_rsa_priv_legc_plain_struct">3.1.2.1.1. Structure</a></h6>
<div class="paragraph">
<p>Legacy private keys are encoded in standard RSA PEM format (<a href="https://datatracker.ietf.org/doc/html/rfc7468" target="_blank" rel="noopener">RFC 7468</a> § <a href="https://datatracker.ietf.org/doc/html/rfc7468#section-10" target="_blank" rel="noopener">10</a>, <a href="https://datatracker.ietf.org/doc/html/rfc3447#appendix-A" target="_blank" rel="noopener">APPENDIX-A</a>).</p>
</div>
</div>
<div class="sect5">
<h6 id="bytes_rsa_plain_legacy"><a class="link" href="#bytes_rsa_plain_legacy">3.1.2.1.2. Example</a></h6>
<h6 id="bkdn_rsa_priv_legc_plain_ex"><a class="link" href="#bkdn_rsa_priv_legc_plain_ex">3.1.2.1.2. Example</a></h6>
<div class="listingblock">
<div class="content">
<pre class="rouge highlight"><code data-lang="text"><table class="linenotable"><tbody><tr><td class="linenos gl"><pre class="lineno"> 1
@@ -1145,9 +1047,9 @@ Zb7jkiz4m88ol7ezdWZyHhVMZqy4bWMCI4moTDcpqJuox6JTQiO2Ajj2pFU=
</div>
</div>
<div class="sect4">
<h5 id="legacy_encrypted"><a class="link" href="#legacy_encrypted">3.1.2.2. Legacy (Encrypted)</a></h5>
<h5 id="bkdn_rsa_priv_legc_crypt"><a class="link" href="#bkdn_rsa_priv_legc_crypt">3.1.2.2. Legacy (Encrypted)</a></h5>
<div class="sect5">
<h6 id="struct_rsa_crypt_legacy"><a class="link" href="#struct_rsa_crypt_legacy">3.1.2.2.1. Structure</a></h6>
<h6 id="bkdn_rsa_priv_legc_crypt_struct"><a class="link" href="#bkdn_rsa_priv_legc_crypt_struct">3.1.2.2.1. Structure</a></h6>
<div class="paragraph">
<p>Legacy private keys are encoded in standard RSA PEM format (<a href="https://datatracker.ietf.org/doc/html/rfc7468" target="_blank" rel="noopener">RFC 7468</a> § <a href="https://datatracker.ietf.org/doc/html/rfc7468#section-11" target="_blank" rel="noopener">11</a>, <a href="https://datatracker.ietf.org/doc/html/rfc3447#appendix-A" target="_blank" rel="noopener">APPENDIX-A</a>).</p>
</div>
@@ -1157,7 +1059,7 @@ The <code>DEK-Info</code> field is defined in <a href="https://datatracker.ietf.
</div>
</div>
<div class="sect5">
<h6 id="bytes_rsa_crypt_legacy"><a class="link" href="#bytes_rsa_crypt_legacy">3.1.2.2.2. Example</a></h6>
<h6 id="bkdn_rsa_priv_legc_crypt_ex"><a class="link" href="#bkdn_rsa_priv_legc_crypt_ex">3.1.2.2.2. Example</a></h6>
<div class="paragraph">
<p>The following example, being encrypted, is protected with a passphrase. The passphrase used in this example key is <strong><code>testpassword</code></strong>.</p>
</div>
@@ -1278,12 +1180,12 @@ ftSfkGNUzTzPFbF5iEukTvKm42a7F/I/ExMVgpN/eQxJ7+m5TOgja0KC1h5fCN4L
</div>
</div>
<div class="paragraph">
<p>See the <a href="#bytes_rsa_plain_legacy">plaintext example</a> for the decrypted (non-password-protected) version of this key.</p>
<p>See the <a href="#bkdn_rsa_priv_legc_plain_ex">plaintext example</a> for the decrypted (non-password-protected) version of this key.</p>
</div>
</div>
</div>
<div class="sect4">
<h5 id="v1_plain"><a class="link" href="#v1_plain">3.1.2.3. v1 (Plain)</a></h5>
<h5 id="bkdn_rsa_priv_v1_plain"><a class="link" href="#bkdn_rsa_priv_v1_plain">3.1.2.3. v1 (Plain)</a></h5>
<div class="admonitionblock tip">
<table>
<tr>
@@ -1299,7 +1201,7 @@ ftSfkGNUzTzPFbF5iEukTvKm42a7F/I/ExMVgpN/eQxJ7+m5TOgja0KC1h5fCN4L
</table>
</div>
<div class="sect5">
<h6 id="struct_rsa_plain"><a class="link" href="#struct_rsa_plain">3.1.2.3.1. Structure</a></h6>
<h6 id="bkdn_rsa_priv_v1_plain_struct"><a class="link" href="#bkdn_rsa_priv_v1_plain_struct">3.1.2.3.1. Structure</a></h6>
<div class="listingblock">
<div class="content">
<pre class="rouge highlight"><code data-lang="text"><table class="linenotable"><tbody><tr><td class="linenos gl"><pre class="lineno"> 1
@@ -1362,7 +1264,7 @@ ftSfkGNUzTzPFbF5iEukTvKm42a7F/I/ExMVgpN/eQxJ7+m5TOgja0KC1h5fCN4L
4.0.1.5 uint32 allocator for 4.0.1.5.0 (4 bytes)
4.0.1.5.0 private exponent ('d')
4.0.1.6 uint32 allocator for 4.0.1.6.0 (4 bytes)
4.0.1.6.0 CRT helper value ('q^(-1) % p')
4.0.1.6.0 CRT
4.0.1.7 uint32 allocator for 4.0.1.7.0 (4 bytes)
4.0.1.7.0 prime #1 ('p')
4.0.1.8 uint32 allocator for 4.0.1.8.0 (4 bytes)
@@ -1381,15 +1283,22 @@ ftSfkGNUzTzPFbF5iEukTvKm42a7F/I/ExMVgpN/eQxJ7+m5TOgja0KC1h5fCN4L
</td>
<td class="content">
<div class="paragraph">
<p><strong>Chunk 3.0.0 to 3.0.1:</strong> These blocks are not present in unencrypted keys (see the <a href="#struct_rsa_crypt">encrypted key structure</a> for what these look like). <strong>3.0</strong> reflects this, as it&#8217;s always going to be <code>00000000</code> (0).</p>
<p><strong>Chunk 3.0.0 to 3.0.1:</strong> These blocks are not present in unencrypted keys (see the <a href="#bkdn_rsa_priv_v1_crypt_struct">encrypted key structure</a> for what these look like). <strong>3.0</strong> reflects this, as it&#8217;s always going to be <code>00000000</code> (0).</p>
</div>
<div class="paragraph">
<p><strong>Chunk 4.0:</strong> This is technically currently unused; upstream hardcodes to 1 (left zero-padded 0x01).</p>
</div>
<div class="paragraph">
<p><strong>Chunk 4.0.0.1.0, 4.0.1.4.0:</strong> This is almost always <code>65537</code> for a couple reasons. It&#8217;s the <a href="https://en.wikipedia.org/wiki/Fermat_number" target="_blank" rel="noopener">"Fermat Prime"</a> <em>F<sub>4</sub></em>.</p>
</div>
<div class="paragraph">
<p><strong>Chunk 4.0.0.1.0, 4.0.0.2.0, 4.0.1.3.0, 4.0.1.4.0:</strong> Note that the ordering of <code>e</code>/<code>n</code> in <strong>4.0.0</strong> is changed to <code>n</code>/<code>e</code> in <strong>4.0.1</strong>.</p>
</div>
<div class="paragraph">
<p><strong>Chunk 4.0.1.6.0:</strong> The CRT coefficient, or <a href="https://en.wikipedia.org/wiki/Chinese_remainder_theorem" target="_blank" rel="noopener">Chinese Remainder Theorem</a>, is a helper used during decryption and signing. It&#8217;s commonly referred to as <code>qnlv</code> or <em>inverse coefficient</em>.
The value for this chunk is the result of <code>q^(-1) % p</code>.</p>
</div>
<div class="paragraph">
<p><strong>Chunk 4.0.1.10:</strong> The padding used aligns the private key (<strong>4.0.1.0</strong> to <strong>4.0.1.9.0</strong>) to the cipher blocksize. For plaintext keys, a blocksize of 8 is used.</p>
</div>
</td>
@@ -1398,10 +1307,7 @@ ftSfkGNUzTzPFbF5iEukTvKm42a7F/I/ExMVgpN/eQxJ7+m5TOgja0KC1h5fCN4L
</div>
</div>
<div class="sect5">
<h6 id="bytes_rsa_plain"><a class="link" href="#bytes_rsa_plain">3.1.2.3.2. Example</a></h6>
<div class="paragraph">
<p>The following example, being encrypted, is protected with a passphrase. The passphrase used in this example key is <strong><code>test</code></strong>.</p>
</div>
<h6 id="bkdn_rsa_priv_v1_plain_ex"><a class="link" href="#bkdn_rsa_priv_v1_plain_ex">3.1.2.3.2. Example</a></h6>
<div class="listingblock">
<div class="title"><code>id_rsa</code> Format</div>
<div class="content">
@@ -1466,7 +1372,7 @@ CnIIHn+l1HLBQosH6uXRW2TqHip1CFEv6atlX4ajE0htPMod2OkKzFyfuk1udnUH+6ufOn
oAiRAxnBFtR72SSzmUJUO4ig9hJ5UrLY4SkPMCn1Qq6+nAyONs8yloZc1mQ8iSTVZuv0lx
gJOZoawJb+Htw7X4cb9e8LTUTg6idiDSBRQuC/z2d7TbAlUyEho/B0WqTQWGMxczJXhVpc
7L46xEA9BP8MwMWLfASQS0AhJcK8KmOiDrswnMbz5l2zAaBYuNrOB+cbOPPzWVQz9psZjw
cAAAdQU4NHElODRxIAAAAHc3NoLXJzYQAAAgEAt87ARgHOKhLwySTLmjDrmQBmgSyxQ2kZ
cAAAdQobGx2KGxsdgAAAAHc3NoLXJzYQAAAgEAt87ARgHOKhLwySTLmjDrmQBmgSyxQ2kZ
PzCyuf3Ur8swDJGPKnfWRBDzYXrnyMoxjCV9PE304sQQi7vpOoaJS6FLNXXy9yFQvDgdy/
t0LHoZaGb9MYSs6WdhrdoPwpkvbIZtdWmRn8ItnEvw3kBajHbVGaoqUyncaCV3ciml0LdT
p4JaiblSdfnAJeIVNDxsiM1mkKIh+K6e9nXuRk3H0RjaQQUH6l1rZIndYK/YpmRkkts+J5
@@ -1614,10 +1520,10 @@ hau1VzZBnp8AAAAYVGhpcyBpcyBhIGNvbW1lbnQgc3RyaW5nAQID
103
104
</pre></td><td class="code"><pre>0.0 6f70656e7373682d6b65792d763100 ("openssh-key-v1" + 0x00)
1.0 0000000a (10)
1.0.0 6165733235362d637472 ("none")
2.0 00000006 (6)
2.0.0 626372797074 ("none")
1.0 00000004 (4)
1.0.0 6e6f6e65 ("none")
2.0 00000004 (4)
2.0.0 6e6f6e65 ("none")
3.0 00000000 (0)
4.0 00000001 (1)
4.0.0 00000217 (535)
@@ -1644,8 +1550,8 @@ hau1VzZBnp8AAAAYVGhpcyBpcyBhIGNvbW1lbnQgc3RyaW5nAQID
bc2a63a20ebb309cc6f3e65db301a058b8dace07e71b38f3f3595433f69b198f
07 (bytes)
4.0.1 00000750 (1872)
4.0.1.0 53834712 (1401112338)
4.0.1.1 53834712 (1401112338)
4.0.1.0 a1b1b1d8 (2712777176)
4.0.1.1 a1b1b1d8 (2712777176)
4.0.1.2 00000007 (7)
4.0.1.2.0 7373682d727361 ("ssh-rsa")
4.0.1.3 00000201 (513)
@@ -1695,7 +1601,7 @@ hau1VzZBnp8AAAAYVGhpcyBpcyBhIGNvbW1lbnQgc3RyaW5nAQID
2a52312abee4c6155acfee9384a16348c715346ebe693895fe6d2348d4dedb0a
137c487185ff949c209115b9c8a106329991f049e8430c7ba60dd5408d72ac98 (bytes)
4.0.1.7 00000101 (257)
4.0.1.7.0 00e50b65ba6ae4cb29ae66129c3e41ffeba36cd6ecbaa7045ff90cea71d09bc0
00e50b65ba6ae4cb29ae66129c3e41ffeba36cd6ecbaa7045ff90cea71d09bc0
56b0b9134dc5754c49da1fe8ab169cd149eedaeccf4913d915f4f241c5fd86c7
7511e0c261c344600a84cce78e8cf493e492844cb82c42ab6d1246a53e5cf50a
d4759c2a5c09d53b1c5c3b449328eea01434d6e537b3a513928dfaddf0a72728
@@ -1723,7 +1629,7 @@ hau1VzZBnp8AAAAYVGhpcyBpcyBhIGNvbW1lbnQgc3RyaW5nAQID
</div>
</div>
<div class="sect4">
<h5 id="v1_encrypted"><a class="link" href="#v1_encrypted">3.1.2.4. v1 (Encrypted)</a></h5>
<h5 id="bkdn_rsa_priv_v1_crypt"><a class="link" href="#bkdn_rsa_priv_v1_crypt">3.1.2.4. v1 (Encrypted)</a></h5>
<div class="admonitionblock tip">
<table>
<tr>
@@ -1816,7 +1722,7 @@ Note that <strong>1.0.0</strong> has nothing to do with SSH connections themselv
</table>
</div>
<div class="sect5">
<h6 id="struct_rsa_crypt"><a class="link" href="#struct_rsa_crypt">3.1.2.4.1. Structure</a></h6>
<h6 id="bkdn_rsa_priv_v1_crypt_struct"><a class="link" href="#bkdn_rsa_priv_v1_crypt_struct">3.1.2.4.1. Structure</a></h6>
<div class="listingblock">
<div class="content">
<pre class="rouge highlight"><code data-lang="text"><table class="linenotable"><tbody><tr><td class="linenos gl"><pre class="lineno"> 1
@@ -1873,7 +1779,7 @@ Note that <strong>1.0.0</strong> has nothing to do with SSH connections themselv
<p><strong>Chunk 4.0:</strong> This is technically currently unused; upstream hardcodes to 1 (left zero-padded 0x01).</p>
</div>
<div class="paragraph">
<p><strong>Chunk 4.0.1.0:</strong> When decrypted, this is equivalent to the <a href="#struct_rsa_plain">plaintext</a> <strong>4.0.1.0</strong> to <strong>4.0.1.10</strong>. It uses a padded size appropriate to the encryption cipher used.</p>
<p><strong>Chunk 4.0.1.0:</strong> When decrypted, this is equivalent to the <a href="#bkdn_rsa_priv_v1_plain_struct">plaintext</a> <strong>4.0.1.0</strong> to <strong>4.0.1.10</strong>. It uses a padded size appropriate to the encryption cipher used.</p>
</div>
</td>
</tr>
@@ -1881,7 +1787,7 @@ Note that <strong>1.0.0</strong> has nothing to do with SSH connections themselv
</div>
</div>
<div class="sect5">
<h6 id="bytes_rsa_crypt"><a class="link" href="#bytes_rsa_crypt">3.1.2.4.2. Example</a></h6>
<h6 id="bkdn_rsa_priv_v1_crypt_ex"><a class="link" href="#bkdn_rsa_priv_v1_crypt_ex">3.1.2.4.2. Example</a></h6>
<div class="paragraph">
<p>The following example, being encrypted, is protected with a passphrase. The passphrase used in this example key is <strong><code>test</code></strong>.</p>
</div>
@@ -2191,7 +2097,7 @@ ZnrXZl+8QIW1MSvaaQFmJFqTs=
</td>
<td class="content">
<div class="paragraph">
<p>The decrypted <strong>4.0.1.0</strong> should match the <a href="#struct_rsa_plain">plaintext key&#8217;s structure</a> for <strong>4.0.1.0</strong> through <strong>4.0.1.10</strong>. The padding length WILL change, however, between the two unless using a cipher with an 8-byte block size.</p>
<p>The decrypted <strong>4.0.1.0</strong> should match the <a href="#bkdn_rsa_priv_v1_plain_ex">plaintext key&#8217;s structure</a> for <strong>4.0.1.0</strong> through <strong>4.0.1.10</strong>. The padding length WILL change, however, between the two unless using a cipher with an 8-byte block size.</p>
</div>
</td>
</tr>
@@ -2353,14 +2259,14 @@ ZnrXZl+8QIW1MSvaaQFmJFqTs=
</div>
</div>
<div class="paragraph">
<p>See the <a href="#struct_rsa_plain">plaintext structure</a> for details.</p>
<p>See the <a href="#bkdn_rsa_priv_v1_plain_struct">plaintext structure</a> for details.</p>
</div>
</div>
</div>
</div>
</div>
<div class="sect2">
<h3 id="ed25519"><a class="link" href="#ed25519">3.2. ED25519</a></h3>
<h3 id="bkdn_ed25519"><a class="link" href="#bkdn_ed25519">3.2. ED25519</a></h3>
<div class="paragraph">
<p>ED25519<sup class="footnote">[<a id="_footnoteref_4" class="footnote" href="#_footnotedef_4" title="View footnote.">4</a>]</sup> is a relatively somewhat new OpenSSH key algorithm. It has numerous benefits over e.g. RSA, including:</p>
</div>
@@ -2395,9 +2301,9 @@ ZnrXZl+8QIW1MSvaaQFmJFqTs=
<p>I recommend it over all other key types for new SSH keys as long as it&#8217;s supported by clients/servers.</p>
</div>
<div class="sect3">
<h4 id="public_2"><a class="link" href="#public_2">3.2.1. Public</a></h4>
<h4 id="bkdn_ed25519_pub"><a class="link" href="#bkdn_ed25519_pub">3.2.1. Public</a></h4>
<div class="sect4">
<h5 id="structure_2"><a class="link" href="#structure_2">3.2.1.1. Structure</a></h5>
<h5 id="bkdn_ed25519_pub_struct"><a class="link" href="#bkdn_ed25519_pub_struct">3.2.1.1. Structure</a></h5>
<div class="paragraph">
<p>Public keys are stored in the following structure:</p>
</div>
@@ -2417,7 +2323,7 @@ ZnrXZl+8QIW1MSvaaQFmJFqTs=
</div>
</div>
<div class="sect4">
<h5 id="example_2"><a class="link" href="#example_2">3.2.1.2. Example</a></h5>
<h5 id="bkdn_ed25519_pub_ex"><a class="link" href="#bkdn_ed25519_pub_ex">3.2.1.2. Example</a></h5>
<div class="listingblock">
<div class="title"><code>id_ed25519.pub</code> Format</div>
<div class="content">
@@ -2443,9 +2349,9 @@ ZnrXZl+8QIW1MSvaaQFmJFqTs=
</div>
</div>
<div class="sect3">
<h4 id="private_2"><a class="link" href="#private_2">3.2.2. Private</a></h4>
<h4 id="bkdn_ed25519_priv"><a class="link" href="#bkdn_ed25519_priv">3.2.2. Private</a></h4>
<div class="sect4">
<h5 id="legacy_2"><a class="link" href="#legacy_2">3.2.2.1. Legacy</a></h5>
<h5 id="bkdn_ed25519_priv_legc"><a class="link" href="#bkdn_ed25519_priv_legc">3.2.2.1. Legacy</a></h5>
<div class="admonitionblock note">
<table>
<tr>
@@ -2462,7 +2368,7 @@ ZnrXZl+8QIW1MSvaaQFmJFqTs=
</div>
</div>
<div class="sect4">
<h5 id="v1_plain_2"><a class="link" href="#v1_plain_2">3.2.2.2. v1 (Plain)</a></h5>
<h5 id="bkdn_ed25519_priv_v1_plain"><a class="link" href="#bkdn_ed25519_priv_v1_plain">3.2.2.2. v1 (Plain)</a></h5>
<div class="admonitionblock tip">
<table>
<tr>
@@ -2478,7 +2384,7 @@ ZnrXZl+8QIW1MSvaaQFmJFqTs=
</table>
</div>
<div class="sect5">
<h6 id="struct_ed25519_plain"><a class="link" href="#struct_ed25519_plain">3.2.2.2.1. Structure</a></h6>
<h6 id="bkdn_ed25519_priv_v1_plain_struct"><a class="link" href="#bkdn_ed25519_priv_v1_plain_struct">3.2.2.2.1. Structure</a></h6>
<div class="listingblock">
<div class="content">
<pre class="rouge highlight"><code data-lang="text"><table class="linenotable"><tbody><tr><td class="linenos gl"><pre class="lineno"> 1
@@ -2540,7 +2446,7 @@ ZnrXZl+8QIW1MSvaaQFmJFqTs=
</td>
<td class="content">
<div class="paragraph">
<p><strong>Chunk 3.0.0 to 3.0.1:</strong> These blocks are not present in unencrypted keys (see the <a href="#struct_ed25519_crypt">encrypted key structure</a> for what these look like). <strong>3.0</strong> reflects this, as it&#8217;s always going to be <code>00000000</code> (0).</p>
<p><strong>Chunk 3.0.0 to 3.0.1:</strong> These blocks are not present in unencrypted keys (see the <a href="#bkdn_ed25519_priv_v1_crypt_struct">encrypted key structure</a> for what these look like). <strong>3.0</strong> reflects this, as it&#8217;s always going to be <code>00000000</code> (0).</p>
</div>
<div class="paragraph">
<p><strong>Chunk 4.0:</strong> This is technically currently unused; upstream hardcodes to 1 (left zero-padded <code>0x01</code>).</p>
@@ -2557,7 +2463,7 @@ ZnrXZl+8QIW1MSvaaQFmJFqTs=
</div>
</div>
<div class="sect5">
<h6 id="bytes_ed25519_plain"><a class="link" href="#bytes_ed25519_plain">3.2.2.2.2. Example</a></h6>
<h6 id="bkdn_ed25519_priv_v1_plain_ex"><a class="link" href="#bkdn_ed25519_priv_v1_plain_ex">3.2.2.2.2. Example</a></h6>
<div class="listingblock">
<div class="title"><code>id_ed25519</code> Format</div>
<div class="content">
@@ -2645,7 +2551,7 @@ g7umsWLE6XzRH3PDnZewAAAAElRoaXMgaXMgYSB0ZXN0IGtleQECAw==
</div>
</div>
<div class="sect4">
<h5 id="v1_encrypted_2"><a class="link" href="#v1_encrypted_2">3.2.2.3. v1 (Encrypted)</a></h5>
<h5 id="bkdn_ed25519_priv_v1_crypt"><a class="link" href="#bkdn_ed25519_priv_v1_crypt">3.2.2.3. v1 (Encrypted)</a></h5>
<div class="admonitionblock tip">
<table>
<tr>
@@ -2738,7 +2644,7 @@ Note that <strong>1.0.0</strong> has nothing to do with SSH connections themselv
</table>
</div>
<div class="sect5">
<h6 id="struct_ed25519_crypt"><a class="link" href="#struct_ed25519_crypt">3.2.2.3.1. Structure</a></h6>
<h6 id="bkdn_ed25519_priv_v1_crypt_struct"><a class="link" href="#bkdn_ed25519_priv_v1_crypt_struct">3.2.2.3.1. Structure</a></h6>
<div class="listingblock">
<div class="content">
<pre class="rouge highlight"><code data-lang="text"><table class="linenotable"><tbody><tr><td class="linenos gl"><pre class="lineno"> 1
@@ -2789,7 +2695,7 @@ Note that <strong>1.0.0</strong> has nothing to do with SSH connections themselv
<p><strong>Chunk 4.0:</strong> This is technically currently unused; upstream hardcodes to 1 (left zero-padded <code>0x01</code>).</p>
</div>
<div class="paragraph">
<p><strong>Chunk 4.0.1.0:</strong> When decrypted, this is equivalent to the <a href="#struct_ed25519_plain">plaintext</a> <strong>4.0.1.0</strong> to <strong>4.0.1.6</strong>. It uses a padded size appropriate to the encryption cipher used.</p>
<p><strong>Chunk 4.0.1.0:</strong> When decrypted, this is equivalent to the <a href="#bkdn_ed25519_priv_v1_plain_struct">plaintext</a> <strong>4.0.1.0</strong> to <strong>4.0.1.6</strong>. It uses a padded size appropriate to the encryption cipher used.</p>
</div>
</td>
</tr>
@@ -2797,7 +2703,7 @@ Note that <strong>1.0.0</strong> has nothing to do with SSH connections themselv
</div>
</div>
<div class="sect5">
<h6 id="bytes_ed25519_crypt"><a class="link" href="#bytes_ed25519_crypt">3.2.2.3.2. Example</a></h6>
<h6 id="bkdn_ed25519_priv_v1_crypt_ex"><a class="link" href="#bkdn_ed25519_priv_v1_crypt_ex">3.2.2.3.2. Example</a></h6>
<div class="paragraph">
<p>The following example, being encrypted, is protected with a passphrase. The passphrase used in this example key is <strong><code>test</code></strong>.</p>
</div>
@@ -2891,7 +2797,7 @@ dCXGDaRlL924VVCYUytRvu7ilZ+dtc9aCQUFJyDF3iXyxN2H68x7teo9e8vqzGtzLkw5KV
</td>
<td class="content">
<div class="paragraph">
<p>The decrypted <strong>4.0.1.0</strong> should match the <a href="#struct_ed25519_plain">plaintext key&#8217;s structure</a> for <strong>4.0.1</strong> through <strong>4.0.1.6</strong>. The padding length WILL change, however, between the two unless using a cipher with an 8-byte block size.</p>
<p>The decrypted <strong>4.0.1.0</strong> should match the <a href="#bkdn_ed25519_priv_v1_plain_struct">plaintext key&#8217;s structure</a> for <strong>4.0.1</strong> through <strong>4.0.1.6</strong>. The padding length WILL change, however, between the two unless using a cipher with an 8-byte block size.</p>
</div>
</td>
</tr>
@@ -2937,7 +2843,7 @@ dCXGDaRlL924VVCYUytRvu7ilZ+dtc9aCQUFJyDF3iXyxN2H68x7teo9e8vqzGtzLkw5KV
</div>
</div>
<div class="paragraph">
<p>See the <a href="#struct_ed25519_plain">plaintext structure</a> for details.</p>
<p>See the <a href="#bkdn_ed25519_priv_v1_plain_struct">plaintext structure</a> for details.</p>
</div>
</div>
</div>
@@ -2945,6 +2851,19 @@ dCXGDaRlL924VVCYUytRvu7ilZ+dtc9aCQUFJyDF3iXyxN2H68x7teo9e8vqzGtzLkw5KV
</div>
</div>
</div>
<div class="sect1">
<h2 id="moar"><a class="link" href="#moar">4. Further Information</a></h2>
<div class="sectionbody">
<a rel="license" href="http://creativecommons.org/licenses/by-sa/4.0/">
<img alt="Creative Commons License" style="border-width:0" src="https://i.creativecommons.org/l/by-sa/4.0/88x31.png" />
</a><br />
<span xmlns:dct="http://purl.org/dc/terms/" href="http://purl.org/dc/dcmitype/Text" property="dct:title" rel="dct:type">OpenSSH Key Structure Guide</span> by <a xmlns:cc="http://creativecommons.org/ns#" href="https://r00t2.io/" property="cc:attributionName" rel="cc:attributionURL">Brent Saner</a> is licensed under a <a rel="license" href="http://creativecommons.org/licenses/by-sa/4.0/">Creative Commons Attribution-ShareAlike 4.0 International License</a>.<br />
<br />
<div class="paragraph">
<p>You are free to use, distribute, modify, redistribute, use for commercial purposes, etc. with very few restrictions; please see <a href="http://creativecommons.org/licenses/by-sa/4.0/" target="_blank" rel="noopener">the license summary</a> and <a href="https://creativecommons.org/licenses/by-sa/4.0/legalcode" target="_blank" rel="noopener">full license</a> for further details.</p>
</div>
</div>
</div>
</div>
<div id="footnotes">
<hr>
@@ -2963,7 +2882,7 @@ dCXGDaRlL924VVCYUytRvu7ilZ+dtc9aCQUFJyDF3iXyxN2H68x7teo9e8vqzGtzLkw5KV
</div>
<div id="footer">
<div id="footer-text">
Last updated 2022-03-07 03:36:15 -0500
Last updated 2025-09-01 12:09:44 -0400
</div>
</div>
</body>

View File

@@ -1,3 +1,10 @@
////
This work is licensed under the Creative Commons Attribution-ShareAlike 4.0 International License.
To view a copy of this license, visit
http://creativecommons.org/licenses/by-sa/4.0/.
////
[id="bkdn_ed25519"]
=== ED25519
ED25519footnote:[https://datatracker.ietf.org/doc/html/rfc8709] is a relatively somewhat new OpenSSH key algorithm. It has numerous benefits over e.g. RSA, including:
@@ -11,4 +18,5 @@ ED25519footnote:[https://datatracker.ietf.org/doc/html/rfc8709] is a relatively
I recommend it over all other key types for new SSH keys as long as it's supported by clients/servers.
include::public.adoc[]
include::private/main.adoc[]

View File

@@ -1,3 +1,10 @@
////
This work is licensed under the Creative Commons Attribution-ShareAlike 4.0 International License.
To view a copy of this license, visit
http://creativecommons.org/licenses/by-sa/4.0/.
////
[id="bkdn_ed25519_priv_legc"]
===== Legacy
[NOTE]

View File

@@ -1,3 +1,10 @@
////
This work is licensed under the Creative Commons Attribution-ShareAlike 4.0 International License.
To view a copy of this license, visit
http://creativecommons.org/licenses/by-sa/4.0/.
////
[id="bkdn_ed25519_priv"]
==== Private
include::legacy/main.adoc[]

View File

@@ -1,3 +1,10 @@
////
This work is licensed under the Creative Commons Attribution-ShareAlike 4.0 International License.
To view a copy of this license, visit
http://creativecommons.org/licenses/by-sa/4.0/.
////
[id="bkdn_ed25519_priv_v1_crypt"]
===== v1 (Encrypted)
[TIP]
@@ -34,7 +41,7 @@ This is likely going to be:
The author recommends using `aes256-ctr`. It is currently the upstream default.
====
[id=struct_ed25519_crypt]
[id="bkdn_ed25519_priv_v1_crypt_struct"]
====== Structure
[source,text,linenums]
@@ -62,10 +69,10 @@ The author recommends using `aes256-ctr`. It is currently the upstream default.
====
*Chunk 4.0:* This is technically currently unused; upstream hardcodes to 1 (left zero-padded `0x01`).
*Chunk 4.0.1.0:* When decrypted, this is equivalent to the <<struct_ed25519_plain, plaintext>> *4.0.1.0* to *4.0.1.6*. It uses a padded size appropriate to the encryption cipher used.
*Chunk 4.0.1.0:* When decrypted, this is equivalent to the <<bkdn_ed25519_priv_v1_plain_struct, plaintext>> *4.0.1.0* to *4.0.1.6*. It uses a padded size appropriate to the encryption cipher used.
====
[id=bytes_ed25519_crypt]
[id="bkdn_ed25519_priv_v1_crypt_ex"]
====== Example
The following example, being encrypted, is protected with a passphrase. The passphrase used in this example key is *`test`*.
@@ -117,7 +124,7 @@ dCXGDaRlL924VVCYUytRvu7ilZ+dtc9aCQUFJyDF3iXyxN2H68x7teo9e8vqzGtzLkw5KV
[NOTE]
====
The decrypted *4.0.1.0* should match the <<struct_ed25519_plain, plaintext key's structure>> for *4.0.1* through *4.0.1.6*. The padding length WILL change, however, between the two unless using a cipher with an 8-byte block size.
The decrypted *4.0.1.0* should match the <<bkdn_ed25519_priv_v1_plain_struct, plaintext key's structure>> for *4.0.1* through *4.0.1.6*. The padding length WILL change, however, between the two unless using a cipher with an 8-byte block size.
====
When *4.0.1.0* is decrypted, it yields:
@@ -142,4 +149,4 @@ When *4.0.1.0* is decrypted, it yields:
4.0.1.6 0102030405060708090a0b ([1 2 3 4 5 6 7 8 9 10 11], 11 bytes)
----
See the <<struct_ed25519_plain, plaintext structure>> for details.
See the <<bkdn_ed25519_priv_v1_plain_struct, plaintext structure>> for details.

View File

@@ -1,3 +1,9 @@
////
This work is licensed under the Creative Commons Attribution-ShareAlike 4.0 International License.
To view a copy of this license, visit
http://creativecommons.org/licenses/by-sa/4.0/.
////
include::plain.adoc[]
include::encrypted.adoc[]

View File

@@ -1,3 +1,10 @@
////
This work is licensed under the Creative Commons Attribution-ShareAlike 4.0 International License.
To view a copy of this license, visit
http://creativecommons.org/licenses/by-sa/4.0/.
////
[id="bkdn_ed25519_priv_v1_plain"]
===== v1 (Plain)
[TIP]
@@ -5,7 +12,7 @@
Since plaintext/unencrypted keys do not have a cipher or KDF (as there's no encryption key or algorithm used), they use the string "none" to identify these (and entirely leave out the KDF options).
====
[id=struct_ed25519_plain]
[id="bkdn_ed25519_priv_v1_plain_struct"]
====== Structure
[source,text,linenums]
@@ -38,7 +45,7 @@ Since plaintext/unencrypted keys do not have a cipher or KDF (as there's no encr
[NOTE]
====
*Chunk 3.0.0 to 3.0.1:* These blocks are not present in unencrypted keys (see the <<struct_ed25519_crypt, encrypted key structure>> for what these look like). *3.0* reflects this, as it's always going to be `00000000` (0).
*Chunk 3.0.0 to 3.0.1:* These blocks are not present in unencrypted keys (see the <<bkdn_ed25519_priv_v1_crypt_struct, encrypted key structure>> for what these look like). *3.0* reflects this, as it's always going to be `00000000` (0).
*Chunk 4.0:* This is technically currently unused; upstream hardcodes to 1 (left zero-padded `0x01`).
@@ -47,7 +54,7 @@ Since plaintext/unencrypted keys do not have a cipher or KDF (as there's no encr
*Chunk 4.0.1.6:* The padding used aligns the private key (*4.0.1.0* to *4.0.1.5.0*) to the cipher blocksize. For plaintext keys, a blocksize of 8 is used.
====
[id=bytes_ed25519_plain]
[id="bkdn_ed25519_priv_v1_plain_ex"]
====== Example
.`id_ed25519` Format

View File

@@ -1,5 +1,13 @@
////
This work is licensed under the Creative Commons Attribution-ShareAlike 4.0 International License.
To view a copy of this license, visit
http://creativecommons.org/licenses/by-sa/4.0/.
////
[id="bkdn_ed25519_pub"]
==== Public
[id="bkdn_ed25519_pub_struct"]
===== Structure
Public keys are stored in the following structure:
@@ -13,6 +21,7 @@ Public keys are stored in the following structure:
1.0.0 Public key payload (bytes)
----
[id="bkdn_ed25519_pub_ex"]
===== Example
.`id_ed25519.pub` Format

View File

@@ -1,5 +1,11 @@
package main
/*
* This work is licensed under the Creative Commons Attribution-ShareAlike 4.0 International License.
* To view a copy of this license, visit
* http://creativecommons.org/licenses/by-sa/4.0/.
*/
import (
"crypto"
"crypto/aes"

View File

@@ -1,5 +1,11 @@
package main
/*
* This work is licensed under the Creative Commons Attribution-ShareAlike 4.0 International License.
* To view a copy of this license, visit
* http://creativecommons.org/licenses/by-sa/4.0/.
*/
import (
"crypto/aes"
"crypto/cipher"

View File

@@ -1,3 +1,10 @@
////
This work is licensed under the Creative Commons Attribution-ShareAlike 4.0 International License.
To view a copy of this license, visit
http://creativecommons.org/licenses/by-sa/4.0/.
////
[id="bkdn_rsa"]
=== RSA
RSAfootnote:[https://datatracker.ietf.org/doc/html/rfc8017] is a widely-supported PKI system. It is ubiquitous, but it is recommended to use newer systems (e.g. ED25519) for OpenSSH if all clients and destinations support it.

View File

@@ -1,6 +1,13 @@
////
This work is licensed under the Creative Commons Attribution-ShareAlike 4.0 International License.
To view a copy of this license, visit
http://creativecommons.org/licenses/by-sa/4.0/.
////
[id="bkdn_rsa_priv_legc_crypt"]
===== Legacy (Encrypted)
[id=struct_rsa_crypt_legacy]
[id="bkdn_rsa_priv_legc_crypt_struct"]
====== Structure
Legacy private keys are encoded in standard RSA PEM format (https://datatracker.ietf.org/doc/html/rfc7468[RFC 7468^] § https://datatracker.ietf.org/doc/html/rfc7468#section-11[11^], https://datatracker.ietf.org/doc/html/rfc3447#appendix-A[APPENDIX-A^]).
@@ -8,7 +15,7 @@ Legacy private keys are encoded in standard RSA PEM format (https://datatracker.
The `Proc-Type` field is defined in https://datatracker.ietf.org/doc/html/rfc1421.html#section-4.6.1.1[RFC 1421 § 4.6.1.1^]. +
The `DEK-Info` field is defined in https://datatracker.ietf.org/doc/html/rfc1421.html#section-4.6.1.3[RFC 1421 § 4.6.1.3^].
[id=bytes_rsa_crypt_legacy]
[id="bkdn_rsa_priv_legc_crypt_ex"]
====== Example
The following example, being encrypted, is protected with a passphrase. The passphrase used in this example key is *`testpassword`*.
@@ -73,4 +80,4 @@ ftSfkGNUzTzPFbF5iEukTvKm42a7F/I/ExMVgpN/eQxJ7+m5TOgja0KC1h5fCN4L
-----END RSA PRIVATE KEY-----
----
See the <<bytes_rsa_plain_legacy, plaintext example>> for the decrypted (non-password-protected) version of this key.
See the <<bkdn_rsa_priv_legc_plain_ex, plaintext example>> for the decrypted (non-password-protected) version of this key.

View File

@@ -1,3 +1,9 @@
////
This work is licensed under the Creative Commons Attribution-ShareAlike 4.0 International License.
To view a copy of this license, visit
http://creativecommons.org/licenses/by-sa/4.0/.
////
include::plain.adoc[]
include::encrypted.adoc[]

View File

@@ -1,11 +1,18 @@
////
This work is licensed under the Creative Commons Attribution-ShareAlike 4.0 International License.
To view a copy of this license, visit
http://creativecommons.org/licenses/by-sa/4.0/.
////
[id="bkdn_rsa_priv_legc_plain"]
===== Legacy (Plain)
[id=struct_rsa_plain_legacy]
[id="bkdn_rsa_priv_legc_plain_struct"]
====== Structure
Legacy private keys are encoded in standard RSA PEM format (https://datatracker.ietf.org/doc/html/rfc7468[RFC 7468^] § https://datatracker.ietf.org/doc/html/rfc7468#section-10[10^], https://datatracker.ietf.org/doc/html/rfc3447#appendix-A[APPENDIX-A^]).
[id=bytes_rsa_plain_legacy]
[id="bkdn_rsa_priv_legc_plain_ex"]
====== Example
[source,text,linenums]

View File

@@ -1,3 +1,10 @@
////
This work is licensed under the Creative Commons Attribution-ShareAlike 4.0 International License.
To view a copy of this license, visit
http://creativecommons.org/licenses/by-sa/4.0/.
////
[id="bkdn_rsa_priv"]
==== Private
include::legacy/main.adoc[]

View File

@@ -1,3 +1,10 @@
////
This work is licensed under the Creative Commons Attribution-ShareAlike 4.0 International License.
To view a copy of this license, visit
http://creativecommons.org/licenses/by-sa/4.0/.
////
[id="bkdn_rsa_priv_v1_crypt"]
===== v1 (Encrypted)
[TIP]
@@ -34,7 +41,7 @@ This is likely going to be:
The author recommends using `aes256-ctr`. It is currently the upstream default.
====
[id=struct_rsa_crypt]
[id="bkdn_rsa_priv_v1_crypt_struct"]
====== Structure
[source,text,linenums]
@@ -65,10 +72,10 @@ The author recommends using `aes256-ctr`. It is currently the upstream default.
====
*Chunk 4.0:* This is technically currently unused; upstream hardcodes to 1 (left zero-padded 0x01).
*Chunk 4.0.1.0:* When decrypted, this is equivalent to the <<struct_rsa_plain,plaintext>> *4.0.1.0* to *4.0.1.10*. It uses a padded size appropriate to the encryption cipher used.
*Chunk 4.0.1.0:* When decrypted, this is equivalent to the <<bkdn_rsa_priv_v1_plain_struct, plaintext>> *4.0.1.0* to *4.0.1.10*. It uses a padded size appropriate to the encryption cipher used.
====
[id=bytes_rsa_crypt]
[id="bkdn_rsa_priv_v1_crypt_ex"]
====== Example
The following example, being encrypted, is protected with a passphrase. The passphrase used in this example key is *`test`*.
@@ -228,7 +235,7 @@ ZnrXZl+8QIW1MSvaaQFmJFqTs=
[NOTE]
====
The decrypted *4.0.1.0* should match the <<struct_rsa_plain, plaintext key's structure>> for *4.0.1.0* through *4.0.1.10*. The padding length WILL change, however, between the two unless using a cipher with an 8-byte block size.
The decrypted *4.0.1.0* should match the <<bkdn_rsa_priv_v1_plain_ex, plaintext key's structure>> for *4.0.1.0* through *4.0.1.10*. The padding length WILL change, however, between the two unless using a cipher with an 8-byte block size.
====
When *4.0.1.0* is decrypted, it yields:
@@ -311,4 +318,4 @@ When *4.0.1.0* is decrypted, it yields:
4.0.1.10 010203 ([1 2 3], 3 bytes)
----
See the <<struct_rsa_plain, plaintext structure>> for details.
See the <<bkdn_rsa_priv_v1_plain_struct, plaintext structure>> for details.

View File

@@ -1,3 +1,9 @@
////
This work is licensed under the Creative Commons Attribution-ShareAlike 4.0 International License.
To view a copy of this license, visit
http://creativecommons.org/licenses/by-sa/4.0/.
////
include::plain.adoc[]
include::encrypted.adoc[]

View File

@@ -1,3 +1,10 @@
////
This work is licensed under the Creative Commons Attribution-ShareAlike 4.0 International License.
To view a copy of this license, visit
http://creativecommons.org/licenses/by-sa/4.0/.
////
[id="bkdn_rsa_priv_v1_plain"]
===== v1 (Plain)
[TIP]
@@ -5,7 +12,7 @@
Since plaintext/unencrypted keys do not have a cipher or KDF (as there's no encryption key or algorithm used), they use the string "none" to identify these (and entirely leave out the KDF options).
====
[id=struct_rsa_plain]
[id="bkdn_rsa_priv_v1_plain_struct"]
====== Structure
[source,text,linenums]
@@ -36,7 +43,7 @@ Since plaintext/unencrypted keys do not have a cipher or KDF (as there's no encr
4.0.1.5 uint32 allocator for 4.0.1.5.0 (4 bytes)
4.0.1.5.0 private exponent ('d')
4.0.1.6 uint32 allocator for 4.0.1.6.0 (4 bytes)
4.0.1.6.0 CRT helper value ('q^(-1) % p')
4.0.1.6.0 CRT
4.0.1.7 uint32 allocator for 4.0.1.7.0 (4 bytes)
4.0.1.7.0 prime #1 ('p')
4.0.1.8 uint32 allocator for 4.0.1.8.0 (4 bytes)
@@ -48,20 +55,23 @@ Since plaintext/unencrypted keys do not have a cipher or KDF (as there's no encr
[NOTE]
====
*Chunk 3.0.0 to 3.0.1:* These blocks are not present in unencrypted keys (see the <<struct_rsa_crypt, encrypted key structure>> for what these look like). *3.0* reflects this, as it's always going to be `00000000` (0).
*Chunk 3.0.0 to 3.0.1:* These blocks are not present in unencrypted keys (see the <<bkdn_rsa_priv_v1_crypt_struct, encrypted key structure>> for what these look like). *3.0* reflects this, as it's always going to be `00000000` (0).
*Chunk 4.0:* This is technically currently unused; upstream hardcodes to 1 (left zero-padded 0x01).
*Chunk 4.0.0.1.0, 4.0.1.4.0:* This is almost always `65537` for a couple reasons. It's the https://en.wikipedia.org/wiki/Fermat_number["Fermat Prime"^] __F~4~__.
*Chunk 4.0.0.1.0, 4.0.0.2.0, 4.0.1.3.0, 4.0.1.4.0:* Note that the ordering of `e`/`n` in *4.0.0* is changed to `n`/`e` in *4.0.1*.
*Chunk 4.0.1.6.0:* The CRT coefficient, or https://en.wikipedia.org/wiki/Chinese_remainder_theorem[Chinese Remainder Theorem^], is a helper used during decryption and signing. It's commonly referred to as `qnlv` or _inverse coefficient_.
The value for this chunk is the result of `q^(-1) % p`.
*Chunk 4.0.1.10:* The padding used aligns the private key (*4.0.1.0* to *4.0.1.9.0*) to the cipher blocksize. For plaintext keys, a blocksize of 8 is used.
====
[id=bytes_rsa_plain]
[id="bkdn_rsa_priv_v1_plain_ex"]
====== Example
The following example, being encrypted, is protected with a passphrase. The passphrase used in this example key is *`test`*.
.`id_rsa` Format
[source,text,linenums]
----
@@ -77,7 +87,7 @@ CnIIHn+l1HLBQosH6uXRW2TqHip1CFEv6atlX4ajE0htPMod2OkKzFyfuk1udnUH+6ufOn
oAiRAxnBFtR72SSzmUJUO4ig9hJ5UrLY4SkPMCn1Qq6+nAyONs8yloZc1mQ8iSTVZuv0lx
gJOZoawJb+Htw7X4cb9e8LTUTg6idiDSBRQuC/z2d7TbAlUyEho/B0WqTQWGMxczJXhVpc
7L46xEA9BP8MwMWLfASQS0AhJcK8KmOiDrswnMbz5l2zAaBYuNrOB+cbOPPzWVQz9psZjw
cAAAdQU4NHElODRxIAAAAHc3NoLXJzYQAAAgEAt87ARgHOKhLwySTLmjDrmQBmgSyxQ2kZ
cAAAdQobGx2KGxsdgAAAAHc3NoLXJzYQAAAgEAt87ARgHOKhLwySTLmjDrmQBmgSyxQ2kZ
PzCyuf3Ur8swDJGPKnfWRBDzYXrnyMoxjCV9PE304sQQi7vpOoaJS6FLNXXy9yFQvDgdy/
t0LHoZaGb9MYSs6WdhrdoPwpkvbIZtdWmRn8ItnEvw3kBajHbVGaoqUyncaCV3ciml0LdT
p4JaiblSdfnAJeIVNDxsiM1mkKIh+K6e9nXuRk3H0RjaQQUH6l1rZIndYK/YpmRkkts+J5
@@ -120,10 +130,10 @@ hau1VzZBnp8AAAAYVGhpcyBpcyBhIGNvbW1lbnQgc3RyaW5nAQID
[source,text,linenums]
----
0.0 6f70656e7373682d6b65792d763100 ("openssh-key-v1" + 0x00)
1.0 0000000a (10)
1.0.0 6165733235362d637472 ("none")
2.0 00000006 (6)
2.0.0 626372797074 ("none")
1.0 00000004 (4)
1.0.0 6e6f6e65 ("none")
2.0 00000004 (4)
2.0.0 6e6f6e65 ("none")
3.0 00000000 (0)
4.0 00000001 (1)
4.0.0 00000217 (535)
@@ -150,8 +160,8 @@ hau1VzZBnp8AAAAYVGhpcyBpcyBhIGNvbW1lbnQgc3RyaW5nAQID
bc2a63a20ebb309cc6f3e65db301a058b8dace07e71b38f3f3595433f69b198f
07 (bytes)
4.0.1 00000750 (1872)
4.0.1.0 53834712 (1401112338)
4.0.1.1 53834712 (1401112338)
4.0.1.0 a1b1b1d8 (2712777176)
4.0.1.1 a1b1b1d8 (2712777176)
4.0.1.2 00000007 (7)
4.0.1.2.0 7373682d727361 ("ssh-rsa")
4.0.1.3 00000201 (513)
@@ -201,7 +211,7 @@ hau1VzZBnp8AAAAYVGhpcyBpcyBhIGNvbW1lbnQgc3RyaW5nAQID
2a52312abee4c6155acfee9384a16348c715346ebe693895fe6d2348d4dedb0a
137c487185ff949c209115b9c8a106329991f049e8430c7ba60dd5408d72ac98 (bytes)
4.0.1.7 00000101 (257)
4.0.1.7.0 00e50b65ba6ae4cb29ae66129c3e41ffeba36cd6ecbaa7045ff90cea71d09bc0
00e50b65ba6ae4cb29ae66129c3e41ffeba36cd6ecbaa7045ff90cea71d09bc0
56b0b9134dc5754c49da1fe8ab169cd149eedaeccf4913d915f4f241c5fd86c7
7511e0c261c344600a84cce78e8cf493e492844cb82c42ab6d1246a53e5cf50a
d4759c2a5c09d53b1c5c3b449328eea01434d6e537b3a513928dfaddf0a72728

View File

@@ -1,5 +1,13 @@
////
This work is licensed under the Creative Commons Attribution-ShareAlike 4.0 International License.
To view a copy of this license, visit
http://creativecommons.org/licenses/by-sa/4.0/.
////
[id="bkdn_rsa_pub"]
==== Public
[id="bkdn_rsa_pub_struct"]
===== Structure
Public keys are stored in the following structure:
@@ -15,6 +23,7 @@ Public keys are stored in the following structure:
2.0 modulus ('n') (bytes)
----
[id="bkdn_rsa_pub_ex"]
===== Example
.`.pub` format

View File

@@ -1,3 +1,13 @@
######################################################################################################
# This work is licensed under the Creative Commons Attribution-ShareAlike 4.0 International License. #
# To view a copy of this license, visit #
# http://creativecommons.org/licenses/by-sa/4.0/. #
######################################################################################################
= NOTES =
This document is largely just a dump of "further reading" resources.
https://github.com/openssh/openssh-portable/blob/master/PROTOCOL.key
canonical: https://cvsweb.openbsd.org/src/usr.bin/ssh/PROTOCOL.key?annotate=HEAD
https://peterlyons.com/problog/2017/12/openssh-ed25519-private-key-file-format/

View File

@@ -0,0 +1,3 @@
TODO
I need to look further into the aes128-gcm@openssh.com variant to see if this is valid.

View File

@@ -0,0 +1,3 @@
TODO
I need to look further into the aes256-gcm@openssh.com variant to see if this is valid.

View File

@@ -1 +1,14 @@
TODO
I need to fork out the chacha20-poly1305 pkg from golang x-stdlib
(
https://pkg.go.dev/golang.org/x/crypto/chacha20poly1305
https://cs.opensource.google/go/x/crypto/+/master:chacha20poly1305/
https://github.com/golang/go/issues/36646
)
because they explicitly do NOT support the chacha20-poly1305 OpenSSH variant (chacha20-poly1305@openssh.com)
(https://github.com/golang/go/issues/36646#issue-552055939
"and there is exactly one widely used (or otherwise) composition:
ChaCha20Poly1305 as implemented by x/crypto/chacha20poly1305 (or by SSH in their weird variant)"
sidenote, this is the same guy that decided it would be a good idea to deprecate golang x-stdlib gpg).

View File

@@ -0,0 +1,6 @@
package poly1305
const (
Name string = "chacha20-poly1305@openssh.com"
BlockSize int = 8
)

View File

@@ -4,8 +4,7 @@ import (
`bytes`
`io`
`r00t2.io/sshkeys/cipher/aes`
`r00t2.io/sshkeys/cipher/aes/aes128`
`r00t2.io/cc20p1305ssh`
`r00t2.io/sshkeys/internal`
)
@@ -39,7 +38,7 @@ func (c *Cipher) NameBytes() (name []byte) {
// BlockSize returns the blocksize of this Cipher.
func (c *Cipher) BlockSize() (size int) {
size = aes.BlockSize
size = BlockSize
return
}
@@ -47,7 +46,7 @@ func (c *Cipher) BlockSize() (size int) {
// KdfKeySize returns the target key length from KDF to use with this Cipher.
func (c *Cipher) KdfKeySize() (size int) {
size = aes128.KeySize
size = cc20p1305ssh.KeySize
return
}

View File

@@ -0,0 +1,22 @@
package poly1305
/*
Cipher is a ChaCha20-Poly1305 (OpenSSH variant) cipher.Cipher.
In the OpenSSH variant (for *key* encryption), only the first
32 bytes is used from the 64-byte key as generated from ChaCha20.
It then proceeds per https://datatracker.ietf.org/doc/html/rfc8439#section-2.8
except:
* The nonce used is a constant of 16 zero bytes
* There is no additional authenticated data
* The Poly1305 authentication tag is generated via a message
that consists *only* of the ciphertext.
In other words, OpenSSH does *not* add padding or
encode message lengths to generate the Poly1305
authentication tag.
*/
type Cipher struct {
Key []byte
}

6
cipher/null/consts.go Normal file
View File

@@ -0,0 +1,6 @@
package null
const (
Name string = ""
BlockSize int = 8
)

View File

@@ -1,17 +1,26 @@
package null
import (
`bytes`
`io`
"bytes"
`r00t2.io/sshkeys/cipher/aes`
`r00t2.io/sshkeys/cipher/aes/aes128`
`r00t2.io/sshkeys/internal`
`r00t2.io/sshkeys/cipher`
"r00t2.io/sshkeys/internal"
)
/*
Setup populates a Cipher from a key.
This is basically a no-op as null.Cipher does not offer any encryption, so there's no setup necessary.
*/
func (c *Cipher) Setup(key []byte) (err error) {
// TODO
if c == nil {
*c = Cipher{}
}
if err = c.keyChk(); err != nil {
return
}
return
}
@@ -39,21 +48,26 @@ func (c *Cipher) NameBytes() (name []byte) {
// BlockSize returns the blocksize of this Cipher.
func (c *Cipher) BlockSize() (size int) {
size = aes.BlockSize
return
}
// KdfKeySize returns the target key length from KDF to use with this Cipher.
func (c *Cipher) KdfKeySize() (size int) {
size = aes128.KeySize
size = BlockSize
return
}
/*
Encrypt encrypts data (a string, []byte, byte, *bytes.Buffer, or *bytes.Reader) to the *bytes.Reader encrypted.
KdfKeySize returns the target key length from KDF to use with this Cipher.
Because this function is only here for interface compat, it always returns 0
(as a Null cipher uses no KDF).
*/
func (c *Cipher) KdfKeySize() (size int) {
size = 0
return
}
/*
Encrypt "encrypts" data (a string, []byte, byte, *bytes.Buffer, or *bytes.Reader) to the *bytes.Reader encrypted.
NOTE: Padding IS applied automatically.
@@ -61,31 +75,15 @@ func (c *Cipher) KdfKeySize() (size int) {
It is up to the caller to consume the buffer as desired beforehand or isolate to a specific sub-buffer beforehand to pass to Cipher.Encrypt.
NOTE: If data is a *bytes.Reader, ALL bytes WILL be consumed.
NOTE: No actual encryption takes place, only padding.
*/
func (c *Cipher) Encrypt(data interface{}) (encrypted *bytes.Reader, err error) {
var b []byte
var cryptDst []byte
var padded *bytes.Reader
if b, err = internal.SerializeData(data); err != nil {
if encrypted, err = c.Pad(data); err != nil {
return
}
if padded, err = c.Pad(b); err != nil {
return
}
b = make([]byte, padded.Len())
if b, err = io.ReadAll(padded); err != nil {
return
}
cryptDst = make([]byte, len(b))
// TODO
_ = cryptDst
return
}
@@ -125,17 +123,38 @@ func (c *Cipher) AllocateEncrypt(data interface{}) (encrypted *bytes.Reader, err
}
/*
Pad will pad data (a string, []byte, byte, or *bytes.Buffer) to the Cipher.BlockSize (if necessary).
The resulting padded buffer is returned.
Pad will pad data (a string, []byte, byte, *bytes.Buffer, *bytes.Reader) to the Cipher.BlockSize. The resulting padded *bytes.Reader is returned.
NOTE: If data is a *bytes.Buffer, no bytes will be consumed -- the bytes are taken in entirety without consuming them (Buffer.Bytes()).
It is up to the caller to consume the buffer as desired beforehand or isolate to a specific sub-buffer beforehand to pass to Cipher.Pad.
It is up to the caller to consume the buffer as desired beforehand or isolate to a specific sub-buffer beforehand to pass to Pad.
NOTE: If data is a *bytes.Reader, ALL bytes WILL be consumed.
*/
func (c *Cipher) Pad(data interface{}) (paddedBuf *bytes.Reader, err error) {
// TODO
var b []byte
var padNum int
var pad []byte
var buf *bytes.Buffer
if err = c.keyChk(); err != nil {
return
}
if b, err = internal.UnpackBytes(data); err != nil {
return
}
buf = bytes.NewBuffer(b)
for padIdx := 1; (buf.Len() % BlockSize) != 0; padIdx++ {
padNum = padIdx & cipher.PadMod
pad = []byte{byte(uint32(padNum))}
if _, err = buf.Write(pad); err != nil {
return
}
}
return
}
@@ -152,17 +171,13 @@ func (c *Cipher) Pad(data interface{}) (paddedBuf *bytes.Reader, err error) {
*/
func (c *Cipher) Decrypt(data interface{}) (decrypted *bytes.Reader, err error) {
var b []byte
var decryptDst []byte
var plain []byte
if b, err = internal.SerializeData(data); err != nil {
if plain, err = internal.SerializeData(data); err != nil {
return
}
decryptDst = make([]byte, len(b))
// TODO
_ = decryptDst
decrypted = bytes.NewReader(plain)
return
}
@@ -195,11 +210,21 @@ func (c *Cipher) AllocatedDecrypt(data interface{}) (decrypted *bytes.Reader, er
/*
IsPlain indicates if this Cipher is a plain/null encryption (cipher.null.Null).
It will always return false. It is included for interface compatability.
It will always return true. It is included for interface compatability.
*/
func (c *Cipher) IsPlain() (plain bool) {
plain = false
plain = true
return
}
// keyChk is more or less a no-op but provides some basic sanity checking.
func (c *Cipher) keyChk() (err error) {
if c == nil {
*c = Cipher{}
}
return
}

4
cipher/null/types.go Normal file
View File

@@ -0,0 +1,4 @@
package null
// Cipher is a null (plaintext) cipher.Cipher.
type Cipher struct{}