5 Public-Key Cryptography
Public-key (PK) cryptography covers operations such as signing, encryption, and key agreement between parties that do not start with any shared secrets. Instead of shared secrets, each party possesses a keypair consisting of a secret private key and a widely-published public key. Not all PK cryptosystems support all PK operations (for example, DSA does not support encryption or secret derivation), and some PK implementations may support a subset of a PK cryptosystem’s potential operations.
A PK cryptosystem specifies the information represented by the public and private keys and the algorithms that operate on that information. The following PK systems are supported:
'rsa —
RSA keys with RSAES-* encryption and RSASSA-* signing. 'ec —
Elliptic curve keys with ECDSA signing and ECDH key agreement. Only named curves are supported, and different implementations support different curves; use factory-print-info to see supported curves. 'eddsa —
Edwards curve keys with EdDSA signing, specifically Ed25519 and Ed448. 'ecx —
Montgomery curve keys with ECDH key agreement, specifically X25519 and X448.
Changed in version 1.1 of package crypto-lib: Added 'eddsa and 'ecx.
procedure
pki : pk-spec? factories : (or/c crypto-factory? (listof crypto-factory?))
procedure
(pk-can-sign? pk) → boolean?
pk : (or/c pk-impl? pk-key?)
procedure
(pk-can-encrypt? pk) → boolean?
pk : (or/c pk-impl? pk-key?)
procedure
(pk-can-key-agree? pk) → boolean?
pk : (or/c pk-impl? pk-key?)
Note that the functions only report the capabilities of the cryptosystem implementation, regardless of the limitations of pk if pk is a key. For example, (pk-can-sign? pk) would return true when pk is an RSA public-only key, even though signing requires a private key.
procedure
(pk-has-parameters? pk) → boolean?
pk : (or/c pk-impl? pk-key?)
5.1 PK Keys and Parameters
A PK keypair consists of public key component and private key components. A public key is a key that contains the public key components. In this library, a private key contains both private and public components, so it can also be used wherever a public key is required. That is, every private key is also a public key. This library uses the term “public-only key” to refer to a public key that is not a private key.
In some PK cryptosystems, the public components are further divided into key-specific values and “key parameters.” Key parameters are public quantities that are expensive to compute; they can be generated once and many keypairs can use the same parameter values. For example, a DSA key requires a large prime with certain relatively rare mathematical properties, and so finding such a prime is relatively expensive, but once a suitable prime is found, generating private keys is relatively fast, and since the prime is public, many keypairs can use the same prime. Elliptic curve (EC) cryptography is another example: the key parameter is the curve equation, and the public and private key components are points on the curve. In contrast, RSA does not have key parameters; simple quantities like the size of an RSA modulus are not key parameters.
procedure
(private-key? v) → boolean?
v : any/c
procedure
(public-only-key? v) → boolean?
v : any/c
procedure
(pk-parameters? v) → boolean?
v : any/c
procedure
(pk-key->parameters pk) → (or/c pk-parameters? #f)
pk : pk-key?
procedure
(public-key=? pk1 pk2 ...) → boolean?
pk1 : pk-key? pk2 : pk-key?
procedure
pk : pk-key?
procedure
(generate-pk-parameters pki [ paramgen-config]) → pk-parameters? pki : (or/c pk-spec? pk-impl?) paramgen-config : (listof (list/c symbol? any/c)) = '()
- The following configuration values are recognized for DSA ('dsa):
(list 'nbits nbits) —
Optional. Generate a prime modulus of size nbits. Examples include 1024 and 2048.
- The following configuration values are recognized for DH ('dh):
- The following configuration values are recognized for EC ('ec):
(list 'curve curve-name) —
Required. Use the standard curve named curve-name. Examples include "NIST P-256" and "secp192r1". Use factory-print-info to show available curves.
- The following configuration values are recognized for EdDSA ('eddsa):
(list 'curve curve-sym) —
Required. Generate a key for the given curve. The curve-sym must be 'ed25519 or 'ed448.
- The following configuration values are recognized for 'ecx:
(list 'curve curve-sym) —
Required. Generate a key for the given curve. The curve-sym must be 'x25519 or 'x448.
Changed in version 1.1 of package crypto-lib: Added 'eddsa and 'ecx options.
procedure
(generate-private-key pki [keygen-config]) → private-key?
pki : (or/c pk-spec? pk-impl? pk-parameters?) keygen-config : (listof (list/c symbol? any/c)) = '()
If pki is a PK parameters object (pk-parameters?), then keygen-config must be empty.
- The following configuration values are recognized for RSA ('rsa):
(list 'nbits nbits) —
Optional. Generate a modulus of size nbits. Examples include 1024 and 2048.
If pki is a PK specifier (pk-spec?) or PK implementation (pk-impl?), then the same configuration arguments are supported as for generate-parameters. This is equivalent to (generate-private-key (generate-pk-parameters pki keygen-config) '()).
5.2 PK Signatures
In PK signing, the sender uses their own private key to sign a message; any other party can verify the sender’s signature using the sender’s public key.
In RSA, DSA, and ECDSA, only short messages can be signed directly (limits are generally proportional to the size of the keys), so a typical process is to compute a digest of the message and sign the digest. The message and digest signature are sent together, possibly with additional data.
In EdDSA, messages are signed directly. (The signing process computes a message digest internally.)
procedure
pk : private-key? msg : bytes? padding : (or/c #f 'pkcs1-v1.5 'pss 'pss*) = #f dspec : (or/c digest-spec? 'none #f) = #f
'pkcs1-v1.5 or #f —
use PKCS#1-v1.5 padding 'pss —
use PSS padding with a salt length equal to (digest-size di) 'pss* —
sign using PSS padding with a salt length equal to (digest-size di), but infer the salt length when verifying
If pk is an RSA private key, then dspec must be the name of a digest algorithm, and msg must be a digest computed with dspec (in particular, it must have the correct size for dspec). The resulting signature depends on the identity of the digest algorithm. Different RSA implementations may support different digest algorithms.
If pk is a DSA or EC private key, the signature does not depend on the digest algorithm; the dspec should be omitted. (For backwards compatibility, the dspec argument is accepted, but it has no effect other than checking the length of msg.)
If pk is a EdDSA private key, then dspec must be #f or 'none (both values mean the same thing). The message may be of any length, and the EdDSA signature is computed. Future versions of this library may accept other values of dspec and compute HashEdDSA signatures (eg, Ed25519ph) in reponse.
Added in version 1.1 of package crypto-lib.
procedure
(pk-verify pk msg sig [ #:digest dspec #:pad padding]) → boolean? pk : pk-key? msg : bytes? sig : bytes? dspec : (or/c digest-spec? #f 'none) = #f padding : (or/c #f 'pkcs1-v1.5 'pss) = #f
The dspec and padding arguments have the same meanings as for pk-sign.
Added in version 1.1 of package crypto-lib.
procedure
(digest/sign pk di input [#:pad padding]) → bytes?
pk : private-key? di : (or/c digest-spec? digest-impl?) input : input/c padding : (or/c #f 'pkcs1-v1.5 'pss) = #f
procedure
(digest/verify pk di input sig [#:pad padding]) → boolean?
pk : pk-key? di : (or/c digest-spec? digest-impl?) input : input/c sig : bytes? padding : (or/c #f 'pkcs1-v1.5 'pss) = #f
Do not use these functions with EdDSA keys; use pk-sign and pk-verify directly on the messages. (This library currently does not support pre-hashing EdDSA variants, eg Ed25519ph.)
procedure
(pk-sign-digest pk di dgst [#:pad padding]) → bytes?
pk : private-key? di : (or/c digest-spec? digest-impl?) dgst : bytes? padding : (or/c #f 'pkcs1-v1.5 'pss 'pss*) = #f
procedure
(pk-verify-digest pk di dgst sig [ #:pad padding]) → boolean? pk : pk-key? di : (or/c digest-spec? digest-impl?) dgst : bytes? sig : bytes? padding : (or/c #f 'pkcs1-v1.5 'pss) = #f
5.3 PK Encryption
In PK encryption, the sender uses the public key of the intended receiver to encrypt a message; the receiver decrypts the message with the receiver’s own private key. Only short messages can be directly encrypted using PK cryptosystems (limits are generally proportional to the size of the PK keys), so a typical approach is to encrypt the message using a symmetric cipher with a randomly-generated key (sometimes called the bulk encryption key) and encrypt that key using PK cryptography. The symmetric-key-encrypted message and PK-encrypted symmetric key are sent together, perhaps with additional data such as a MAC. PK encryption is supported by the RSA cryptosystem.
procedure
(pk-encrypt pk msg [#:pad padding]) → bytes?
pk : pk-key? msg : bytes? padding : (or/c #f 'pkcs1-v1.5 'oaep) = #f
procedure
(pk-decrypt pk msg [#:pad padding]) → bytes?
pk : private-key? msg : bytes? padding : (or/c #f 'pkcs1-v1.5 'oaep) = #f
If pk is an RSA key, then padding choses between PKCS#1-v1.5 padding and OAEP padding [PKCS1]. If padding is #f, then an implementation-dependent mode is chosen. For all other cryptosystems, padding must be #f.
If msg is too large to encrypt using pk, then an exception is raised.
5.4 PK Key Agreement
In PK key agreement (sometimes called key exchange) two parties derive a shared secret by exchanging public keys. Each party can compute the secret from their own private key and the other’s public key, but it is believed infeasible for an observer to compute the secret from the two public keys alone. PK secret derivation is supported by the 'dh, 'ec, and 'ecx cryptosystems.
procedure
(pk-derive-secret pk peer-pk) → bytes?
pk : private-key? peer-pk : (or/c pk-key? bytes?)
Note that the derived secret is a deterministic function of the private keys: if two parties perform secret derivation twice, they will produce the same secret both times. In addition, the secret is not uniformly distributed. For these reasons, the derived secret should not be used directly as a key; instead, it should be used to generate key material using a process such as described in RFC 2631 [RFC2631].
5.5 PK External Representations
This section describes serialization of public and private keys in various formats.
procedure
(pk-key->datum pk fmt) → printable/c
pk : pk-key? fmt : symbol?
'SubjectPublicKeyInfo —
DER-encoded SubjectPublicKeyInfo [PKIX] representation of the public part of pk. All key types are supported, and an identifier for the key type is embedded in the encoding. For compatibility with OpenSSL, DH keys are encoded using the PKCS #3 identifier and parameters [PKCS3] rather than those specified by [PKIX-AlgId], and EdDSA keys are encoded using the algorithm identifiers specified in the draft [PKIX-EdC].
'PrivateKeyInfo —
DER-encoded PrivateKeyInfo [PKCS8] representation of pk, which must be a private key. All key types are supported, and an identifier for the key type is embedded in the encoding. For DSA, DH, and EdDSA keys, the PrivateKeyInfo (version 1) format does not store derived public-key fields. Some implementations (eg GCrypt) do not expose the ability to recompute the public key, so they may not be able to read such keys. See also 'OneAsymmetricKey.
'OneAsymmetricKey —
DER-encoded OneAsymmetricKey [AKP] representation of pk, which must be a private key. OneAsymmetricKey is essentially PrivateKeyInfo version 2; it adds an optional field for the public key. Prefer OneAsymmetricKey for storing DSA, DH, and EdDSA keys. 'RSAPrivateKey —
DER-encoded RSAPrivateKey [PKCS1] representation of pk, which must be an RSA private key. 'rkt-private —
An S-expression of one of the following forms: 'rkt-public —
An S-expression of one of the following forms:
More formats may be added in future versions of this library.
Changed in version 1.1 of package crypto-lib: Added 'OneAsymmetricKey, 'rkt-private, and 'rkt-public support.
procedure
(datum->pk-key datum fmt [factories]) → pk-key?
datum : any/c fmt : symbol?
factories : (or/c crypto-factory? (listof crypto-factory?)) = (crypto-factories)
See pk-key->datum for information about the fmt argument.
procedure
(pk-parameters->datum pkp fmt) → printable/c
pkp : pk-parameters? fmt : symbol?
'AlgorithmIdentifier —
DER-encoded AlgorithmIdentifier [PKIX] representation of pkp. All key parameter types are supported, and an identifier for the key parameter type is embedded in the encoding. For compatibility with OpenSSL, the PKCS #3 identifier and parameter format [PKCS3] are used rather than those specified by [PKIX-AlgId].
'DSAParameters —
DER-encoded Dss-Parms [PKIX-AlgId] 'DHParameter —
DER-encoded DHParameter [PKCS3]. Note: this format differs from the DomainParameters format specified by PKIX. 'EcpkParameters —
DER-encoded EcpkParameters [PKIX-AlgId] (called ECDomainParameters in [SEC1]). 'rkt-params —
An S-expression of one of the following forms:
More formats may be added in future versions of this library.
Changed in version 1.1 of package crypto-lib: Added 'rkt-params support.
procedure
(datum->pk-parameters datum fmt [factories]) → pk-parameters?
datum : any/c fmt : symbol?
factories : (or/c crypto-factory? (listof crypto-factory?)) = (crypto-factories)
5.6 PKCS #8 Encrypted Private Keys
(require crypto/pkcs8) | package: crypto-lib |
Added in version 1.5 of package crypto-lib.
procedure
(pkcs8-encrypt/pbkdf2-hmac password pk [ #:digest digest-spec #:iterations iterations #:cipher cipher-spec #:key-size key-size]) → bytes? password : bytes? pk : (or/c private-key? bytes?) digest-spec : digest-spec? = 'sha512 iterations : exact-positive-integer? = (expt 2 16) cipher-spec : cipher-spec? = '(aes cbc)
key-size : exact-positive-integer? = (cipher-default-key-size cipher-spec)
procedure
(pkcs8-encrypt/scrypt password pk [ #:N N #:r r #:p p #:cipher cipher-spec #:key-size key-size]) → bytes? password : bytes? pk : (or/c private-key? bytes?) N : exact-positive-integer? = (expt 2 14) r : exact-positive-integer? = 8 p : exact-positive-integer? = 1 cipher-spec : cipher-spec? = '(aes cbc)
key-size : exact-positive-integer? = (cipher-default-key-size cipher-spec)
See pbkdf2-hmac for the meaning of the iterations argument, and see scrypt for the meanings of the N, r, and p arguments.
The following values of cipher-spec are currently supported: '(aes cbc), '(des-ede3 cbc), '(aes gcm), and '(chacha20-poly1305 stream).
The following values of digest-spec are currently supported: 'sha1, 'sha224, 'sha256, 'sha384, and 'sha512.
Compatibility with OpenSSL 1.1.0 has been tested with '(aes cbc) and '(des-ede3 cbc).
procedure
(pkcs8-decrypt-bytes password p8-epki) → bytes?
password : bytes? p8-epki : bytes?
If the wrong password is given, then an exception will probably be raised, but for non-AEAD ciphers (such as AES-CBC), there is a possibility that the decryption will succeed and return nonsense.
procedure
(pkcs8-decrypt-key password p8-epki) → private-key?
password : bytes? p8-epki : bytes?
(datum->pk-key (pkcs8-decrypt-bytes password p8-epki) 'OneAsymmetricKey)
Bibliography
[AKP] | “RFC 5958: Asymmetric Key Packages.” https://tools.ietf.org/html/rfc5958 | |
[PKCS1] | “PKCS #1: RSA Cryptography, version 2.1.” https://tools.ietf.org/html/rfc3447 | |
[PKCS3] | “PKCS #3: Diffie-Hellman Key-Agreement Standard.” | |
[PKCS8] | “PKCS #8: Private-Key Information Syntax Specification, version 1.2.” https://tools.ietf.org/html/rfc5208 | |
[PKIX] | “RFC 5280: Internet X.509 Public Key Infrastructure: Certificate and CRL Profile.” https://tools.ietf.org/html/rfc5280 | |
[PKIX-AlgId] | “RFC 3279: Algorithms and Identifiers for the Internet X.509 Public Key Infrastructure Certificate and Certificate Revocation List (CRL) Profile.” https://tools.ietf.org/html/rfc3279 | |
[PKIX-EdC] | “RFC 8410: Algorithm Identifiers for Ed25519, Ed448, X25519 and X448 for use in the Internet X.509 Public Key Infrastructure.” https://tools.ietf.org/html/rfc8410 | |
[RFC2631] | “RFC 2631: Diffie-Hellman Key Agreement Method.” https://tools.ietf.org/html/rfc2631 | |
[SEC1] | “SEC 1: Elliptic Curve Cryptography.” http://www.secg.org/sec1-v2.pdf |