/dev/posts/

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:

The following encoding families of formats are detailed in this post:

Notes:

Note: other labels

The following labels are found in OpenSSL code but are not included in the table:

  • ANY PRIVATE KEY can be found in OpenSSL source code but it is actually used for a real format. This is only used inside the API.
  • PARAMETERS and ECDSA PUBLIC KEY, are not used (?).
  • SM2 PRIVATE KEY and SM2 PARAMETERS are 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:

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

  1. the DER serialization,
  2. base64-encoded,
  3. 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:

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:

File extensions:

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:

  1. a (typically self-signed) certificate;
  2. 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);
  3. RFC5914 Trust Anchor Format which can be used to represent a trust anchor without including a signature;
  4. a unsigned X.509 certificates which is a standard X.509 certicicate with a dummy empty signature.

In practice,

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_AUX routines. 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_AUX ASN1 routines. X509_AUX is 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:

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:

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

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:

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 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:

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:

  • Dropbear (server and client);
  • Paramiko (pure Python implementation, server and client);
  • PuTTY (client-only, quite popular on Windows);
  • etc.

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:

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:

ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIK07V4wUjidorCCMY9KSJugdrNaT90zAfwXD2KgKo0fd foo@mymachine.example.com

Notice how the key type is actually duplicated in this line:

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:

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:

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:

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:

Both JWS and JWE can exist in two serializations:

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:

  1. a protected header;
  2. payload
    • which can be an arbitrary binary content in general,
    • which are a set of claims when the JWS is a JWT,
  3. a signature
    • which is actually either a digital signature or a MAC, depending on the alg value,
    • which protects both the protected header and the payload.

The JSON serialization supports more features such as:

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:

  1. a protected header;
  2. a ciphertext (i.e. a payload encrypted with a secret key);
  3. an encrypted secret key aka CEK (content encryption key), which is used to encrypt/decrypt the payload;
  4. an initialization vector (IV), used to encrypt/decrypt the payload;
  5. 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:

A JWT can be:

A lot of other standard claims are defined in additional specifications.

This format can be used for a lot of applications such as:

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):

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):

  1. The JWT only includes their (salted) hashes (digest).
  2. 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:

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:

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

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

Definitions (in CDDL[13]):

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:

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:

Format:

k{version}.{type}.{data}

Example: PASERK

k4.lid.iVtYQDjr5gEijCSjJC3fQaJm7nCeQSeaty0Jixy8dbsk

Other

Appendix, Support in Web Crypto API

The W3C WebCrypto API supports reading/writing/converting keys from/to/between different formats:

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:

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:

PrivateFormat:

PublicFormat:

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:

PEM:

X.509 public-key certificates:

ASN.1 and other ASN.1 based-formats:

GPG:

JOSE and COSE:

SSH:

Media types:

Tools:


  1. The PASERK uses the term “secret key” to talk about “private keys” whereas this term is normally used to talk about “symmetric keys”. ↩︎

  2. To make things simpler the SPKI acronym can designated to different unrelated things in cryptography:

    ↩︎
  3. 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). ↩︎

  4. 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. ↩︎

  5. 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. 😀 ↩︎

  6. 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 of rsaEncryption (1.2.840.113549.1.1.1):

    id-RSASSA-PSS  OBJECT IDENTIFIER  ::=  { pkcs-1 10 }
    

    Moreover, the the rsassaPss algorithm 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  }
    
    ↩︎
  7. 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. ↩︎

  8. No, this is not a private key I am actually using for anything. ↩︎

  9. Can you decrypt this SSH key? 👨‍💻 ↩︎

  10. Currently only PBES2 is supported for password-based encryption. There is currently no standard support for memory-hard password-based encryption in JOSE. ↩︎

  11. This provides protection against malicious reuse of the SD-JWT by one verifier/audience on another verifier/audience and against replay attacks. ↩︎

  12. 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. ↩︎

  13. CDDL is a schema language for CBOR (and JSON). Similar to JSON Schema (for JSON), XSD (for XML), and ASN.1 (for DER). ↩︎

  14. 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. ↩︎

  15. 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. ↩︎