Cryptography formats
Or why OpenSSH can't read a key generated with OpenSSL without some magic incantation (and the other way around)
Published:
Updated:
If you are trying to understand the difference between the different cryptography-related formats (PKS#12, PKCS#8, PEM, X.509 certificate, DER, JWK, BEGIN ENCRYPTED PRIVATE KEY??? 🤯), you will hopefully find some useful information here (and a lot more your did not wanted to know about).
Table of content
Summary
The following table summarizes major many formats for cryptographic key material, signatures, parameters, etc.
| Enc. | Format | Label | Extension | Media subtype |
|---|---|---|---|---|
| ASN.1 | ASN.1 BER (Basic Encoding Rules) | *+ber | ||
| ASN.1 | ASN.1 DER (Distinguished Encoding Rules) | *+der | ||
| ASN.1 | ASN.1 PER (Packed Encoding Rules) | |||
| ASN.1 | ASN.1 UPER (Unaligned Packed Encoding Rules) | *+uper | ||
| ASN.1 | ASN.1 JER (JSON Encoding Rules) | *+jer | ||
| ASN.1 | ASN.1 XER (XML encoding rules) | |||
| ASN.1 | ASN.1 OER (Octet Encoding Rules) | |||
| ASN.1 | X.509 public key certificate (PKC) | CERTIFICATE (or X509 CERTIFICATE) | .cer (DER?), .crt (ASCII?) | pkix-cert |
| ASN.1 | X.509 Certificate Path (RFC6066, PkiPath) | .pkipath | pkix-pkipath | |
| ASN.1 | X.509 Certificate Path (PEM) | (sequence of CERTIFICATE PEM blocks) | (.pem) | pem-certificate-chain |
| ASN.1 | Trust anchor (RFC5914, TrustAnchorInfo) | |||
| ASN.1 | Trust anchor list (RFC5914, TrustAnchorList) | |||
| ASN.1 | OpenSSL Trusted Certificate | TRUSTED CERTIFICATE | ||
| ASN.1 | Public key (SubjectPublicKeyInfo) | PUBLIC KEY | ||
| ASN.1 | RSA Public key (PKCS#1, RSAPublicKey) | RSA PUBLIC KEY | ||
| ASN.1 | PKCS#8 PrivateKeyInfo | PRIVATE KEY | .p8 | pkcs8 |
| ASN.1 | PKCS#8 EncryptedPrivateKeyInfo | ENCRYPTED PRIVATE KEY | .p8e | pkcs8-encrypted |
| ASN.1 | PKCS#3 (DHparameter) | DH PARAMETERS | ||
| ASN.1 | X9.42 (DHX) parameters (DHxparams) | X9.42 DH PARAMETERS | ||
| ASN.1 | DSA parameters (DSS-Parms, RFC2459) | DSA PARAMETERS | ||
| ASN.1 | DSA private key (legacy SSLeay format, DSAPrivateKey) | DSA PRIVATE KEY | ||
| ASN.1 | RSA private key (PKCS#1, RSAPrivateKey) | RSA PRIVATE KEY | ||
| ASN.1 | EC parameters (RFC3279, EcpkParameters) | EC PARAMETERS | ||
| ASN.1 | EC private key, SEC1 (ECPrivateKey) | EC PRIVATE KEY | ||
| ASN.1 | TPM Bound private key (TPMKey) | TSS2 PRIVATE KEY | ||
| ASN.1 | PKCS#12 (PFX) | PKCS12 | .p12, .pfx | pkcs12 |
| ASN.1 | X.509 CertificateList (CertificateList) | X509 CRL | .crl | pkix-crl |
| ASN.1 | X.509 AttributeCertificate (AttributeCertificate) | ATTRIBUTE CERTIFICATE | .ac | pkix-attr-cert |
| ASN.1 | X.509 PKIMessage | - | .pki | pkixcmp |
| ASN.1 | PKCS #10 Certificate request (CSR) (CertificationRequest) | CERTIFICATE REQUEST (or NEW CERTIFICATE REQUEST) | .p10 | pkcs10 |
| ASN.1 | CSR response (??) | .p7r | x-pkcs7-certreqresp | |
| ASN.1 | PKCS#7 CMS (ContentInfo) | PKCS7 | .p7m | pkcs7-mime |
| ASN.1 | PKCS#7 CMS compressed data | .p7z | pkcs7-mime; smime-type=compressed-data | |
| ASN.1 | PKCS#7 CMS detached S/MIME signature | .p7s | pkcs7-signature | |
| ASN.1 | PKCS#7 CMS, CMC Full PKI Request | .p7m / .crq | pkcs7-mime; smime-type=CMC-request | |
| ASN.1 | PKCS#7 CMS, CMC Simple PKI Response | .p7c | pkcs7-mime; smime-type=certs-only | |
| ASN.1 | PKCS#7 CMS, CMC Full PKI Response | .p7m / .crp | pkcs7-mime; smime-type=CMC-response | |
| ASN.1 | CMS (RFC5652, ContentInfo) | CMS | .cmsc | cms |
| ASN.1 | OpenSSL SSL session | SSL SESSION PARAMETERS | ||
| ASN.1 | Attributes for CSR (CsrAttrs) | .csrattrs | csrattrs | |
| ASN.1 | OCSP Request (OCSPRequest) | .orq | ocsp-request | |
| ASN.1 | OCSP Response (OCSRespose) | .ors | ocsp-response | |
| ASN.1 | Time stamp query (TimeStampReq) | .tsq | timestamp-query | |
| ASN.1 | Time stamp response (TimeStampResp) | .tsr | timestamp-reply | |
| ASN.1 | Time stamp token (TST) (TimeStampToken::=ContentInfo) | |||
| TLS | ECHConfigList | ECHCONFIG | ||
| TLS | ObliviousDoHMessage | oblivious-dns-message | ||
| TLS | MLS Message | message/mls | ||
| QUIC | Oblivious HTTP Keys | ohttp-keys | ||
| QUIC | Oblivious HTTP Request | ohttp-req | ||
| QUIC | Oblivious HTTP Response | ohttp-res | ||
| SSH2 | Public Key in SSH2 Format | SSH2 PUBLIC KEY | (.pub) | |
| SSH2 | OpenSSH private key | OPENSSH PRIVATE KEY | ||
| SSH2 | Putty private key | .ppk | ||
| SSH2 | OpenSSH certificate | *-cert.pub | ||
| SSH2 | OpenSSH Key Revocation Lists (KRL) | |||
| SSH2 | OpenSSH signatures | SSH SIGNATURE | ||
| PGP | OpenPGP Message (signed/encrypted) | PGP MESSAGE | ||
| PGP | OpenPGP Encrypted data | (.pgp) | pgp-encrypted | |
| PGP | OpenPGP cleartext signed message | PGP SIGNED MESSAGE | ||
| PGP | OpenPGP signature (armor) | PGP SIGNATURE | (.asc), .sig | pgp-signature |
| PGP | OpenPGP Public Keys | PGP PUBLIC KEY BLOCK | .asc, (.key) | pgp-keys |
| PGP | OpenPGP Private Keys | PGP PRIVATE KEY BLOCK | ||
| PGP | OpenPGP Multi-part message | PGP MESSAGE, PART X/Y | ||
| PGP | OpenPGP Multi-part message | PGP MESSAGE, PART X | ||
| JOSE | JOSE JSON format | jose+json | ||
| JOSE | JOSE compact | jose | ||
| JOSE | JWT (JSON Web Token) | jwt, *+jwt | ||
| JOSE | JWK (public, private or secret key) | jwk+json | ||
| JOSE | JWK Set | (.jwks) | jwk-set+json, jwk-set+jwt | |
| JOSE | SD-JWT | sd-jwt, *+sd-jwt | ||
| JOSE | SD-JWT (JSON serialization) | sd-jwt+json | ||
| COSE | COSE | (.cbor) | cose, *+cose | |
| COSE | COSE key | (.cbor) | cose-key | |
| COSE | COSE key set | (.cbor) | cose-key-set | |
| COSE | COSE_X509 structure | cose-x509 | ||
| COSE | CWT (CBOR Web Token) | cwt, *+cwt | ||
| COSE | COSE standalone V2 countersignature | |||
| PASETO | PASETO (token) | |||
| PASETO | PASERK (keys) | |||
| AGE | AGE | (AGE ENCRYPTED FILE) | ||
| bytes | OpenVPN TLS Auth Key (symmetric) | OpenVPN Static key V1 | ||
| bytes | SSL Key log file | sslkeylogfile |
Explanation:
- Enc.: refers to the underlying encoding logic;
- Format: description of the format;
- Label: label when used in PEM, OpenPGPG ASCII armor and SSH textual format;
- Extension: registered or common file extension;
- Media subtype: media subtype or structured syntax suffix (
application/...).
The following encoding families of formats are detailed in this post:
- For format based on ASN.1, the ASN.1 syntax is used to define data structures. In the formats we are interested in, the data is usually encoded using the DER serialization, and is then often PEM encoded to produce an ASCII representation. This is type of format is used for example for X.509 public-key certificates (PKCs) and is usually used for associated private keys (eg. PKCS#8).
- TLS presentation syntax defined in RFC8446 (TLS 1.3) is used to define the structure of TLS messages.
- SSH uses its own data representation defined in RFC4251.
- PGP uses its own data representation defined in RFC4880.
- JOSE (JavaScript Object Signing and Encryption) is a family of cryptographic formats based on JSON. Important usages include JWT (including OpenID Connect ID tokens) and JWK (for representing public, private or secret keys).
- COSE (CBOR Object Signing and Encryption) is a similar (but not a direct port) family of formats based on CBOR instead of JSON. Important usages include CWT (like JWT but based on CBOR) and COSE keys. One important application is WebAuthn.
- PASETO is an simpler/cleaner alternative to JOSE/JWT. The related PASERK format is defined for representing keys (public, private, secret keys, including encrypted ones)[1].
Notes:
- The
.pemextension is officially registered for PEM-encoded X.509 certificates chains (i.e. the concatenation of one or more PEM-encoded X.509 public-key certificates). In practice, this extension is sometimes used for other kinds of PEM-encoded data.
Note: other labels
The following labels are found in OpenSSL code but are not included in the table:
ANY PRIVATE KEYcan be found in OpenSSL source code but it is actually used for a real format. This is only used inside the API.PARAMETERSandECDSA PUBLIC KEY, are not used (?).SM2 PRIVATE KEYandSM2 PARAMETERSare for China's SM2 (public key cryptographic algorithms based on elliptic curves) but I'm not sure this is actually used somewhere.
ASN.1
ASN.1 (Abstract Syntax Notation One) is a standard framework and schema description language for defining data structures. It is used by many cryptographic formats such as:
- X.509 (public-key) certificates (PKCs) (used for TLS server authentication, TLS client authentication, CA certificates, etc.),
- X.509 CRLs (certificate revocation lists)
- X.509 CSRs (certificate signing requests);
SubjectPublicKeyInfo(SPKI[2]) for representing public keys;- PKCS#8 for representing private keys (including encrypted private keys);
- PKCS#12 for bundling together private keys, certificates and possibly other data[3];
- PKCS#7 (CMS) for signed and/or encrypted messages (eg. in S/MIME).
Many different serialization formats[4] can be used to represent ASN.1 instances (data). For the cryptographic formats we are interested in, the DER (Distinguished Encoding Rules) serialization format is used. DER is binary and has a single possible binary representation of any instance[^asn1-der] (canonical). This property is important when we want to take a cryptographic hash or a digital signature of ASN.1 data.
Tip: useful tools for ASN.1
You can inspect the an arbitrary ASN.1 structure with[5]:
openssl asn1parse -in eckey.der inform DER
0:d=0 hl=3 l= 135 cons: SEQUENCE 3:d=1 hl=2 l= 1 prim: INTEGER :00 6:d=1 hl=2 l= 19 cons: SEQUENCE 8:d=2 hl=2 l= 7 prim: OBJECT :id-ecPublicKey 17:d=2 hl=2 l= 8 prim: OBJECT :prime256v1 27:d=1 hl=2 l= 109 prim: OCTET STRING [HEX DUMP]:306B0201010420BD7708C9DAAD50763FC5C60B21198B4E9E98737D2F51792C9ED388E89368878FA144034200044118CCA71947E6548E5DE4AA6583FA2C54099FDF2DB6E8D27C5D99AB85DEA03D442728ABFB2C1A729EC562015F40EF89BB98D1830C26F95138CB025D1C82C5D
Or if the key is encoded in PEM (see below):
openssl asn1parse -in eckey.pem -inform DER
We can clearly see in the output that the DER serialization does not contain the field names. If you want to interpret the DER data, you need to know which ASN.1 structure it is encoding.
The OpenSSL command-line interface (CLI) has dedicated tools for inspection the different cryptographic formats based on ASN.1.
ASCII armor
The native (DER) serialization of these formats is binary. However, ASN.1 instances are often encoded in a textual (ASCII) encoding, often called PEM (Privacy-Enhanced Mail) encoding (or simply PEM). This is simply
- the DER serialization,
- base64-encoded,
- between “
-----BEGIN XYZ-----” and “-----END XYZ-----”,
where XYZ is a label describing the type of ASN.1 structure, eg. CERTIFICATE for X.509 certificates.
The same idea is used in other standards such as:
Using the ASCII form has several benefits:
- It is usable in context where you expect text.
- The label can be used (by humans and by code) to know which ASN.1 structure is used without having to guess. The ASN.1 (DER) encoding does not include the name of the structure or the names of the fields.
- You can include some free-form text outside of the ASCII-armored block and it will be ignored by many tools expecting ASCII-armored data.
- You can concatenate several PEM-encoded instances (possibly with different types) in the same file. This is for example often used to bundle several certificates (for representing a list of trusted CAs, or for representing a certificate chain) or for bundling a (possibly encrypted) private key with its associated certificate chain:
- OpenSSL and many other TLS implementations can use a single file containing a concatenation of PEM-encoded certificates as trust anchors (CA certificates);
- a certificate and its ancestors (certificate chain) can be bundled in a single PEM file;
- the associated private key (either in plaintext or in encrypted form) can be bundled in the same PEM file as well.
Tip: convert between DER and PEM
You can convert any PEM encoded file into DER with:
openssl asn1parse -in example.pem -noout -out /dev/stdout > example.der
You cannot really go the other way around generically if you do not know the type of data you are handling.
Generating a PEM version from DER (if you already know the structure) can be as simple as:
echo '-----BEGIN CERTIFICATE-----'
cat certificate.crt | base64
echo '-----END CERTIFICATE-----'
The OpenSSL CLI (openssl) has commands and options to do these conversions for you.
Public key certificates
X.509 public key certificates (PKCs) are using the Certificate structure:
Certificate ::= SEQUENCE {
tbsCertificate TBSCertificate,
signatureAlgorithm AlgorithmIdentifier,
signature BIT STRING }
TBSCertificate ::= SEQUENCE {
version [0] Version DEFAULT v1,
serialNumber CertificateSerialNumber,
signature AlgorithmIdentifier,
issuer Name,
validity Validity,
subject Name,
subjectPublicKeyInfo SubjectPublicKeyInfo,
issuerUniqueID [1] IMPLICIT UniqueIdentifier OPTIONAL,
-- If present, version MUST be v2 or v3
subjectUniqueID [2] IMPLICIT UniqueIdentifier OPTIONAL,
-- If present, version MUST be v2 or v3
extensions [3] Extensions OPTIONAL
-- If present, version MUST be v3 -- }
SubjectPublicKeyInfo ::= SEQUENCE {
algorithm AlgorithmIdentifier,
subjectPublicKey BIT STRING }
Note: a short primer public key certificate
A (X.509) public key certificate (PKC), usually simply called a certificate, is a statement that a public key (or, if you will, the associated private key) belongs to a given entity (the subject) which may be for example:
- a HTTPS server (or another TLS server) for server authentication (in TLS);
- a HTTPS client (or another TLS client) such as a software, IoT device or end-user for client authentication (in TLS, see mTLS);
- an intermediate certificate authority;
- a root certificate authority;
- an actual person (for example in S/MIME protected emails).
This statement is signed by a certificate authority (the issuer) (which is expected to be trusted).
Other are (or can be) included in this statement such as:
- the period of validity of the certificate;
- the intended key usages;
- the intended extended key usages.
File extensions:
- The
.cerextension is often used for the raw DER form. - The
.crtor.pemextensions are often used for PEM encoded certificates (withBEGIN CERTIFICATE).
Example: X.509 certificate
For example, the certificate of my blog (https://www.gabriel.urdhr.fr/), in PEM format, is currently:
-----BEGIN CERTIFICATE-----
MIIFBDCCA+ygAwIBAgISBiLPuECTgGnu71vcEGdVjNf8MA0GCSqGSIb3DQEBCwUA
MDMxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1MZXQncyBFbmNyeXB0MQwwCgYDVQQD
EwNSMTMwHhcNMjUxMjMxMDMyOTM1WhcNMjYwMzMxMDMyOTM0WjAfMR0wGwYDVQQD
ExR3d3cuZ2FicmllbC51cmRoci5mcjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCC
AQoCggEBAJa7kkMa7E/DlhvY4q/Y3R7/BIV51aaYtPmUVqvX0ZxkU4MBelBG9+HM
CALScRpYOmyUvMHrdotIgvBp/qj+g1NUXnfy5y23vWW/aQmNIFNJxts+DqIVWBlB
wTkG1aKmntY/WGoNSQIyMT/6159pI3dfs1mfnqUZCcI5jBMmeSO1evMlF1Ikka0f
O0oxYgGrrFGCm8ect1v1MImpfsNbcvK8+bZHFm7Uocj0COAnTZMs2zqsU2C7bEQs
8BSYKiyUslDwvOUgGpHIawPbujg8Avz+FS1KDrSeOmhbaQ0Aaznz05jIpSWLhKJG
ZGiV85PneVLcZ4/dvqAnbcWmz2I1vSUCAwEAAaOCAiQwggIgMA4GA1UdDwEB/wQE
AwIFoDAdBgNVHSUEFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIwDAYDVR0TAQH/BAIw
ADAdBgNVHQ4EFgQUDbS+5CwA+larxE7EDSTRAl9fUQ0wHwYDVR0jBBgwFoAU56uf
DywzoFPTXk94yLKEDjvWkjMwMwYIKwYBBQUHAQEEJzAlMCMGCCsGAQUFBzAChhdo
dHRwOi8vcjEzLmkubGVuY3Iub3JnLzAfBgNVHREEGDAWghR3d3cuZ2FicmllbC51
cmRoci5mcjATBgNVHSAEDDAKMAgGBmeBDAECATAvBgNVHR8EKDAmMCSgIqAghh5o
dHRwOi8vcjEzLmMubGVuY3Iub3JnLzEyNS5jcmwwggEDBgorBgEEAdZ5AgQCBIH0
BIHxAO8AdQDLOPcViXyEoURfW8Hd+8lu8ppZzUcKaQWFsMsUwxRY5wAAAZtyqcBZ
AAAEAwBGMEQCIF36B4+J7X3D9HLemZ0ueoELmrjYCXRiAFm0OgQvtDkuAiAMdsXF
lnfQfFSlY7+Ba2CS+IaiQR62EIh9KIYWE/x00gB2ANFuqaVoB35mNaA/N6XdvAOl
PEESFNSIGPXpMbMjy5UEAAABm3KpwRoAAAQDAEcwRQIgftiAfM1bbhRRGzq2VKaw
N1rwqk0fJglDlKxnEPFyNfsCIQCO9FyjlVm439I8YlaRkBhm9JG4+tkrsEnMzJA4
JegjKDANBgkqhkiG9w0BAQsFAAOCAQEAXWnnMH26PAPviYWgHkJ/32QQgsUem/ot
M3b0BBZUOrw4ixB8/TdIlbqmzsVzuQ4PX87+G8QYciZ0zxqD7qJYBPlRCCJaCr8z
eEERIv3i0jZAXjyg2VryNAS8OEmNe45uS5Nb7zxgtUn26UiyUlAdqpwNKMNNUKg3
SPH+wcrcvejZw1dUZMg9fkauTmfNWkgxPAHXR6PoeY1QfxAfMQJsFzvJC/+MZKkR
Hgl2EZLu4uH9XAh1N4+IeZL9A9pt68nmKiG/CcND8xBLVU66eeyfyaF3pBZiA1GC
BoQK97SZcsiDAndxJlIwITyRd0zV14Q0ZgiB0FfRJUhLDXNbT3k7pA==
-----END CERTIFICATE-----
Which can be decoded as:
Certificate:
Data:
Version: 3 (0x2)
Serial Number:
06:22:cf:b8:40:93:80:69:ee:ef:5b:dc:10:67:55:8c:d7:fc
Signature Algorithm: sha256WithRSAEncryption
Issuer: C=US, O=Let's Encrypt, CN=R13
Validity
Not Before: Dec 31 03:29:35 2025 GMT
Not After : Mar 31 03:29:34 2026 GMT
Subject: CN=www.gabriel.urdhr.fr
Subject Public Key Info:
Public Key Algorithm: rsaEncryption
Public-Key: (2048 bit)
Modulus:
00:96:bb:92:43:1a:ec:4f:c3:96:1b:d8:e2:af:d8:
dd:1e:ff:04:85:79:d5:a6:98:b4:f9:94:56:ab:d7:
d1:9c:64:53:83:01:7a:50:46:f7:e1:cc:08:02:d2:
71:1a:58:3a:6c:94:bc:c1:eb:76:8b:48:82:f0:69:
fe:a8:fe:83:53:54:5e:77:f2:e7:2d:b7:bd:65:bf:
69:09:8d:20:53:49:c6:db:3e:0e:a2:15:58:19:41:
c1:39:06:d5:a2:a6:9e:d6:3f:58:6a:0d:49:02:32:
31:3f:fa:d7:9f:69:23:77:5f:b3:59:9f:9e:a5:19:
09:c2:39:8c:13:26:79:23:b5:7a:f3:25:17:52:24:
91:ad:1f:3b:4a:31:62:01:ab:ac:51:82:9b:c7:9c:
b7:5b:f5:30:89:a9:7e:c3:5b:72:f2:bc:f9:b6:47:
16:6e:d4:a1:c8:f4:08:e0:27:4d:93:2c:db:3a:ac:
53:60:bb:6c:44:2c:f0:14:98:2a:2c:94:b2:50:f0:
bc:e5:20:1a:91:c8:6b:03:db:ba:38:3c:02:fc:fe:
15:2d:4a:0e:b4:9e:3a:68:5b:69:0d:00:6b:39:f3:
d3:98:c8:a5:25:8b:84:a2:46:64:68:95:f3:93:e7:
79:52:dc:67:8f:dd:be:a0:27:6d:c5:a6:cf:62:35:
bd:25
Exponent: 65537 (0x10001)
X509v3 extensions:
X509v3 Key Usage: critical
Digital Signature, Key Encipherment
X509v3 Extended Key Usage:
TLS Web Server Authentication, TLS Web Client Authentication
X509v3 Basic Constraints: critical
CA:FALSE
X509v3 Subject Key Identifier:
0D:B4:BE:E4:2C:00:FA:56:AB:C4:4E:C4:0D:24:D1:02:5F:5F:51:0D
X509v3 Authority Key Identifier:
E7:AB:9F:0F:2C:33:A0:53:D3:5E:4F:78:C8:B2:84:0E:3B:D6:92:33
Authority Information Access:
CA Issuers - URI:http://r13.i.lencr.org/
X509v3 Subject Alternative Name:
DNS:www.gabriel.urdhr.fr
X509v3 Certificate Policies:
Policy: 2.23.140.1.2.1
X509v3 CRL Distribution Points:
Full Name:
URI:http://r13.c.lencr.org/125.crl
CT Precertificate SCTs:
Signed Certificate Timestamp:
Version : v1 (0x0)
Log ID : CB:38:F7:15:89:7C:84:A1:44:5F:5B:C1:DD:FB:C9:6E:
F2:9A:59:CD:47:0A:69:05:85:B0:CB:14:C3:14:58:E7
Timestamp : Dec 31 04:28:06.105 2025 GMT
Extensions: none
Signature : ecdsa-with-SHA256
30:44:02:20:5D:FA:07:8F:89:ED:7D:C3:F4:72:DE:99:
9D:2E:7A:81:0B:9A:B8:D8:09:74:62:00:59:B4:3A:04:
2F:B4:39:2E:02:20:0C:76:C5:C5:96:77:D0:7C:54:A5:
63:BF:81:6B:60:92:F8:86:A2:41:1E:B6:10:88:7D:28:
86:16:13:FC:74:D2
Signed Certificate Timestamp:
Version : v1 (0x0)
Log ID : D1:6E:A9:A5:68:07:7E:66:35:A0:3F:37:A5:DD:BC:03:
A5:3C:41:12:14:D4:88:18:F5:E9:31:B3:23:CB:95:04
Timestamp : Dec 31 04:28:06.298 2025 GMT
Extensions: none
Signature : ecdsa-with-SHA256
30:45:02:20:7E:D8:80:7C:CD:5B:6E:14:51:1B:3A:B6:
54:A6:B0:37:5A:F0:AA:4D:1F:26:09:43:94:AC:67:10:
F1:72:35:FB:02:21:00:8E:F4:5C:A3:95:59:B8:DF:D2:
3C:62:56:91:90:18:66:F4:91:B8:FA:D9:2B:B0:49:CC:
CC:90:38:25:E8:23:28
Signature Algorithm: sha256WithRSAEncryption
Signature Value:
5d:69:e7:30:7d:ba:3c:03:ef:89:85:a0:1e:42:7f:df:64:10:
82:c5:1e:9b:fa:2d:33:76:f4:04:16:54:3a:bc:38:8b:10:7c:
fd:37:48:95:ba:a6:ce:c5:73:b9:0e:0f:5f:ce:fe:1b:c4:18:
72:26:74:cf:1a:83:ee:a2:58:04:f9:51:08:22:5a:0a:bf:33:
78:41:11:22:fd:e2:d2:36:40:5e:3c:a0:d9:5a:f2:34:04:bc:
38:49:8d:7b:8e:6e:4b:93:5b:ef:3c:60:b5:49:f6:e9:48:b2:
52:50:1d:aa:9c:0d:28:c3:4d:50:a8:37:48:f1:fe:c1:ca:dc:
bd:e8:d9:c3:57:54:64:c8:3d:7e:46:ae:4e:67:cd:5a:48:31:
3c:01:d7:47:a3:e8:79:8d:50:7f:10:1f:31:02:6c:17:3b:c9:
0b:ff:8c:64:a9:11:1e:09:76:11:92:ee:e2:e1:fd:5c:08:75:
37:8f:88:79:92:fd:03:da:6d:eb:c9:e6:2a:21:bf:09:c3:43:
f3:10:4b:55:4e:ba:79:ec:9f:c9:a1:77:a4:16:62:03:51:82:
06:84:0a:f7:b4:99:72:c8:83:02:77:71:26:52:30:21:3c:91:
77:4c:d5:d7:84:34:66:08:81:d0:57:d1:25:48:4b:0d:73:5b:
4f:79:3b:a4
~~~
Tip: useful commands for certificates
DER/PEM conversion:
openssl x509 -in certificate.cer -inform DER -outform PEM > certificate.crt
openssl x509 -in certificate.crt -inform PEM -outform DER > certificate.cer
Grab a certificate from a TLS server:
openssl s_client -connect www.gabriel.urdhr.fr:443 < /dev/null 2> /dev/null | openssl x509 > server.crt
Display information about a certificate:
openssl s_client -connect www.gabriel.urdhr.fr:443 < /dev/null 2> /dev/null | openssl x509 -text
Compute SHA-256 fingerprint (cryptographic hash) of a certificate:
openssl s_client -connect www.gabriel.urdhr.fr:443 < /dev/null 2> /dev/null |
openssl x509 -outform DER |
openssl dgst -sha256 -binary |
basenc --base16 -w0
Generate a fingerprint (cryptographic hash) of the public key (SPKI fingereprint):
openssl x509 -in example.pem -pubkey -noout |
openssl pkey -pubin -outform der |
openssl dgst -sha256 -binary |
basenc --base16 -w0
openssl x509 -in example.pem -pubkey -noout |
openssl pkey -pubin -outform der |
openssl dgst -sha256 -binary |
basenc --base64url
Generate a self-signed (server) certificate:
openssl req -x509 \
-days 365 \
-newkey ec -pkeyopt ec_paramgen_curve:P-256 -keyout foo.key -nodes \
-out foo.pem \
-subj "/C=FR/ST=Paris/O=Foo/OU=FooTest/CN=server.example.com" \
-addext "keyUsage = critical, digitalSignature, keyCertSign" \
-addext "extendedKeyUsage = critical, serverAuth" \
-addext "subjectAltName = critical, DNS:foo.example.com"
Trust anchors
The following formats are defined to represent trust anchors for X.509 certificates:
- a (typically self-signed) certificate;
- a OpenSSL-specific "Trusted Certificate" structure (which can be used to add extra info, such as constraints to a trust anchor represented as a certificate);
- RFC5914 Trust Anchor Format which can be used to represent a trust anchor without including a signature;
- a unsigned X.509 certificates which is a standard X.509 certicicate with a dummy empty signature.
In practice,
- Only the first one is widely used.
- The second is specific to OpenSSL and I am not aware of any support for it in other implementations.
- I have not seen any actual support the two other options.
- I would expect the last option to be quite easy to integrate in existing libraries.
Certificate as trust anchors
X.509 public key certificates are usually used to represent trust anchors (root certificate authorities). For example, you typical Unix system will have a bunch of .pem files for representing trust anchors (root CA certificates):
cat /etc/ssl/certs/ISRG_Root_X2.pem
-----BEGIN CERTIFICATE-----
MIICGzCCAaGgAwIBAgIQQdKd0XLq7qeAwSxs6S+HUjAKBggqhkjOPQQDAzBPMQsw
CQYDVQQGEwJVUzEpMCcGA1UEChMgSW50ZXJuZXQgU2VjdXJpdHkgUmVzZWFyY2gg
R3JvdXAxFTATBgNVBAMTDElTUkcgUm9vdCBYMjAeFw0yMDA5MDQwMDAwMDBaFw00
MDA5MTcxNjAwMDBaME8xCzAJBgNVBAYTAlVTMSkwJwYDVQQKEyBJbnRlcm5ldCBT
ZWN1cml0eSBSZXNlYXJjaCBHcm91cDEVMBMGA1UEAxMMSVNSRyBSb290IFgyMHYw
EAYHKoZIzj0CAQYFK4EEACIDYgAEzZvVn4CDCuwJSvMWSj5cz3es3mcFDR0HttwW
+1qLFNvicWDEukWVEYmO6gbf9yoWHKS5xcUy4APgHoIYOIvXRdgKam7mAHf7AlF9
ItgKbppbd9/w+kHsOdx1ymgHDB/qo0IwQDAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0T
AQH/BAUwAwEB/zAdBgNVHQ4EFgQUfEKWrt5LSDv6kviejM9ti6lyN5UwCgYIKoZI
zj0EAwMDaAAwZQIwe3lORlCEwkSHRhtFcP9Ymd70/aTSVaYgLXTWNLxBo1BfASdW
tL4ndQavEi51mI38AjEAi/V3bNTIZargCyzuFJ0nN6T5U6VR5CmD1/iQMVtCnwr1
/q4AaOeMSQ+2b1tbFfLn
-----END CERTIFICATE-----
Many programs expect trust anchors in this form, either in one directory (one certificate per file) or concatenated in a single file.
OpenSSL Trusted Certificate
OpenSSL supports attaching additional information to a trust anchor with its custom (non standard) "TRUSTED CERTIFICATE" format. This is a concatenation of a Certificate instance with a X509_CERT_AUX instance. OpenSSL support attaching extra information to the X.509 trust anchor. This is done by directly concatenating a ASN.1 X509_AUX structure after the X509 structure:
X509_CERT_AUXroutines. These are used to encode additional user modifiable data about a certificate. This data is appended to the X509 encoding when the *_X509_AUX routines are used. This means that the "traditional" X509 routines will simply ignore the extra data.[...]
X509_AUXASN1 routines.X509_AUXis the name given to a certificate with extra info tagged on the end. Since these functions set how a certificate is trusted they should only be used when the certificate comes from a reliable source such as local storage.
The OpenSSL X509_CERT_AUX structure (defined in C):
ASN1_SEQUENCE(X509_CERT_AUX) = {
ASN1_SEQUENCE_OF_OPT(X509_CERT_AUX, trust, ASN1_OBJECT),
ASN1_IMP_SEQUENCE_OF_OPT(X509_CERT_AUX, reject, ASN1_OBJECT, 0),
ASN1_OPT(X509_CERT_AUX, alias, ASN1_UTF8STRING),
ASN1_OPT(X509_CERT_AUX, keyid, ASN1_OCTET_STRING),
ASN1_IMP_SEQUENCE_OF_OPT(X509_CERT_AUX, other, X509_ALGOR, 1)
} ASN1_SEQUENCE_END(X509_CERT_AUX)
Which (AFAIU) should translate into something like this in ASN.1:
CertAux ::= SEQUENCE {
trust SEQUENCE OF OBJECT IDENTIFIER OPTIONAL,
reject [0] SEQUENCE OF OBJECT IDENTIFIER OPTIONAL,
alias UTF8String OPTIONAL,
keyid OCTET STRING OPTIONAL,
other [1] SEQUENCE OF AlgorithmIdentifier AlgorithmIdentifier OPTIONAL,
}
Meaning:
trustis a list of extended key usages (EKE) to allow for this CA;rejectis a list of extended key usages (EKE) to refuse for this CA;aliasis just an alias for the CA;keyid???other???
This does not allow setting name constraints for certificates (eg. restrict a given CA to be acceptable only for .gov.ir domains).
Trust Anchor Format
A Trust Anchor Format is defined in RFC5914 to attach informations to a X.509 certificate (TrustAnchorInfo structure). This could for example be used to apply a name constraint on a given CA. However, I am not aware of any software which actually support this format.
TrustAnchorInfo ::= SEQUENCE {
version TrustAnchorInfoVersion DEFAULT v1,
pubKey SubjectPublicKeyInfo,
keyId KeyIdentifier,
taTitle TrustAnchorTitle OPTIONAL,
certPath CertPathControls OPTIONAL,
exts [1] EXPLICIT Extensions OPTIONAL,
taTitleLangTag [2] UTF8String OPTIONAL }
CertPathControls ::= SEQUENCE {
taName Name,
certificate [0] Certificate OPTIONAL,
policySet [1] CertificatePolicies OPTIONAL,
policyFlags [2] CertPolicyFlags OPTIONAL,
nameConstr [3] NameConstraints OPTIONAL,
pathLenConstraint[4] INTEGER (0..MAX) OPTIONAL}
CertPolicyFlags ::= BIT STRING {
inhibitPolicyMapping (0),
requireExplicitPolicy (1),
inhibitAnyPolicy (2) }
The TrustAnchorList structure is defined as well as a way to represent a collection of trust anchors (either as plain certificate, as unsigned certificates or as a TrustAnchorInfo).
TrustAnchorList ::= SEQUENCE SIZE (1..MAX) OF TrustAnchorChoice
TrustAnchorChoice ::= CHOICE {
certificate Certificate,
tbsCert [1] EXPLICIT TBSCertificate,
taInfo [2] EXPLICIT TrustAnchorInfo }
Unsigned X.509 certificates
Alternatively RFC9925, adds support for omitting the signature in X.509 certificates. This can be useful to represent trust anchors with minimal impact on the application code.
Note: structure of unsigned X.509 certificates
- Uses a “unsigned” algorithm identifier (
id-alg-unsigned) - Signature is empty.
- Does not really have an issuer:
- The issuer can be set as the subject (for better compatibility with self-signed certificates).
- Alternatively it can be set to a dummy RDN of type
id-rdna-unsigned.
id-alg-unsigned OBJECT IDENTIFIER ::= {1 3 6 1 5 5 7 6 36}
id-rdna-unsigned OBJECT IDENTIFIER ::= {1 3 6 1 5 5 7 25 1}
Certificate chain
A (.pem) text file containing a concatenation of PEM encoded certificates is typically used to represent a certificate chain:
- RFC8555 (ACME) defines the
application/pem-certificate-chainmedia type for such a file. The certificates are listed from the leaf to the root (which may not be included). - Many programs expect a certificate chain in this form. See for example NGINX's
ssl_certificatedirective or Apache'sSSLCertificateFiledirective. In both cases, the leaf certificate should come first and the intermediate certificates after. Moreover, in both cases the private key can be included in the same file as well.
RFC6066 defines the application/pkix-pkipath media type for representing a certificate chain in raw DER based on the following structure (from leaf to root):
PkiPath ::= SEQUENCE OF Certificate
In practice, the PkiPath structure is only used in the context of the Client Certificate URLs TLS extension.
Public keys
SPKI
The SubjectPublicKeyInfo (SPKI) structure is the structure used as part of certificates to represent the certificate public key. This structure can be used to store public keys of various types without additional metadata:
SubjectPublicKeyInfo ::= SEQUENCE {
algorithm AlgorithmIdentifier,
subjectPublicKey BIT STRING }
Tip: useful commands for public keys
Extract the public key from the private key:
openssl pkey -in eckey.pem -pubout -out ecpubkey.pem # BEGIN PUBLIC KEY
openssl pkey -in eckey.der -pubout -out ecpubkey.der -inform DER -outform DER
Extract the public key from the certificate:
openssl x509 -in example.pem -pubkey -noout | openssl pkey -pubin -outform DER
openssl x509 -in example.pem -pubkey -noout | openssl pkey -pubin -outform PEM # BEGIN PUBLIC KEY
Legacy public key formats
The PKCS#1 RSAPublicKey structrue can be used for RSA keys as well:
RSAPublicKey ::= SEQUENCE {
modulus INTEGER, -- n
publicExponent INTEGER -- e
}
Private keys
Several ASN.1 formats can be used to represent private keys:
- PKCS#8 (standard);
- PKCS#12 (used to store a private key with associated certificates);
- Legacy PKCS#1 format.
PKCS#8
PKCS#8 is a standard for representing private keys either in plaintext (PrivateKeyInfo) or encrypted (EncryptedPrivateKeyInfo):
PrivateKeyInfo ::= SEQUENCE {
version Version,
privateKeyAlgorithm PrivateKeyAlgorithmIdentifier,
privateKey PrivateKey,
attributes [0] IMPLICIT Attributes OPTIONAL }
EncryptedPrivateKeyInfo ::= SEQUENCE {
encryptionAlgorithm EncryptionAlgorithmIdentifier,
encryptedData EncryptedData }
Tip: commands for private keys
Display information about a private key:
openssl pkey -in eckey.pem -text -noout
Encrypt a private key:
openssl pkey -in key.pem -aes256 -out keyenc.pem
Decryt a private key:
openssl pkey -in keyenc.pem -out key.pem
Tip: commands for key generation
Generate an EC key:
openssl genpkey -algorithm ec -pkeyopt ec_paramgen_curve:P-256 -out eckey.pem
Generate an encrypted private key:
openssl genpkey -algorithm ec -pkeyopt ec_paramgen_curve:P-256 -out eckey.pem -aes256
Generate a (finite field) Diffie-Hellman (DH, FFDH) private key:
openssl genpkey -algorithm DH -pkeyopt dh_param:ffdhe8192 -out dhkey.pem
Generate a RSA private key:
openssl genpkey -algorithm rsa -pkeyopt rsa_keygen_bits:7680 -out rsakey.pem
Generate a RSA-PSS[6] private key:
openssl genpkey -algorithm rsa-pss -pkeyopt rsa_keygen_bits:7680 -out rsapsskey.pem
openssl genpkey -algorithm rsa-pss -pkeyopt rsa_keygen_bits:7680 -out rsapsskey.pem -outpuibkey rsapsspubkey.pem
Generate a DSA private key (don't use this for real stuff!):
openssl genpkey -algorithm dsa -genparam -out dsaparam.pem
openssl genpkey -paramfile dsaparam.pem -out dsakey.pem
PKCS#12
PKCS#12 (Public Key Cryptography Standards #12) is a container which can contain:
- one or more certificates;
- one or more private keys (can be encrypted);
- CRLs.
This is often used to bundle a private keys with its associated (leaf) certificate and intermediate certificates.
Tip: commands for PKCS#12 file format
Combine all your input PEM files:
cat foo.key foo.pem /etc/ssl/certs/emSign_ECC_Root_CA_-_C3.pem > combined.pem
Convert this into a PKCS#12 file:
openssl pkcs12 -export -in combined.pem -out foo.p12 -name "My key"
Convert a PKCS#12 into a combined PEM file:
openssl pkcs12 -in foo.p12 -out foo.pem
Display info about PKCS#12:
openssl pkcs12 -in foo.p12 -info -nokeys # Info
openssl pkcs12 -in foo.p12 -nokeys -clcerts # Client certs
openssl pkcs12 -in foo.p12 -nokeys -cacerts # CA certs
openssl pkcs12 -in foo.p12 -noenc -nocerts # Raw key
openssl pkcs12 -in foo.p12 -nocerts # Encrypted key
openssl pkcs12 -in foo.p12 -noenc # Chain, not encrypted
Legacy private key formats
RSA and DSA keys could be found in some older traditional/legacy format. For RSA, this is based on PKCS#1 (RSAPrivateKey). There is probably not much reason to use these.
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
}
Tip: commands for private keys and legacy format
Convert a DSA or RSA private key to the legacy (traditional) format (BEGIN DSA/RSA PRIVATE KEY):
openssl pkey -in key.pem -traditional -out keytraditional.pem
Convert from the legacy format to the standard format:
openssl pkey -in keytraditional.pem -out key.pem
TLS
TLS messages are defined using the TLS presentation syntax. This syntax and the associated serialization are documented in RFC8446 (TLS 1.3).
For example:
struct {
ProtocolVersion legacy_version = 0x0303; /* TLS v1.2 */
Random random;
opaque legacy_session_id<0..32>;
CipherSuite cipher_suites<2..2^16-2>;
opaque legacy_compression_methods<1..2^8-1>;
Extension extensions<8..2^16-1>;
} ClientHello;
This serialization format is reused for:
- TLS Encrypted Client Hello (ECH) configuration when stored in PEM file;
- Oblivious DNS over HTTPS (ODoH) configuration and messages;
- MLS (Messaging Layer Security) which slightly extends the TLS presentation language.
TLS Encrypted Client Hello Configuration
The TLS Encrypted Client Hello (ECH) configuration (ECHConfigList) is typically transported in the DNS (in HTTPS or SVCB resource records). In can as well be stored in PEM file with the ECHCONFIG label.
"Example: ECH configuration in PEM (including private key):
-----BEGIN PRIVATE KEY-----
MC4CAQAwBQYDK2VuBCIEICjd4yGRdsoP9gU7YT7My8DHx1Tjme8GYDXrOMCi8v1V
-----END PRIVATE KEY-----
-----BEGIN ECHCONFIG-----
AD7+DQA65wAgACA8wVN2BtscOl3vQheUzHeIkVmKIiydUhDCliA4iyQRCwAEAAEA
AQALZXhhbXBsZS5jb20AAA==
-----END ECHCONFIG-----
Schema of the ECH configuration:
~~~asn1
struct {
uint8 config_id;
HpkeKemId kem_id;
HpkePublicKey public_key;
HpkeSymmetricCipherSuite cipher_suites<4..2^16-4>;
} HpkeKeyConfig;
struct {
ECHConfigExtensionType type;
opaque data<0..2^16-1>;
} ECHConfigExtension;
struct {
HpkeKeyConfig key_config;
uint8 maximum_name_length;
opaque public_name<1..255>;
ECHConfigExtension extensions<0..2^16-1>;
} ECHConfigContents;
struct {
uint16 version;
uint16 length;
select (ECHConfig.version) {
case 0xfe0d: ECHConfigContents contents;
}
} ECHConfig;
ECHConfig ECHConfigList<4..2^16-1>;
Oblivious DNS over HTTPS
Oblivious DNS over HTTPS (ODoH) it an extension of DNS-over-HTTPS where an intermediate relay server (Oblivious Proxy) is used to hide the identity of the real DNS client from the target DNS server.
ODoH message are defined using the TLS presentation syntax (ObliviousDoHMessage):
struct {
opaque dns_message<1..2^16-1>;
opaque padding<0..2^16-1>;
} ObliviousDoHMessagePlaintext;
ObliviousDoHMessagePlaintext ObliviousDoHQuery;
ObliviousDoHMessagePlaintext ObliviousDoHResponse;
struct {
uint8 message_type;
opaque key_id<0..2^16-1>;
opaque encrypted_message<1..2^16-1>;
} ObliviousDoHMessage;
They use the application/oblivious-dns-message media type.
The cryptographic configuration[7] of an ODoH target server (Oblivious Target) is defined using the TLS presentation syntax as well:
struct {
uint16 kem_id;
uint16 kdf_id;
uint16 aead_id;
opaque public_key<1..2^16-1>;
} ObliviousDoHConfigContents;
struct {
uint16 version;
uint16 length;
select (ObliviousDoHConfig.version) {
case 0x0001: ObliviousDoHConfigContents contents;
}
} ObliviousDoHConfig;
ObliviousDoHConfig ObliviousDoHConfigs<1..2^16-1>;
MLS
The MLS (Messaging Layer Security) Protocol reuses (and extends) the TLS presentation language for its messages:
struct {
ProtocolVersion version = mls10;
WireFormat wire_format;
select (MLSMessage.wire_format) {
case mls_public_message:
PublicMessage public_message;
case mls_private_message:
PrivateMessage private_message;
case mls_welcome:
Welcome welcome;
case mls_group_info:
GroupInfo group_info;
case mls_key_package:
KeyPackage key_package;
};
} MLSMessage;
They use the message/mls media type.
QUIC encoding
QUIC defines yet another format and notation.
Example of QUIC notation:
Long Header Packet {
Header Form (1) = 1,
Fixed Bit (1) = 1,
Long Packet Type (2),
Type-Specific Bits (4),
Version (32),
Destination Connection ID Length (8),
Destination Connection ID (0..160),
Source Connection ID Length (8),
Source Connection ID (0..160),
Type-Specific Payload (..),
}
This format is reused for:
- Oblivious HTTP key confirmation and messages.
SSH
SSH uses its own data representation defined in RFC4251.
Note: SSH vs. OpenSSH
This section mentions sometimes “SSH” and sometimes “OpenSSH”. At the risk of stating the obvious, they are not the same thing:
- SSH is the name of the protocol. This is a standard protocol defined (for version 2, SSHv2) in RFC 4250 through RFC 4256.
- OpenSSH is the name of a very popular implementation (both client-side and server-side) of the protocol SSH.
Many other SSH implementations exist such as:
SSH public keys
Raw SSH public keys
The SSH protocol transports public keys in a different (binary) format which can be seen as the native SSH format for public keys. This format is not based on ASN.1 but on SSH's data representation conventions. The IANA registry for SSH public key names can be used to find descriptions of the representation of the different public keys types. They always begin with a string representing the key type. For example:
ssh-dssfor DSA public keys;ssh-rsafor RSA public keys;ssh-ed25519andssh-ed448for Ed25519 and Ed448 public keys.
For example, the schema for ssh-ed25519:
string "ssh-ed25519"
string key
Here, 'key' is the 32-octet public key described in [RFC8032], Section 5.1.5.
SSH public keys in standard text format
RFC4716 defines format for representing SSH public keys in text format. This works similar to PEM encoding but encodes the public in raw SSH format.
In addition, the format support attaching header fields (Subject, Comment, etc.) to the public key.
Example: SSH public key in standard SSH key format
The following is an example of SSH public key in standard SSH format:
---- BEGIN SSH2 PUBLIC KEY ----
Comment: foo@mymachine.example.com
AAAAC3NzaC1lZDI1NTE5AAAAIK07V4wUjidorCCMY9KSJugdrNaT90zAfwXD2KgKo0fd
---- END SSH2 PUBLIC KEY ----
Notice how the binary blob contains the SSH key type (here ssh-ed25519):
$ echo AAAAC3NzaC1lZDI1NTE5AAAAIK07V4wUjidorCCMY9KSJugdrNaT90zAfwXD2KgKo0fd | base64 -d | xxd
00000000: 0000 000b 7373 682d 6564 3235 3531 3900 ....ssh-ed25519.
00000010: 0000 20ad 3b57 8c14 8e27 68ac 208c 63d2 .. .;W...'h. .c.
00000020: 9226 e81d acd6 93f7 4cc0 7f05 c3d8 a80a .&......L.......
00000030: a347 dd
SSH public keys in OpenSSH format
This text format is not the format typically used by OpenSSH. OpenSSH encodes a public key on a single line with:
- a leading string containing the key type;
- an optional trailing comment.
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIK07V4wUjidorCCMY9KSJugdrNaT90zAfwXD2KgKo0fd foo@mymachine.example.com
Notice how the key type is actually duplicated in this line:
- it is present in the first field;
- it is present as well in the public key data (encoded in base64).
Files such as ~/.ssh/id_ed25519.pub (for private key ~/.ssh/id_ed25519), ~/.ssh/known_hosts, /etc/ssh/ssh_host_ecdsa_key.pub are using this format:
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIK07V4wUjidorCCMY9KSJugdrNaT90zAfwXD2KgKo0fd foo@mymachine.example.com
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIB82YazPtJ7IdagLVTDuecv7ftFLb3RxBBck3KhV/qwT foo@myothermachine.example.com
Tip: convert between RFC4716 and OpenSSH format
ssh-keygen -e -m RFC4716 -f id_ed25519.pub > id_ed25519.rfc4716
ssh-keygen -i -m RFC4716 -f id_ed25519.rfc4716 > id_ed25519.pub
OpenSSH authorized_keys files (~/.ssh/authorized_keys) contain one such entry per line with optional additional options.
OpenSSH known_hosts files (~/.ssh/known_hosts) are based on this format as well:
odin.urdhr.fr,91.134.135.153 ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBF+KgjoBJDa1o6ujC7gfJTTEqDTruJV78scD7fUDJ/hGpPRl4CoWcWAX28AJksh/o9shfdxEnIjA9xFROFoH0Mw=
gitlab.freedesktop.org ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOzdNH/aTnOOINO/iGupQ/rYnmKF40ESCrkRg+5JkLVN
gitlab.freedesktop.org ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBLuKqEdZd9cm7EpVVkg2iWADT6+x2eJuwHuRqXU6/V/hcZZ53Xf/5Ha6wfa9Cqq+U1cfYtYHKLsAXHolhOCZlEg=
SSH certificates
In the basic use case, SSH clients and servers only send public keys. However, SSH has support for certificates as well. For SSH, a certificate is just a special type of public keys with extra information.
Standard SSH certificates
The following standard public key type can be used for representing (X.509 or PGP) certificates:
pgp-sign-rsa, OpenPGP certificates (RSA key)pgp-sign-dss, OpenPGP certificates (DSS key)x509v3-ssh-dssx509v3-ssh-rsax509v3-rsa2048-sha256x509v3-ecdsa-sha2-*
RFC6187 describes using a X.509 certificates chain as a SSH public key which could be used to integrate SSH in a standard X.509 PKI:
string "x509v3-ssh-dss" / "x509v3-ssh-rsa" /
"x509v3-rsa2048-sha256" / "x509v3-ecdsa-sha2-[identifier]"
uint32 certificate-count
string certificate[1..certificate-count]
uint32 ocsp-response-count
string ocsp-response[0..ocsp-response-count]
However, these forms of SSH certificates are not supported by your typical SSH implementation (OpenSSH, PuTTY, dropbear).
OpenSSH certificates
OpenSSH supports (non-standard, OpenSSH-specific) lightweight certificates with types such as:
ssh-rsa-cert-v01@openssh.comssh-dss-cert-v01@openssh.comecdsa-sha2-nistp256-cert-v01@openssh.comecdsa-sha2-nistp384-cert-v01@openssh.comecdsa-sha2-nistp521-cert-v01@openssh.comssh-ed25519-cert-v01@openssh.comrsa-sha2-256-cert-v01@openssh.comrsa-sha2-512-cert-v01@openssh.com
In OpenSSH certificates, the certrifictes are always leaf certificates. There is no notion of a CA certificate (or certificate chain), only a CA public key.
For example for ed25519:
ED25519 certificate
string "ssh-ed25519-cert-v01@openssh.com"
string nonce
string pk
uint64 serial
uint32 type
string key id
string valid principals
uint64 valid after
uint64 valid before
string critical options
string extensions
string reserved
string signature key
string signature
For private key ~/.ssh/id_ed25519, OpenSSH looks for a certificate in the file ~/.ssh/id_ed25519-cert.pub.
Tip: generate OpenSSH certificates
ssh-keygen -s ca_key -I key_id -n john,root user_key.pub -V +1d
ssh-keygen -s ca_key -I key_id -h -n server.example.com host_key.pub -V +7d
SSH private keys
SSH does not send private keys on the wire 😆 so there is no standard serialization for private keys. Different implementations use different private key formats.
OpenSSH private keys
The native format for private keys in OpenSSH is described in PROTOCOL.key:
#define AUTH_MAGIC "openssh-key-v1"
byte[] AUTH_MAGIC
string ciphername
string kdfname
string kdfoptions
uint32 number of keys N
string publickey1
string publickey2
...
string publickeyN
string encrypted, padded list of private keys
The private key can be protected using some form of password-based encryption.
In practice, private key are stored in ASCII-armored form such as[8][9]:
-----BEGIN OPENSSH PRIVATE KEY-----
b3BlbnNzaC1rZXktdjEAAAAACmFlczI1Ni1jdHIAAAAGYmNyeXB0AAAAGAAAABAYfZmqpq
tI4O0qtR1RG1XaAAAAZAAAAAEAAAAzAAAAC3NzaC1lZDI1NTE5AAAAINvEX77hMOphkw6S
FHXWOG8tdeteG4BvAICgIebo8t5OAAAAkF9t9ZjC2mbp8QT0uLaTSYhnfck69wkUb3scVE
fhkb+amCmNjaD65nDBLgHSYYtMEHoFAEy7Yn8oIUaezLRjovu1eTqUJf05kJERYHj9gcz9
7WhJI8JJVP9gth9U8ngu9KpR7li5uqlsYpPtUW2eAwtsDEsOapCtbFwOfy4wd0bZtTO864
r30CXlqRh54JyELQ==
-----END OPENSSH PRIVATE KEY-----
The private key can be encrypted using password-based encryption (currently using PBKDF2 with bcrypt).
This format is used in ~/.ssh/id_ed25519, etc.
Tip: Generate an OpenSSH private key
ssh-keygen -a 100 -t ed25519 -f ./id_ed25519
This generates a private key (./id_ed25519) and the corresponding public key file (./id_ed25519.pub).
Note: support for ASN.1 formats
OpenSSH can work with ASN1 fomats as well.
PKCS#1 (PEM):
ssh-keygen -a 100 -t rsa -f ./id_rsa -m pem
-----BEGIN RSA PRIVATE KEY-----
...
-----END RSA PRIVATE KEY-----
PKCS#8 (PEM):
ssh-keygen -a 100 -t rsa -f ./id_rsa -m pem
-----BEGIN PRIVATE KEY-----
...
-----END PRIVATE KEY-----
SSH private keys in PuTTY format
PuTTY uses its own format (.ppk files).
OpenSSH Key Revocation Lists
OpenSSH defines a Key Revocation Lists (KRL) format for representing revoked public keys and certificates.
The format is described in PROTOCOL.krl
OpenSSH signatures
OpenSSH 8.0 and above supports reusing your SSH private/public keys to sign (and verify the signature of) arbitrary files/data. This uses a dedicated signature format which is described in PROTOCOL.sshsig.
Format:
#define MAGIC_PREAMBLE "SSHSIG"
#define SIG_VERSION 0x01
byte[6] MAGIC_PREAMBLE
uint32 SIG_VERSION
string publickey
string namespace
string reserved
string hash_algorithm
string signature
Example of OpenSSH signature
-----BEGIN SSH SIGNATURE-----
U1NIU0lHAAAAAQAAADMAAAALc3NoLWVkMjU1MTkAAAAgJKxoLBJBivUPNTUJUSslQTt2hD
jozKvHarKeN8uYFqgAAAADZm9vAAAAAAAAAFMAAAALc3NoLWVkMjU1MTkAAABAKNC4IEbt
Tq0Fb56xhtuE1/lK9H9RZJfON4o6hE9R4ZGFX98gy0+fFJ/1d2/RxnZky0Y7GojwrZkrHT
FgCqVWAQ==
-----END SSH SIGNATURE-----
Tip: basic commands for SSH signatures
ssh-keygen -Y sign -f ~/.ssh/id_ed25519 -n myapp@mydomain.example.com file
ssh-keygen -Y verify -f ./signers -I john@example.com -n myapp@mydomain.example.com -f file.sig
The namespace is some form of application identifier. The ssh-keygen man page using “file” for file signing, “email” for email signing. You should use “your-application@your-domain” for your own application.
OpenPGP
PGP uses its own formats. PGP data are a sequence of packets taking one of two forms:
OpenPGP format:
Bit 7 -- always one
Bit 6 -- always one
Bits 5 to 0 -- Packet Type ID
Legacy format:
Bit 7 -- always one
Bit 6 -- always zero
Bits 5 to 2 -- Packet Type ID
Bits 1 to 0 -- length-type
PGP ASCII
The associated text format, PGP ASCII armor is similar to PEM with the following differences:
- it might include some legacy headers;
- it includes 0+ empty lines between the legacy headers and the main content;
- it might include a legacy CRC24 checksum.
Example: PGP ASCII messages
Example from RFC 9580"
-----BEGIN PGP MESSAGE-----
xEYGAQobIHZJX1AhiJD39eLuPBgiUU9wUA9VHYblySHkBONKU/usyxhsTwYJppfk
1S36bHIrDB8eJ8GKVnCPZSXsJ7rZrMkBy0p1AAAAAABXaGF0IHdlIG5lZWQgZnJv
bSB0aGUgZ3JvY2VyeSBzdG9yZToKCi0gdG9mdQotIHZlZ2V0YWJsZXMKLSBub29k
bGVzCsKYBgEbCgAAACkFgmOYo2MiIQbLGGxPBgmml+TVLfpscisMHx4nwYpWcI9l
JewnutmsyQAAAABpNiB2SV9QIYiQ9/Xi7jwYIlFPcFAPVR2G5ckh5ATjSlP7rCfQ
b7gKqPxbyxbhljGygHQPnqau1eBzrQD5QVplPEDnemrnfmkrpx0GmhCfokxYz9jj
FtCgazStmsuOXF9SFQE=
-----END PGP MESSAGE-----
Example with legacy headers from RFC9580:
-----BEGIN PGP MESSAGE-----
Comment: Encrypted using AES with 192-bit key
Comment: Session key: 27006DAE68E509022CE45A14E569E91001C2955...
Comment: Session key: ...AF8DFE194
wy8ECAThTKxHFTRZGKli3KNH4UP4AQQVhzLJ2va3FG8/pmpIPd/H/mdoVS5VBLLw
F9I+AdJ1Sw56PRYiKZjCvHg+2bnq02s33AJJoyBexBI4QKATFRkyez2gldJldRys
LVg77Mwwfgl2n/d572WciAM=
-----END PGP MESSAGE-----
JOSE ecosystem
JOSE (JSON Object Signing and Encryption) is a family of cryptographic formats based on JSON. This includes:
- JWS (JSON Web Signature), for authenticated data (either signed with a digital signature or protected by a MAC);
- JWE (JSON Web Encryption), for encrypted data (using either symmetric or public-key encryption);
- JWT (JSON Web Token), a text format for tokens (a JWT can be either a JWS, or a JWE);
- JWK (JSON Web Key), a JSON format for representing keys (secret keys, public keys, private keys);
- JWKS (JWK Set), a JSON format for representing a collection of keys (in JWK format).
Both JWS and JWE can exist in two serializations:
- compact serialization (a dot-delimited URL-safe string);
- JSON serialization.
Note: examples
Examples in this section are largely taken from the relevant RFCs.
For presentation purpose, whitespaces and newlines have been added to compact serialization: they are not present in the actual serialization.
Comments have been added to the JSON serialization even if JSON does not support comment: these comments are no present in the actual serialization.
JWS
A basic JWS is made of:
- a protected header;
- payload
- which can be an arbitrary binary content in general,
- which are a set of claims when the JWS is a JWT,
- a signature
- which is actually either a digital signature or a MAC, depending on the
algvalue, - which protects both the protected header and the payload.
- which is actually either a digital signature or a MAC, depending on the
The JSON serialization supports more features such as:
header, unprotected header,signatures, for including more than one signature (eg. with different signature algorithms) and/or MAC (eg. for different recipients).
JWS in itself does not support informations about the issuer, the audience, the validity period of the token, etc. All of this is covered by JWT.
Example: JWS
Example of KWS in compact serialization from RFC7517:
eyJ0eXAiOiJKV1QiLA0KICJhbGciOiJIUzI1NiJ9
.
eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogImh0dHA6Ly9leGFt
cGxlLmNvbS9pc19yb290Ijp0cnVlfQ
.
dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk
Corresponding flattened JSON serialization:
{
// {"typ":"JWT","alg":"HS256"}
"protected": "eyJ0eXAiOiJKV1QiLA0KICJhbGciOiJIUzI1NiJ9",
// {"iss":"joe","exp":1300819380,"http://example.com/is_root":true}
"payload": "eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogI
mh0dHA6Ly9leGFtcGxlLmNvbS9pc19yb290Ijp0cnVlfQ",
"signature": "dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk"
}
This example is protected using a MAC (alg":"HS256" for HMAC-SHA-256). This last example uses the application/jwt` media type. However, the JSON serialization is not allowed for JWT.
Example: JWS in general JSON serialization
Example from RFC7515:
{
// {"iss":"joe","exp":1300819380,"http://example.com/is_root":true}
"payload": "eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogI
mh0dHA6Ly9leGFtcGxlLmNvbS9pc19yb290Ijp0cnVlfQ",
"signatures": [
{
// {"alg":"RS256"}
"protected":"eyJhbGciOiJSUzI1NiJ9",
"header": {
"kid":"2010-12-29"
},
"signature":
"cC4hiUPoj9Eetdgtv3hF80EGrhuB__dzERat0XF9g2VtQgr9
PJbu3XOiZj5RZmh7AAuHIm4Bh-0Qc_lF5YKt_O8W2Fp5jujGb
ds9uJdbF9CUAr7t1dnZcAcQjbKBYNX4BAynRFdiuB--f_nZLg
rnbyTyWzO75vRK5h6xBArLIARNPvkSjtQBMHlb1L07Qe7K0Ga
rZRmB_eSN9383LcOLn6_dO--xi12jzDwusC-eOkHWEsqtFZES
c6BfI7noOPqvhJ1phCnvWh6IeYI2w9QOYEUipUTI8np6LbgGY
9Fs98rqVt5AXLIhWkWywlVmtVrBp0igcN_IoypGlUPQGe77Rw"
},
{
// {"alg":"ES256"}
"protected":"eyJhbGciOiJFUzI1NiJ9",
"header": {
"kid":"e9bc097a-ce51-4036-9562-d2ade882db0d"
},
"signature":
"DtEhU3ljbEg8L38VWAfUAqOyKAM6-Xx-F4GawxaepmXFCgfT
jDxw5djxLa8ISlSApmWQxfKTUJqPP3-Kg6NU1Q"
}
]
}
The authenticity of this payload can be verified using two different signatures:
- the first one is a RSA signature (
"alg":"RS256"for RSA Signature with SHA-256); - the second is a ECDSA signature (
"alg":"ES256"for ECDSA using P-256 and SHA-256).
Note: detached JWS payload
The payload of the JWS can be detached.
JWE
A basic JWE is made of:
- a protected header;
- a ciphertext (i.e. a payload encrypted with a secret key);
- an encrypted secret key aka CEK (content encryption key), which is used to encrypt/decrypt the payload;
- an initialization vector (IV), used to encrypt/decrypt the payload;
- an authentication tag.
Example: JWE
Example JWE in of compact serialization from RFC 7516:
eyJhbGciOiJSU0EtT0FFUCIsImVuYyI6IkEyNTZHQ00ifQ
.
OKOawDo13gRp2ojaHV7LFpZcgV7T6DVZKTyKOMTYUmKoTCVJRgckCL9kiMT03JGe
ipsEdY3mx_etLbbWSrFr05kLzcSr4qKAq7YN7e9jwQRb23nfa6c9d-StnImGyFDb
Sv04uVuxIp5Zms1gNxKKK2Da14B8S4rzVRltdYwam_lDp5XnZAYpQdb76FdIKLaV
mqgfwX7XWRxv2322i-vDxRfqNzo_tETKzpVLzfiwQyeyPGLBIO56YJ7eObdv0je8
1860ppamavo35UgoRdbYaBcoh9QcfylQr66oc6vFWXRcZ_ZT2LawVCWTIy3brGPi
6UklfCpIMfIjf7iGdXKHzg
.
48V1_ALb6US04U3b
.
5eym8TW_c8SuK0ltJ3rpYIzOeDQz7TALvtu6UG9oMo4vpzs9tX_EFShS8iB7j6ji
SdiwkIr3ajwQzaBtQD_A
.
XFBoMYUZodetZdvTiFvSkQ
Corresponding flattened JSON serialization:
{
// {"alg":"RSA-OAEP","enc":"A256GCM"}
"protected": "eyJhbGciOiJSU0EtT0FFUCIsImVuYyI6IkEyNTZHQ00ifQ",
"encrypted_key": "OKOawDo13gRp2ojaHV7LFpZcgV7T6DVZKTyKOMTY...",
"iv": "48V1_ALb6US04U3b",
"ciphertext": "5eym8TW_c8SuK0ltJ3rpYIzOeDQz7TALvtu6UG9oMo...",
"tag": "XFBoMYUZodetZdvTiFvSkQ"
}
In this example, the payload is encrypted using AES-256-GCM ("enc":"A256GCM"). The encryption key (CEK) can be obtained by decrypting the encrypted_key with RSA-OAEP.
Example: JWE in general JSON serialization
Example of general JSON serialization from RFC 7516:
{
// {"enc":"A128CBC-HS256"}
"protected": "eyJlbmMiOiJBMTI4Q0JDLUhTMjU2In0",
"unprotected":{
"jku":"https://server.example.com/keys.jwks"
},
"recipients":[
{
"header": {
"alg":"RSA1_5",
"kid":"2011-04-29"
},
"encrypted_key":
"UGhIOguC7IuEvf_NPVaXsGMoLOmwvc1GyqlIKOK1nN94nHPoltGRhWhw7Zx0-
kFm1NJn8LE9XShH59_i8J0PH5ZZyNfGy2xGdULU7sHNF6Gp2vPLgNZ__deLKx
GHZ7PcHALUzoOegEI-8E66jX2E4zyJKx-YxzZIItRzC5hlRirb6Y5Cl_p-ko3
YvkkysZIFNPccxRU7qve1WYPxqbb2Yw8kZqa2rMWI5ng8OtvzlV7elprCbuPh
cCdZ6XDP0_F8rkXds2vE4X-ncOIM8hAYHHi29NX0mcKiRaD0-D-ljQTP-cFPg
wCp6X-nZZd9OHBv-B3oWh2TbqmScqXMR4gp_A"
},
{
"header": {
"alg":"A128KW",
"kid":"7"
},
"encrypted_key": "6KB707dM9YTIgHtLvtgWQ8mKwboJW3of9locizkDTHzBC2IlrT1oOQ"
}
],
"iv": "AxY8DCtDaGlsbGljb3RoZQ",
"ciphertext": "KDlTtXchhZTGufMYmOYGS4HffxPSUrfmqCHXaI9wOGY",
"tag": "Mz-VPPyU4RlcuYv1IwIvzw"
}
In this second example, the payload is encrypted with AES-128-CBC with HMAC-SHA-256 for integrity protection ("enc":"A128CBC-HS256"). The encryption key (CEK) is encrypted with two different encryption algorithms and public keys: RSA encryption (RSAES-PKCS1-v1_5, "alg":"RSA1_5"), and AES Key Wrap with (A128KW). With any of the two private keys (either 2011-04-29 or 7), the CEK and then the payload can be decrypted.
The general JSON representation can use the following additional fields:
unprotected, unprotected header fields;aad, optional additional data (which is authenticated but not encrypted);recipients, when more than one recipient is used (with a different KEK for each recipient).
Warning: authenticity
JWE does not provide authenticity in general[^jwe-authenticity]. If authenticity is required, you generally need nesting JWE and JWS. The suggested order is sign-then-encrypt (i.e. a JWS in a JWE).
In some very specific cases, JWEs can provide authenticity (as they are based on AEADs) but you should usually assume that they do not in general.
JWT
JWT extends JWS and JWE by providing a semantic to the body (payload of the JWS or plaintext of the JWE) of the JOSE object. The core claims of JWT can be used to convey basic informations such as:
- the issuer, subject and audience of the token;
- the validity period of the token;
- the issuance time of the token;
- a token identifier.
A JWT can be:
- a JWS (signed using digital signature or MAC);
- a JWE (encrypted);
- a nested JWT (usually a JWS in a JWE);
- an unsigned JWS (deprecated).
A lot of other standard claims are defined in additional specifications.
This format can be used for a lot of applications such as:
- short lived token (eg. access tokens);
- proof of possession of a private key;
- signed messages.
Example: JWT claim set
Example of headers and claims from RFC9068:
{
"typ":"at+JWT",
"alg":"RS256",
"kid":"RjEwOwOA"
}
{
// Core JWT claims:
"iss": "https://authorization-server.example.com/",
"sub": "5ba552d67",
"aud": "https://rs.example.com/",
"exp": 1639528912,
"iat": 1618354090,
"jti" : "dbe39bf3a3ba4238a513f51d6e1691c4",
// Claims fom at+JWT:
"client_id": "s6BhdRkqt3",
"scope": "openid profile reademail"
}
JWK
The JWK (JSON Web Key) format can be used to represent secret, public or private keys as JSON data:
| Type | kty | Public fields | Private/Secret Fields | Examples |
|---|---|---|---|---|
| RSA key | RSA | n, e | d, p, q, dp, dq, qi, oth | |
| Elliptic curve key | EC | crv, x, y | d | P-256, P-384, P-521 |
| Secret key | oct | - | k | |
| Octet string key pairs | OKP | crv, x | d | Ed25519, Ed448 (signature), X25519, X448 (ECDH-ES) |
| Algorithm Key Pair | AKP | alg, pub | priv | ML-DSA-, FN-DSA-, SLH-DSA* |
JWK supports attaching some extra information to the actual key such as (see the IANA registry for more details):
- a key identifier (
kid); - key usage (
use); - supported key operations (
key_ops); - an associated X.509 certificate or certificate chain (
x5u,x5c); - an associated X.509 thumbprint (
x5t,x5t#S256); - status information (
iat,exp,revoked).
There is no explicit support for encrypted keys. A JWK could be encrypted in a JWE (for example using password-based encryption with PBES2[10]).
Example: JWK
Example of secret key from RFC7517:
{
"kty":"oct",
"alg":"A128KW",
"k":"GawgguFyGrWKav7AX4VKUg"
}
Example of public key from RFC7517:
{
"kty":"EC",
"crv":"P-256",
"x":"MKBCTNIcKUSDii11ySs3526iDZ8AiTo7Tu6KPAqv7D4",
"y":"4Etl6SRW2YiLUrN5vfvVHuhp7x8PxltmWWlbbM4IFyM",
"use":"enc",
"kid":"1"
}
Example of corresponding private key from RFC7517:
{
"kty":"EC",
"crv":"P-256",
"x":"MKBCTNIcKUSDii11ySs3526iDZ8AiTo7Tu6KPAqv7D4",
"y":"4Etl6SRW2YiLUrN5vfvVHuhp7x8PxltmWWlbbM4IFyM",
"d":"870MB6gfuTJ4HtUnUvYMyJpr5eUZNP4Bk43bVdj3eAE",
"use":"enc",
"kid":"1"
}
Example of secret key from RFC7517:
{
"kty":"oct",
"alg":"A128KW",
"k":"GawgguFyGrWKav7AX4VKUg"
}
Note: JWK Thumbprint
Defined in RFC7638 a JWK thumbprint is a thumbprint (cryptographic hash) of a public (or secret) key in JWK format. Only the mandatory fields are included in the hash and they are serialized in lexicographic order.
The hash of a private key is defined as the hsh of its public key.
RFC 9278 defines a URI prefix for JWK thumbprints. Example:
urn:ietf:params:oauth:jwk-thumbprint:sha-256:NzbLsXh8uDCcd-6MNwXF4W_7noWXFZAfHkxZsRGC9Xs
JWKS
A JWKS (JWK set) is a collection of keys in JWK format.
Example: JWK set
Example from RFC7517:
{
"keys": [
{
"kty":"EC",
"crv":"P-256",
"x":"MKBCTNIcKUSDii11ySs3526iDZ8AiTo7Tu6KPAqv7D4",
"y":"4Etl6SRW2YiLUrN5vfvVHuhp7x8PxltmWWlbbM4IFyM",
"use":"enc",
"kid":"1"
},
{
"kty":"RSA",
"n": "0vx7agoebGcQSuuPiLJXZptN9nndrQmbXEps2aiAFbWhM78LhWx
4cbbfAAtVT86zwu1RK7aPFFxuhDR1L6tSoc_BJECPebWKRXjBZCiFV4n3oknjhMs
tn64tZ_2W-5JsGY4Hc5n9yBXArwl93lqt7_RN5w6Cf0h4QyQ5v-65YGjQR0_FDW2
QvzqY368QQMicAtaSqzs8KJZgnYb9c7d0zgdAZHzu6qMQvRL5hajrn1n91CbOpbI
SD08qNLyrdkt-bFTWhAI4vMQFh6WeZu0fM4lFd2NcRwr3XPksINHaQ-G_xBniIqb
w0Ls1jF44-csFCur-kEgU8awapJzKnqDKgw",
"e":"AQAB",
"alg":"RS256",
"kid":"2011-04-29"
}
]
}
Example: usage in Oauth2 and OpenID Connect
JWKS are widely used in OAuth2 (and OpenID Connect) to distribute the public-keys (usually for digital signatures) of the authorization server (AS). The URI of the JWKS document is indicated in the jwks_uri AS metadata parameter (a https: URI).
SD-JWT
Selectively Disclosable JWT (SD-JWT) extends the JWT format for selective disclosure of claims. Instead of disclosing all the claims in the JWT, the holder can chose which claims it wants to disclose to a verifier. In order to achieve this, some of the claims (and/or some parts of claims) are not directly included in the JWT payload (selectively disclosable claims):
- The JWT only includes their (salted) hashes (digest).
- The holder of the token can choose to disclose some of these claims to a verifier/audience by revealing the claim value (and the associated hash) (disclosure).
A SD-JWT is a tilde-separated list of elements:
- the first is the JWT (with some selectively disclosable claims);
- then any number of disclosures of claims;
- and in final position an optional key binding JWT (KB-JWT), a proof-of-possession of a private key owned by the holder of the JWT (SD-JWT+KB)[11].
SD-JWT (without KB-JWT):
<Issuer-signed JWT>~<Disclosure1>~<Disclosure2>~...~<DisclosureN>~
SD-JWT+KB (with KB-JWT):
<Issuer-signed JWT>~<Disclosure1>~<Disclosure2>~...~<DisclosureN>~<KB-JWT>
SD-JWT+KB without disclosure:
<Issuer-signed JWT>~<KB-JWT>
Example: SD-JWT
Example with two disclosures (and no key binding):
eyJhbGciOiAiRVMyNTYiLCAidHlwIjogImV4YW1wbGUrc2Qtand0In0
.
eyJfc2QiOiBb
IkM5aW5wNllvUmFFWFI0Mjd6WUpQN1FyazFXSF84YmR3T0FfWVVyVW5HUVUiLCAiS3Vl
dDF5QWEwSElRdlluT1ZkNTloY1ZpTzlVZzZKMmtTZnFZUkJlb3d2RSIsICJNTWxkT0ZG
ekIyZDB1bWxtcFRJYUdlcmhXZFVfUHBZZkx2S2hoX2ZfOWFZIiwgIlg2WkFZT0lJMnZQ
TjQwVjd4RXhad1Z3ejd5Um1MTmNWd3Q1REw4Ukx2NGciLCAiWTM0em1JbzBRTExPdGRN
cFhHd2pCZ0x2cjE3eUVoaFlUMEZHb2ZSLWFJRSIsICJmeUdwMFdUd3dQdjJKRFFsbjFs
U2lhZW9iWnNNV0ExMGJRNTk4OS05RFRzIiwgIm9tbUZBaWNWVDhMR0hDQjB1eXd4N2ZZ
dW8zTUhZS08xNWN6LVJaRVlNNVEiLCAiczBCS1lzTFd4UVFlVTh0VmxsdE03TUtzSVJU
ckVJYTFQa0ptcXhCQmY1VSJdLCAiaXNzIjogImh0dHBzOi8vaXNzdWVyLmV4YW1wbGUu
Y29tIiwgImlhdCI6IDE2ODMwMDAwMDAsICJleHAiOiAxODgzMDAwMDAwLCAiYWRkcmVz
cyI6IHsiX3NkIjogWyI2YVVoelloWjdTSjFrVm1hZ1FBTzN1MkVUTjJDQzFhSGhlWnBL
bmFGMF9FIiwgIkF6TGxGb2JrSjJ4aWF1cFJFUHlvSnotOS1OU2xkQjZDZ2pyN2ZVeW9I
emciLCAiUHp6Y1Z1MHFiTXVCR1NqdWxmZXd6a2VzRDl6dXRPRXhuNUVXTndrclEtayIs
ICJiMkRrdzBqY0lGOXJHZzhfUEY4WmN2bmNXN3p3Wmo1cnlCV3ZYZnJwemVrIiwgImNQ
WUpISVo4VnUtZjlDQ3lWdWIyVWZnRWs4anZ2WGV6d0sxcF9KbmVlWFEiLCAiZ2xUM2hy
U1U3ZlNXZ3dGNVVEWm1Xd0JUdzMyZ25VbGRJaGk4aEdWQ2FWNCIsICJydkpkNmlxNlQ1
ZWptc0JNb0d3dU5YaDlxQUFGQVRBY2k0MG9pZEVlVnNBIiwgInVOSG9XWWhYc1poVkpD
TkUyRHF5LXpxdDd0NjlnSkt5NVFhRnY3R3JNWDQiXX0sICJfc2RfYWxnIjogInNoYS0y
NTYifQ
.
EOZa2YqK8j4i7cqBDkfPcTMaFsgPwcx3aYJkFoMfvV46LxL-PPqrWsIyNukB4
x8Y2LT31eIHDc4Wg4XNzaqu4w
~
WyJHMDJOU3JRZmpGWFE3SW8wOXN5YWpBIiwgInJlZ2lvbiIsICJcdTZlMmZcdTUzM2EiXQ
~
WyJsa2x4RjVqTVlsR1RQVW92TU5JdkNBIiwgImNvdW50cnkiLCAiSlAiXQ
~
Example with key binding (SD-JWT+KB):
eyJhbGciOiAiRVMyNTYiLCAidHlwIjogImV4YW1wbGUrc2Qtand0In0
.
eyJAY29udGV4
dCI6IFsiaHR0cHM6Ly93d3cudzMub3JnLzIwMTgvY3JlZGVudGlhbHMvdjEiLCAiaHR0
cHM6Ly93M2lkLm9yZy92YWNjaW5hdGlvbi92MSJdLCAidHlwZSI6IFsiVmVyaWZpYWJs
ZUNyZWRlbnRpYWwiLCAiVmFjY2luYXRpb25DZXJ0aWZpY2F0ZSJdLCAiaXNzdWVyIjog
Imh0dHBzOi8vZXhhbXBsZS5jb20vaXNzdWVyIiwgImlzc3VhbmNlRGF0ZSI6ICIyMDIz
LTAyLTA5VDExOjAxOjU5WiIsICJleHBpcmF0aW9uRGF0ZSI6ICIyMDI4LTAyLTA4VDEx
OjAxOjU5WiIsICJuYW1lIjogIkNPVklELTE5IFZhY2NpbmF0aW9uIENlcnRpZmljYXRl
IiwgImRlc2NyaXB0aW9uIjogIkNPVklELTE5IFZhY2NpbmF0aW9uIENlcnRpZmljYXRl
IiwgImNyZWRlbnRpYWxTdWJqZWN0IjogeyJfc2QiOiBbIjFWX0stOGxEUThpRlhCRlhi
Wlk5ZWhxUjRIYWJXQ2k1VDB5Ykl6WlBld3ciLCAiSnpqTGd0UDI5ZFAtQjN0ZDEyUDY3
NGdGbUsyenk4MUhNdEJnZjZDSk5XZyIsICJSMmZHYmZBMDdaX1lsa3FtTlp5bWExeHl5
eDFYc3RJaVM2QjFZYmwySlo0IiwgIlRDbXpybDdLMmdldl9kdTdwY01JeXpSTEhwLVll
Zy1GbF9jeHRyVXZQeGciLCAiVjdrSkJMSzc4VG1WRE9tcmZKN1p1VVBIdUtfMmNjN3la
UmE0cVYxdHh3TSIsICJiMGVVc3ZHUC1PRERkRm9ZNE5semxYYzN0RHNsV0p0Q0pGNzVO
dzhPal9nIiwgInpKS19lU01YandNOGRYbU1aTG5JOEZHTTA4ekozX3ViR2VFTUotNVRC
eTAiXSwgInZhY2NpbmUiOiB7Il9zZCI6IFsiMWNGNWhMd2toTU5JYXFmV0pyWEk3Tk1X
ZWRMLTlmNlkyUEE1MnlQalNaSSIsICJIaXk2V1d1ZUxENWJuMTYyOTh0UHY3R1hobWxk
TURPVG5CaS1DWmJwaE5vIiwgIkxiMDI3cTY5MWpYWGwtakM3M3ZpOGViT2o5c214M0Mt
X29nN2dBNFRCUUUiXSwgInR5cGUiOiAiVmFjY2luZSJ9LCAicmVjaXBpZW50IjogeyJf
c2QiOiBbIjFsU1FCTlkyNHEwVGg2T0d6dGhxLTctNGw2Y0FheHJZWE9HWnBlV19sbkEi
LCAiM256THE4MU0yb04wNndkdjFzaEh2T0VKVnhaNUtMbWREa0hFREpBQldFSSIsICJQ
bjFzV2kwNkc0TEpybm4tX1JUMFJiTV9IVGR4blBKUXVYMmZ6V3ZfSk9VIiwgImxGOXV6
ZHN3N0hwbEdMYzcxNFRyNFdPN01HSnphN3R0N1FGbGVDWDRJdHciXSwgInR5cGUiOiAi
VmFjY2luZVJlY2lwaWVudCJ9LCAidHlwZSI6ICJWYWNjaW5hdGlvbkV2ZW50In0sICJf
c2RfYWxnIjogInNoYS0yNTYiLCAiY25mIjogeyJqd2siOiB7Imt0eSI6ICJFQyIsICJj
cnYiOiAiUC0yNTYiLCAieCI6ICJUQ0FFUjE5WnZ1M09IRjRqNFc0dmZTVm9ISVAxSUxp
bERsczd2Q2VHZW1jIiwgInkiOiAiWnhqaVdXYlpNUUdIVldLVlE0aGJTSWlyc1ZmdWVj
Q0U2dDRqVDlGMkhaUSJ9fX0
.
OZomvwO8iw4db89MYCeeomBVStXkT6u7G7FkicPWZnd2_hGgr0l_u1NHgPVocuOt-m32
Uu6kwtPmYFxKk0AOeA
~
WyJQYzMzSk0yTGNoY1VfbEhnZ3ZfdWZRIiwgIm9yZGVyIiwgIjMvMyJd
~
WyJBSngtMDk1VlBycFR0TjRRTU9xUk9BIiwgImRhdGVPZlZhY2NpbmF0aW9uIiwgIjIw
MjEtMDYtMjNUMTM6NDA6MTJaIl0
~
WyIyR0xDNDJzS1F2ZUNmR2ZyeU5STjl3IiwgImF0Y0NvZGUiLCAiSjA3QlgwMyJd
~
WyJlbHVWNU9nM2dTTklJOEVZbnN4QV9BIiwgIm1lZGljaW5hbFByb2R1Y3ROYW1lIiwgI
kNPVklELTE5IFZhY2NpbmUgTW9kZXJuYSJd
~
eyJhbGciOiAiRVMyNTYiLCAidHlwIjogImtiK2p3dCJ9
.
eyJub25jZSI6ICIxMjM0NTY3ODkwIiwgImF1ZCI6ICJodHRwczovL3ZlcmlmaWVyLmV4Y
W1wbGUub3JnIiwgImlhdCI6IDE3NDg1MzcyNDQsICJzZF9oYXNoIjogIklvV1VIOTFsbG
YzWEVybDQyYlEzc3hfNTNWMW8xdWpDejA4aERxSEs3RGsifQ
.
n0vzyIwCFMDVauEaeJIWEKZZchxXMpXTQewHgAkARbOSZxB09IbXXtHfpoGqO_BtNFN2l
ShJEIQBGyc-XpHigA
As an alternative to the compact serialization, the JOSE JWS JSON serialization can be used. Two new unprotected header parameters (disclosures and kb_jwt) are defined to transport the disclosurs and the key binding JWT.
Example: SD-JWT in JSON serialization
{
// {"alg": "ES256", "typ": "example+sd-jwt"}
"protected": "eyJhbGciOiAiRVMyNTYiLCAidHlwIjogImV4YW1wbGUrc2Qtand0In0",
"payload": "
eyJAY29udGV4
dCI6IFsiaHR0cHM6Ly93d3cudzMub3JnLzIwMTgvY3JlZGVudGlhbHMvdjEiLCAiaHR0
cHM6Ly93M2lkLm9yZy92YWNjaW5hdGlvbi92MSJdLCAidHlwZSI6IFsiVmVyaWZpYWJs
ZUNyZWRlbnRpYWwiLCAiVmFjY2luYXRpb25DZXJ0aWZpY2F0ZSJdLCAiaXNzdWVyIjog
Imh0dHBzOi8vZXhhbXBsZS5jb20vaXNzdWVyIiwgImlzc3VhbmNlRGF0ZSI6ICIyMDIz
LTAyLTA5VDExOjAxOjU5WiIsICJleHBpcmF0aW9uRGF0ZSI6ICIyMDI4LTAyLTA4VDEx
OjAxOjU5WiIsICJuYW1lIjogIkNPVklELTE5IFZhY2NpbmF0aW9uIENlcnRpZmljYXRl
IiwgImRlc2NyaXB0aW9uIjogIkNPVklELTE5IFZhY2NpbmF0aW9uIENlcnRpZmljYXRl
IiwgImNyZWRlbnRpYWxTdWJqZWN0IjogeyJfc2QiOiBbIjFWX0stOGxEUThpRlhCRlhi
Wlk5ZWhxUjRIYWJXQ2k1VDB5Ykl6WlBld3ciLCAiSnpqTGd0UDI5ZFAtQjN0ZDEyUDY3
NGdGbUsyenk4MUhNdEJnZjZDSk5XZyIsICJSMmZHYmZBMDdaX1lsa3FtTlp5bWExeHl5
eDFYc3RJaVM2QjFZYmwySlo0IiwgIlRDbXpybDdLMmdldl9kdTdwY01JeXpSTEhwLVll
Zy1GbF9jeHRyVXZQeGciLCAiVjdrSkJMSzc4VG1WRE9tcmZKN1p1VVBIdUtfMmNjN3la
UmE0cVYxdHh3TSIsICJiMGVVc3ZHUC1PRERkRm9ZNE5semxYYzN0RHNsV0p0Q0pGNzVO
dzhPal9nIiwgInpKS19lU01YandNOGRYbU1aTG5JOEZHTTA4ekozX3ViR2VFTUotNVRC
eTAiXSwgInZhY2NpbmUiOiB7Il9zZCI6IFsiMWNGNWhMd2toTU5JYXFmV0pyWEk3Tk1X
ZWRMLTlmNlkyUEE1MnlQalNaSSIsICJIaXk2V1d1ZUxENWJuMTYyOTh0UHY3R1hobWxk
TURPVG5CaS1DWmJwaE5vIiwgIkxiMDI3cTY5MWpYWGwtakM3M3ZpOGViT2o5c214M0Mt
X29nN2dBNFRCUUUiXSwgInR5cGUiOiAiVmFjY2luZSJ9LCAicmVjaXBpZW50IjogeyJf
c2QiOiBbIjFsU1FCTlkyNHEwVGg2T0d6dGhxLTctNGw2Y0FheHJZWE9HWnBlV19sbkEi
LCAiM256THE4MU0yb04wNndkdjFzaEh2T0VKVnhaNUtMbWREa0hFREpBQldFSSIsICJQ
bjFzV2kwNkc0TEpybm4tX1JUMFJiTV9IVGR4blBKUXVYMmZ6V3ZfSk9VIiwgImxGOXV6
ZHN3N0hwbEdMYzcxNFRyNFdPN01HSnphN3R0N1FGbGVDWDRJdHciXSwgInR5cGUiOiAi
VmFjY2luZVJlY2lwaWVudCJ9LCAidHlwZSI6ICJWYWNjaW5hdGlvbkV2ZW50In0sICJf
c2RfYWxnIjogInNoYS0yNTYiLCAiY25mIjogeyJqd2siOiB7Imt0eSI6ICJFQyIsICJj
cnYiOiAiUC0yNTYiLCAieCI6ICJUQ0FFUjE5WnZ1M09IRjRqNFc0dmZTVm9ISVAxSUxp
bERsczd2Q2VHZW1jIiwgInkiOiAiWnhqaVdXYlpNUUdIVldLVlE0aGJTSWlyc1ZmdWVj
Q0U2dDRqVDlGMkhaUSJ9fX0",
"signature":
"OZomvwO8iw4db89MYCeeomBVStXkT6u7G7FkicPWZnd2_hGgr0l_u1NHgPVocuOt-m32
Uu6kwtPmYFxKk0AOeA",
"header": {
"disclosures": [
// ["Pc33JM2LchcU_lHggv_ufQ", "order", "3/3"]
"WyJQYzMzSk0yTGNoY1VfbEhnZ3ZfdWZRIiwgIm9yZGVyIiwgIjMvMyJd",
// ["AJx-095VPrpTtN4QMOqROA", "dateOfVaccination", "2021-06-23T13:40:12Z"]
"WyJBSngtMDk1VlBycFR0TjRRTU9xUk9BIiwgImRhdGVPZlZhY2NpbmF0aW9uIiwg
IjIwMjEtMDYtMjNUMTM6NDA6MTJaIl0"
// ["2GLC42sKQveCfGfryNRN9w", "atcCode", "J07BX03"]
"WyIyR0xDNDJzS1F2ZUNmR2ZyeU5STjl3IiwgImF0Y0NvZGUiLCAiSjA3QlgwMyJd",
// ["eluV5Og3gSNII8EYnsxA_A", "medicinalProductName", "COVID-19 Vaccine Moderna"]
"WyJlbHVWNU9nM2dTTklJOEVZbnN4QV9BIiwgIm1lZGljaW5hbFByb2R1Y3ROYW1lIi
wgIkNPVklELTE5IFZhY2NpbmUgTW9kZXJuYSJd"
],
// {"alg": "ES256", "typ": "kb+jwt"}
// {"nonce": "1234567890", "aud": "https://verifier.example.org",
// "iat": 1748537244, "sd_hash": "IoWUH91llf3XErl42bQ3sx_53V1o1ujCz08hDqHK7Dk"}
"kb_jwt":
"
eyJhbGciOiAiRVMyNTYiLCAidHlwIjogImtiK2p3dCJ9
.
eyJub25jZSI6ICIxMjM0NTY3ODkwIiwgImF1ZCI6ICJodHRwczovL3ZlcmlmaWVyLmV4Y
W1wbGUub3JnIiwgImlhdCI6IDE3NDg1MzcyNDQsICJzZF9oYXNoIjogIklvV1VIOTFsbG
YzWEVybDQyYlEzc3hfNTNWMW8xdWpDejA4aERxSEs3RGsifQ
.
n0vzyIwCFMDVauEaeJIWEKZZchxXMpXTQewHgAkARbOSZxB09IbXXtHfpoGqO_BtNFN2l
ShJEIQBGyc-XpHigA
"
}
}
COSE ecosystem
The COSE standards are similar to the JOSE ones but are based on CBOR (Concise Binary Object Representation)[12] instead of JSON:
- signed messages (digital signature or MAC);
- encrypted messages;
- CWT (CBOR Web Token) for tokens (similar to JWTs);
- COSE key, a format CBOR keys,
- COSE key set, a format for collections of keys (used in particular in WebAuthn);
- COSE key thumbprint, RFC9679.
COSE is not a direct port of the JOSE/JSON structures into CBOR. Some differences:
Examples in this section are taken from RFC9052, RFC8392 and RFC9679.
Note: some applications
- WebAuthn uses COSE key set but not COSE itself.
- COSE is used for the European Electronic Health Certificates which were used in the COVID-19 pandemic.
- OSCORE uses COSE to encrypt/protect CoAP messages.
Relevant structures and tags:
| Tag | Structure | Description |
|---|---|---|
| 16 | COSE_Encrypt0 | COSE Single Recipient Encrypted Data Object |
| 17 | COSE_Mac0 | COSE Mac w/o Recipients Object |
| 18 | COSE_Sign1 | COSE Single Signer Data Object |
| 19 | COSE_Countersignature | COSE standalone V2 countersignature |
| 61 | CBOR Web Token (CWT) | CBOR Web Token (CWT) |
| 96 | COSE_Encrypt | COSE Encrypted Data Object |
| 97 | COSE_Mac | COSE MACed Data Object |
| 98 | COSE_Sign | COSE Signed Data Object |
| - | COSE_Key | COSE Key |
| - | COSE_KeySet | COSE KeySet |
Signed COSE message
Headers = (
protected : empty_or_serialized_map,
unprotected : header_map
)
COSE_Sign = [
Headers,
payload : bstr / nil,
signatures : [+ COSE_Signature]
]
COSE_Signature = [
Headers,
signature : bstr
]
COSE_Sign1 = [
Headers,
payload : bstr / nil,
signature : bstr
]
COSE_Mac = [
Headers,
payload : bstr / nil,
tag : bstr,
recipients : [+COSE_recipient]
]
COSE_recipient = [
Headers,
ciphertext : bstr / nil,
? recipients : [+COSE_recipient]
]
COSE_Mac0 = [
Headers,
payload : bstr / nil,
tag : bstr,
]
Note: detached payload
The payload can be transported separately/implicit.
Example: COSE_Sign
Example of tagged COSE_Sign (in CBOR Diagnostic Notation[14]):
98(
[
/ protected / h'',
/ unprotected / {},
/ payload / 'This is the content.',
/ signatures / [
[
/ protected h'a10126' / << {
/ alg / 1:-7 / ECDSA 256 /
} >>,
/ unprotected / {
/ kid / 4:'11'
},
/ signature / h'e2aeafd40d69d19dfe6e52077c5d7ff4e408282cbefb
5d06cbf414af2e19d982ac45ac98b8544c908b4507de1e90b717c3d34816fe926a2b
98f53afd2fa0f30a'
]
]
]
)
Encrypted COSE message
Definitions:
COSE_Encrypt0 = [
Headers,
ciphertext : bstr / nil,
]
COSE_Encrypt = [
Headers,
ciphertext : bstr / nil,
recipients : [+COSE_recipient]
]
Note: detached content
The ciphertext can be transported separately.
Example: Encrypted COSE
16(
[
/ protected h'a1010a' / << {
/ alg / 1:10 / AES-CCM-16-64-128 /
} >> ,
/ unprotected / {
/ iv / 5:h'89f52f65a1c580933b5261a78c'
},
/ ciphertext / h'5974e1b99a3a4cc09a659aa2e9e7fff161d38ce71cb45ce
460ffb569'
]
)
COSE Key
Definition:
COSE_Key = {
1 => tstr / int, ; kty
? 2 => bstr, ; kid
? 3 => tstr / int, ; alg
? 4 => [+ (tstr / int) ], ; key_ops
? 5 => bstr, ; Base IV
* label => values
}
COSE_KeySet = [+COSE_Key]
Example: COSE_Key
Example of public key in COSE_Key format:
[
{
-1:1,
-2:h'65eda5a12577c2bae829437fe338701a10aaa375e1bb5b5de108de439c0
8551d',
-3:h'1e52ed75701163f7f9e40ddf9f341b3dc9ba860af7e0ca7ca7e9eecd008
4d19c',
1:2,
2:'meriadoc.brandybuck@buckland.example'
}
]
Corresponding private key in COSE_Key format:
{
1:2,
2:'meriadoc.brandybuck@buckland.example',
-1:1,
-2:h'65eda5a12577c2bae829437fe338701a10aaa375e1bb5b5de108de439c0
8551d',
-3:h'1e52ed75701163f7f9e40ddf9f341b3dc9ba860af7e0ca7ca7e9eecd008
4d19c',
-4:h'aff907c99f9ad3aae6c4cdf21122bce2bd68b5283e6907154ad911840fa
208cf'
}
Note: COSE key thumbprint
COSE key thumbprint (CKT) are similar to JWK thumbprints.
Example of COSE key thumbprint (CKT) represented as a URI:
urn:ietf:params:oauth:ckt:SWvYr63zB-WwjGSwQhv53AFSijRKQ72oj63RZp2iU-w
CWT
A CWT is a COSE (signed, MACed, encrypted) message whose payload is a map of claims.
Example: CWT
Example of CWT (signed, COSE_Sign1):
18(
[
/ protected / << {
/ alg / 1: -7 / ECDSA 256 /
} >>,
/ unprotected / {
/ kid / 4: h'4173796d6d657472696345434453413
23536' / 'AsymmetricECDSA256' /
},
/ payload / << {
/ iss / 1: "coap://as.example.com",
/ sub / 2: "erikw",
/ aud / 3: "coap://light.example.com",
/ exp / 4: 1444064944,
/ nbf / 5: 1443944944,
/ iat / 6: 1443944944,
/ cti / 7: h'0b71'
} >>,
/ signature / h'5427c1ff28d23fbad1f29c4c7c6a555e601d6fa29f
9179bc3d7438bacaca5acd08c8d4d4f96131680c42
9a01f85951ecee743a52b9b63632c57209120e1c9e
30'
]
)
COSE countersignatures
RFC9338 defines COSE countersignatures (v2)[15]. A countersignature is an additional signature on top of a signed document. This (or these) additional signature(s) can be attached to the main signature in the unprotected headers (using label 11 or 12).
| Label | Type | Content | Description |
|---|---|---|---|
| 11 | Countersignature (v2) | COSE_Countersignature / [+ COSE_Countersignature ] | Full countersignature |
| 12 | Countersignature0 (v2) | bstr | Abbreviated countersignature |
Example: COSE countersignatures
Example of document with COSE countersignature:
98(
[
/ protected / h'',
/ unprotected / {
/ countersign / 11:[
/ protected h'a10126' / << {
/ alg / 1:-7 / ECDSA 256 /
} >>,
/ unprotected / {
/ kid / 4: '11'
},
/ signature / h'5ac05e289d5d0e1b0a7f048a5d2b643813ded50bc9e4
9220f4f7278f85f19d4a77d655c9d3b51e805a74b099e1e085aacd97fc29d72f887e
8802bb6650cceb2c'
]
},
/ payload / 'This is the content.',
/ signatures / [
[
/ protected h'a10126' / << {
/ alg / 1:-7 / ECDSA 256 /
} >>,
/ unprotected / {
/ kid / 4: '11'
},
/ signature / h'e2aeafd40d69d19dfe6e52077c5d7ff4e408282cbefb
5d06cbf414af2e19d982ac45ac98b8544c908b4507de1e90b717c3d34816fe926a2b
98f53afd2fa0f30a'
]
]
]
)
PASETO ecosystem
PASETO
PASETO (Platform-Agnostic Security Tokens) is a token format with a much simpler design compared to JOSE and COSE.
It supports two kinds of tokens:
- encrypted with symmetric authenticated encryption with additional data (AEAD) (
local); - signed with a public-key digital signature (no encryption) (
public).
Format:
v{version}.{type}.{data}
v{version}.{type}.{data}.{footer}
Example: PASETO
Example with digital signature (newlines added for presentation):
v2
.
public
.
eyJleHAiOiIyMDM5LTAxLTAxVDAwOjAwOjAwKzAwOjAwIiwiZGF0YSI6In
RoaXMgaXMgYSBzaWduZWQgbWVzc2FnZSJ91gC7-jCWsN3mv4uJaZxZp0bt
LJgcyVwL-svJD7f4IHyGteKe3HTLjHYTGHI1MtCqJ-ESDLNoE7otkIzamF
skCA
Example with AEAD (newlines added for presentation):
v2
.
local
.
QAxIpVe-ECVNI1z4xQbm_qQYomyT3h8FtV8bxkz8pBJWkT8f7HtlOpbr
oPDEZUKop_vaglyp76CzYy375cHmKCW8e1CCkV0Lflu4GTDyXMqQdpZM
M1E6OaoQW27gaRSvWBrR3IgbFIa0AkuUFw
.
UGFyYWdvbiBJbml0aWF0aXZlIEVudGVycHJpc2Vz
This example inclue a footer which plaintext but is protected by the AEAD.
PASERK
PASERK (Platform-Agnostic Serialized Keys) is a simple format for representing keys. It supports:
- key references (identifiers);
- secret keys;
- public keys.
Format:
k{version}.{type}.{data}
Example: PASERK
k4.lid.iVtYQDjr5gEijCSjJC3fQaJm7nCeQSeaty0Jixy8dbsk
Other
- age;
- XML signatures, used for example in SAML;
- PSKC (Portable Symmetric Key Container,
application/pskc+xml), a XML-based formats for symmetric keys, HOTP/TOTP secrets: - DNSSEC stores signatures (
RRSIG) and public keys (DNSKEY) in the DNS (see the IANA registry for details). - Biscuit
Appendix, Support in Web Crypto API
The W3C WebCrypto API supports reading/writing/converting keys from/to/between different formats:
- raw, raw bytes (for secret keys);
- SPKI,
SubjectPublicKeyInfoin DER format (public key); - PKCS8, private key in PKCS#8 format (DER encoded);
- JWK, JSON Web Key.
enum KeyFormat { "raw", "spki", "pkcs8", "jwk" };
There is no builtin support for PEM.
Code for public and private key serialization:
key = await crypto.subtle.generateKey({name:'ECDSA', namedCurve:"P-256"}, true, ['sign']);
await crypto.subtle.exportKey("jwk", key.publicKey)
// {
// "alg":"ES256",
// "crv":"P-256",
// "ext":true,
// "key_ops":[],
// "kty":"EC",
// "x":"smcdPc22cr147LzeEKGU4aE5O1oATXn5h8RjZG6fvbw",
// "y":"NRo9KhikP8lys85VnshzmTpcmg5VIUJyzXMZlX44M60"
// }
await crypto.subtle.exportKey("jwk", key.privateKey);
// {
// "alg":"ES256",
// "crv":"P-256",
// "d":"L0NBw4pe10Z_R8qrTBlihBTLUtNf81BWzWnr-nr4B1E",
// "ext":true,
// "key_ops":["sign"],
// "kty":"EC",
// "x":"smcdPc22cr147LzeEKGU4aE5O1oATXn5h8RjZG6fvbw",
// "y":"NRo9KhikP8lys85VnshzmTpcmg5VIUJyzXMZlX44M60"
// }
In contrast to the Node.js API:
- it forces you to choose the intended algorithm (you cannot juste create an EC key, you have to choose to create an "ECDSA" key or an "ECDH" key);
- it forces you to choose the
key_ops; - it does not support PEM out of the box;
- when importing a key you have to specify which "type" of key (ECDSA, ECDH, etc.) it is.
Appendix, Support in Node.js API
In addition to the W3C WebCrypto API, Node.js supports a native crypto module which supports reading/writing/converting from/to/between different formats.
| Key type | format | type | Usage |
|---|---|---|---|
| Secret | buffer | - | Raw secret key |
jwk | - | Secret key in JWK format | |
| Public | pem/der | pkcs1 | Public RSA key PKCS#1 format, RSAPublicKey |
spki | Generic public key, SubjectPublicKeyInfo | ||
jwk | - | JWK | |
| Private | pem/der | pkcs1 | Private RSA key in PKCS#1 format, RSAPrivateKey |
pem/der | pkcs8 | Generic private key in PKCS#8 | |
pem/der | sec1 | EC private key in SEC1 format, ECPrivateKey | |
jwk | - | JWK |
Code for public and private key serialization:
const { generateKeyPairSync } = require('node:crypto');
let {publicKey, privateKey} = generateKeyPairSync('ec', {namedCurve: 'P-256'});
publicKey.export({format:"jwk"})
// {
// kty: 'EC',
// x: 'qQnEZl4P2PKQ4OsQIymivGVD3uymiS4ysTiD-A31Cgs',
// y: 'bDmp-CXJvnEAGarDhdhSd1yp39YjLZ37cMxo-DZIJ24',
// crv: 'P-256'
// }
publicKey.export({format:"pem",type:"spki"})
// -----BEGIN PUBLIC KEY-----
// MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEqQnEZl4P2PKQ4OsQIymivGVD3uym
// iS4ysTiD+A31CgtsOan4Jcm+cQAZqsOF2FJ3XKnf1iMtnftwzGj4Nkgnbg==
// -----END PUBLIC KEY-----
privateKey.export({format:"pem",type:"sec1"})
// -----BEGIN EC PRIVATE KEY-----
// MHcCAQEEIJglknQfiQDuMiHSAaxg2y08lAnMr82BnXFQBhb0rGCroAoGCCqGSM49
// AwEHoUQDQgAEqQnEZl4P2PKQ4OsQIymivGVD3uymiS4ysTiD+A31CgtsOan4Jcm+
// cQAZqsOF2FJ3XKnf1iMtnftwzGj4Nkgnbg==
// -----END EC PRIVATE KEY-----
privateKey.export({format:"pem", type:"pkcs8"})
// -----BEGIN PRIVATE KEY-----
// MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgmCWSdB+JAO4yIdIB
// rGDbLTyUCcyvzYGdcVAGFvSsYKuhRANCAASpCcRmXg/Y8pDg6xAjKaK8ZUPe7KaJ
// LjKxOIP4DfUKC2w5qfglyb5xABmqw4XYUndcqd/WIy2d+3DMaPg2SCdu
// -----END PRIVATE KEY-----
privateKey.export({format:"jwk"})
// {
// kty: 'EC',
// x: 'qQnEZl4P2PKQ4OsQIymivGVD3uymiS4ysTiD-A31Cgs',
// y: 'bDmp-CXJvnEAGarDhdhSd1yp39YjLZ37cMxo-DZIJ24',
// crv: 'P-256',
// d: 'mCWSdB-JAO4yIdIBrGDbLTyUCcyvzYGdcVAGFvSsYKs'
// }
Appendix, Support in Python PyCA Cryptography
The Python PyCA cryptography library has support for ASN.1 PEM, ASN.1 DER, SubjectPublicKeyInfo, PKCS8, PKCS12, PKCS1, OpenSSH formats.
Encoding:
PEMDEROpenSSH(OpenSSH public keys text format)RawX962SMIME
PrivateFormat:
TraditionalOpenSSL(BEGIN RSA PRIVATE KEY)PKCS8(BEGIN PRIVATE KEY)RawOpenSSH(BEGIN OPENSSH PRIVATE KEY)PKCS12
PublicFormat:
SubjectPublicKeyInfoPKCS1OpenSSHRawCompressedPointUncompressedPoint
This library does not have native support for JWK. The JWCrypto library builds on top of the PyCA cryptography and provides support for JWK , JWS, JWE and JWT.
References
TLS:
- RFC6066, Transport Layer Security (TLS) Extensions: Extension Definitions
- RFC9849, TLS Encrypted Client Hello
- RFC9934, Privacy-Enhanced Mail (PEM) File Format for Encrypted ClientHello (ECH)
- The SSLKEYLOGFILE Format for TLS
PEM:
- RFC7468, Textual Encodings of PKIX, PKCS, and CMS Structures
- OpenSSL pem.h
X.509 public-key certificates:
- RFC2459, Internet X.509 Public Key Infrastructure Certificate and CRL Profile
- RFC3279, Algorithms and Identifiers for the Internet X.509 Public Key Infrastructure Certificate and Certificate Revocation List (CRL) Profile
- RFC5280, Internet X.509 Public Key Infrastructure Certificate and Certificate Revocation List (CRL) Profile (PKIX)
- RFC6818, Updates to the Internet X.509 Public Key Infrastructure Certificate and Certificate Revocation List (CRL) Profile
- RFC5914, Trust Anchor Format
- RFC5937, Using Trust Anchor Constraints during Certification Path Processing
- RFC9925, Unsigned X.509 Certificates
- OpenSSL Trust Anchors
- OpenSSL Trust settings
ASN.1 and other ASN.1 based-formats:
- RFC5208, PKCS #8 v1.2
- RFC5915, Elliptic Curve Private Key Structure
- RFC5958, Asymmetric Key Packages (CMS)
- RFC7292, PKCS #12: Personal Information Exchange Syntax v1.1
- RFC8017, PKCS #1: RSA Cryptography Specifications Version 2.2
- A Warm Welcome to ASN.1 and DER
- ASN.1 Specification for TPM 2.0 Key Files
- ASN.1 key structures in DER and PEM
GPG:
- OpenPGP
- Forming ASCII Armor in RFC4880 (OpenPGP Message Format)
- Armor Header Line in RFC9580 (OpenPGP)
JOSE and COSE:
- JOSE IANA registry
- COSE IANA registry
- RFC7515, JSON Web Signature (JWS)
- RFC7516, JSON Web Encryption (JWE)
- RFC7517, JSON Web Key (JWK)
- RFC7518, JSON Web Algorithms (JWA)
- RFC7519, JSON Web Token (JWT)
- RFC9052, CBOR Object Signing and Encryption (COSE): Structures and Process
- RFC8152, CBOR Object Signing and Encryption (COSE)
- RFC9360, COSE: Header Parameters for Carrying and Referencing X.509 Certificates
- RFC9052, COSE: Structures and Process
- RFC9679, COSE Key Thumbprint
- RFC9781, A CBOR Tag for Unprotected CBOR Web Token Claims Sets (UCCS)
- RFC9901, Selective Disclosure for JSON Web Tokens (SD-JWT)
- ML-DA for JOSE and COSE
- FN-DSA for JOSE and COSE
- SLH-DSA for JOSE and COSE
- RFC8725, JSON Web Token Best Current Practices
- draft-ietf-oauth-rfc8725bis, JSON Web Token Best Current Practices
SSH:
- RFC4716, The Secure Shell (SSH) Public Key File Format
- SECSH Public Key File Format (SSH2 format)
- SSH Certificate Format
- Anatomy of OpenSSH Key Revocation List (KRL) File
- OpenSSH PROTOCOL.certkey
- It's Now Possible To Sign Arbitrary Data With Your SSH Keys
Media types:
- Media Types IANA registry
- Structured Syntax Suffixes IANA registry
- Appendix A: MIME Types in OpenSSL PKI Tutorial
Tools:
- GNUTLS pkcs12 man page
- OpenSSL openssl-format-options
- OpenSSL verification options
- openssl-x509 - Certificate display and signing command
The PASERK uses the term “secret key” to talk about “private keys” whereas this term is normally used to talk about “symmetric keys”. ↩︎
To make things simpler the SPKI acronym can designated to different unrelated things in cryptography:
SubjectPublicKeyInfoi.e. a public key represented in ASN.1 (DER) formats as found in X.509 public key-certificates;- Simple Public Key Infrastructure using Lisp-like s-expressions (not covered here).
PKCS#12 is often used to bundle a private key (usually encrypted) with its associated certificate chain (the associated certificate and its intermediate CA certificates). ↩︎
Some ASN.1 encoding rules include:
- BER and DER;
- PER and CPER;
- UPER and CUPER;
- OER and COER;
- JER (JSON);
- XER, CXER and E-XER (XML);
- etc.
But I have never seen any use of anything but DER in practice. ↩︎
This is EC private key I generated just for this example. This is not a really super secret private key that you could use to hack me. 😀 ↩︎
This is a RSA private key explictly marked as intended to be used for signing using PSS (probabilistic signature scheme) signatures, instead of the older RSASSA-PKCS#1 v1.5 (signatures) or RSAES-PKCS#1 v1.5 (encryption).
In practice, it uses
rsassaPss(1.2.840.113549.1.1.10) instead ofrsaEncryption(1.2.840.113549.1.1.1):id-RSASSA-PSS OBJECT IDENTIFIER ::= { pkcs-1 10 }Moreover, the the
rsassaPssalgorithm is associated with additional information about the parameters of the RSA-PSS signatures:
↩︎RSASSA-PSS-params ::= SEQUENCE { hashAlgorithm [0] HashAlgorithm DEFAULT sha1Identifier, maskGenAlgorithm [1] MaskGenAlgorithm DEFAULT mgf1SHA1Identifier, saltLength [2] INTEGER DEFAULT 20, trailerField [3] INTEGER DEFAULT 1 }For some reason, the URI path of the ODOh target server (and its IP addresses) is not convered by this configuration and must be provisionned in addition to this. Moreover, thed ODoH RFC does not define any discovery mechanism for the ODoH server configuration. A more complete configuration of ODoH servers can be achieved using
sdns://URIS or/.well-known/odohconfigs. These methods are not really standard and documented. ↩︎No, this is not a private key I am actually using for anything. ↩︎
Can you decrypt this SSH key? 👨💻 ↩︎
Currently only PBES2 is supported for password-based encryption. There is currently no standard support for memory-hard password-based encryption in JOSE. ↩︎
This provides protection against malicious reuse of the SD-JWT by one verifier/audience on another verifier/audience and against replay attacks. ↩︎
Roughly CBOR is a standard binary JSON-like format (with more features such as tagged items, binary data, integer keys in dicts) derived from MessagePack. ↩︎
CDDL is a schema language for CBOR (and JSON). Similar to JSON Schema (for JSON), XSD (for XML), and ASN.1 (for DER). ↩︎
CBOR is a binary format. As it has more features than JSON, we cannot in general use JSON to represent a (any) CBOR data. A Diagnostic Notation (and the Extended Diagnostic Format) is defined for presentation to human. This format is originally intended for human consumption and is not intended to be parseable by software. A draft provides a formal definition of the format making it suitable for consumption by software. ↩︎
Countersignatrues v1 were defined in RFC8152, the original CBOR specification but were removed from the updated version of CBOR because of securityy issues. They are replaced by countersignatrues v2. ↩︎