On this page:
1.1 Cryptography Examples
1.2 Public-Key Cryptography Examples

1 Introduction to the Crypto Library

Cryptography is not security. It is a tool that may be used in some cases to achieve security goals.

This library is not a turn-key solution to security. It is a library of low-level cryptographic operations—or, in other words, just enough rope for the unwary to hang themselves.

This manual assumes that you already know to use the cryptographic operations properly. Every operation has conditions that must be satisfied for the operation’s security properties to hold; they are not always well-advertised in documentation or literature, and they are sometimes revised as new weaknesses or attacks are discovered. Aside from the occasional off-hand comment, this manual does not discuss them at all. You are on your own.

1.1 Cryptography Examples

In order to use a cryptographic operation, you need an implementation of it from a crypto provider. Implementations are managed through crypto factories. This introduction will use the factory for libcrypto (OpenSSL), since it is widely available and supports many useful cryptographic operations. See Cryptography Factories for other crypto providers.

> (require crypto)
> (require crypto/libcrypto)

You can configure this library with a “search path” of crypto factories:

> (crypto-factories (list libcrypto-factory))

That allows you to perform an operation by providing a crypto algorithm specifier, which is automatically resolved to an implementation using the factories in (crypto-factories). For example, to compute a message digest, call the digest function with the name of the digest algorithm:

> (digest 'sha1 "Hello world!")

#"\323Hj\351\23nxV\274B!#\205\352yp\224GX\2"

Or, if you prefer, you can obtain an algorithm implementation explicitly:

> (define sha1-impl (get-digest 'sha1 libcrypto-factory))
> (digest sha1-impl "Hello world!")

#"\323Hj\351\23nxV\274B!#\205\352yp\224GX\2"

To encrypt using a symmetric cipher, call the encrypt function with a cipher specifier consisting of the name of the cipher and the cipher mode (see cipher-spec? for details).

> (define skey #"VeryVerySecr3t!!")
> (define iv (make-bytes (cipher-iv-size '(aes ctr)) 0))
> (encrypt '(aes ctr) skey iv "Hello world!")

#"wu\345\215\e\16\256\355.\242\30x"

Of course, using an all-zero IV is usually a very bad idea. You can generate a random IV of the right size (if a random IV is appropriate), or you can get the IV size and construct one yourself:

> (define iv (generate-cipher-iv '(aes ctr)))
> iv

#"\351\256\17\\f\3505l\227\235\17\0007\376\vu"

> (cipher-iv-size '(aes ctr))

16

There are also functions to generate session keys, HMAC keys, etc. These functions use crypto-random-bytes, a cryptographically strong source of randomness.

When an authenticated encryption (AEAD) cipher, such as AES-GCM, is used with encrypt or decrypt, the authentication tag is automatically appended to (or taken from) the end of the cipher text, respectively. AEAD ciphers also support additionally authenticated data, passed with the #:aad keyword.

> (define key (generate-cipher-key '(aes gcm)))
> (define iv (generate-cipher-iv '(aes gcm)))
> (define ct (encrypt '(aes gcm) key iv #"Nevermore!" #:aad #"quoth the raven"))
> (decrypt '(aes gcm) key iv ct #:aad #"quoth the raven")

#"Nevermore!"

If authentication fails at the end of decryption, an exception is raised:

> (decrypt '(aes gcm) key iv ct #:aad #"said the bird")

decrypt: authenticated decryption failed

In addition to “all-at-once” operations like digest and encrypt, this library also supports algorithm contexts for incremental computation.

> (define sha1-ctx (make-digest-ctx 'sha1))
> (digest-update sha1-ctx #"Hello ")
> (digest-update sha1-ctx #"world!")
> (digest-final sha1-ctx)

#"\323Hj\351\23nxV\274B!#\205\352yp\224GX\2"

1.2 Public-Key Cryptography Examples

Public-key (PK) cryptography uses keypairs consisting of public and private keys. A keypair can be generated by calling generate-private-key with the desired PK cryptosystem and an association list of key-generation options. The private key consists of the whole keypair—both private and public components. A key containing only the public components can be obtained with the pk-key->public-only-key function.

> (define rsa-impl (get-pk 'rsa libcrypto-factory))
> (define privkey (generate-private-key rsa-impl '((nbits 512))))
> (define pubkey (pk-key->public-only-key privkey))

RSA keys support both signing and encryption. Other PK cryptosystems may support different operations; for example, DSA supports signing but not encryption, and DH only supports key agreement.

PK signature algorithms are limited in the amount of data they can sign directly, so the message is first processed with a digest function, then the digest is signed. The digest/sign and digest/verify functions compute the digest automatically. The private key signs, and the public key verifies.

> (define sig (digest/sign privkey 'sha1 "Hello world!"))
> (digest/verify pubkey 'sha1 "Hello world!" sig)

#t

> (digest/verify pubkey 'sha1 "Transfer $100" sig)

#f

It is also possible to sign a precomputed digest. The digest algorithm is still required as an argument, because some signature schemes include a digest algorithm identifier.

> (define dgst (digest 'sha1 "Hello world!"))
> (define sig (pk-sign-digest privkey 'sha1 dgst))
> (pk-verify-digest pubkey 'sha1 (digest 'sha1 "Hello world!") sig)

#t

> (pk-verify-digest pubkey 'sha1 (digest 'sha1 "Transfer $100") sig)

#f

Encryption is similar, except that the public key encrypts, and the private key decrypts.

> (define skey #"VeryVerySecr3t!!")
> (define e-skey (pk-encrypt pubkey skey))
> (pk-decrypt privkey e-skey)

#"VeryVerySecr3t!!"

The other PK operation is key agreement, or shared secret derivation. Two parties exchange public keys, and each party uses their own private key together with their peer’s public key to derive a shared secret.

For additional examples, see the crypto/examples directory (online here).