A cryptographic toolkit written in Common Lisp
Clone or download
Latest commit ab342da Nov 3, 2018

README.org

Ironclad

https://travis-ci.org/sharplispers/ironclad.svg

Ironclad is a cryptography library written entirely in Common Lisp. It includes support for several popular ciphers, digests, MACs and public key cryptography algorithms. For several implementations that support Gray streams, support is included for convenient stream wrappers.

Most of the algorithms were written with efficiency for specific Common Lisp implementations in mind, although portable code is provided as an alternative in nearly all instances. The framework should be flexible enough to accomodate implementation-specific optimizations when possible.

Test vectors for many of the algorithms are included to provide a level of confidence in the correctness of the implementations.

Warning

Ironclad should not be considered safe against side channel attacks.

Some algorithms can be safe against side channel attacks on some architectures using some Common Lisp implementations, but in the general case it can’t be guaranteed. This is due to the fact that integers and arithmetic functions of Common Lisp implementations are usually not safe against side channel attacks.

Installation

The current version of Ironclad is 0.44. It can be downloaded at https://github.com/sharplispers/ironclad/archive/v0.44.tar.gz. If you are feeling adventurous, you can download a bleeding-edge version at https://github.com/sharplispers/ironclad.

It comes with an ASDF system definition, so (asdf:load-system "ironclad") should be all that you need to get started. The testsuite can be run by substituting asdf:test-system for asdf:load-system in the form above.

If you are using Quicklisp to manage your libraries, just use (ql:quickload "ironclad").

When Ironclad is loaded, its functions are in the ironclad package (e.g. (ironclad:make-cipher ...)). If you prefer, you can also use the crypto nickname (e.g. (crypto:make-cipher ...)).

Ironclad has been tested in the following implementations:

  • SBCL x86/linux, x86-64/linux (primary development platforms)
  • SBCL x86-64/solaris, x86/darwin
  • CMUCL x86/linux
  • ABCL with Sun’s 1.5.0 JVM
  • Lispworks 5.0.1 x86/linux
  • Lispworks 5.1.2 x86-64/darwin x86/windows
  • Allegro 8.0 x86/linux
  • Allegro 8.1 x86/linux, x86-64/linux, sparc/solaris
  • CLISP 2.41 x86/linux, x86/cygwin
  • Clozure Common Lisp 1.11 x86-64/Linux
  • Clozure Common Lisp 1.10 x86-64/darwin
  • ECL 16.1.3 x86-64/linux

All included tests should pass successfully. If you use a platform not listed above, please send your platform information so that it can be added to the above list. If the tests do not all pass, you have found a bug; please report it.

By default, Ironclad uses some implementation dependent low-level code to make some functions run much faster (currently, some assembly for SBCL and CCL, some C code for ECL). If for some reason you want to disable these optimisations and use the generic Lisp code, it can be achieved by commenting out the (pushnew :ironclad-assembly *features*) line in the src/package.lisp file.

License

Ironclad is released under a MIT-like license; you can do pretty much anything you want to with the code except claim that you wrote it.

Ciphers

(make-cipher name &key key mode initialization-vector padding tweak) => cipher

Return a cipher object suitable for use for both encryption and decryption.

name denotes the encryption algorithm to use. list-all-ciphers will tell you the names of all supported ciphers. They are:

  • 3des
  • aes
  • arcfour (rc4)
  • aria
  • blowfish
  • camellia
  • cast5
  • chacha
  • chacha/12
  • chacha/8
  • des
  • idea
  • kalyna128
  • kalyna256
  • kalyna512
  • kuznyechik
  • misty1
  • rc2
  • rc5
  • rc6
  • salsa20
  • salsa20/12
  • salsa20/8
  • seed
  • serpent
  • sosemanuk
  • square
  • tea
  • threefish1024
  • threefish256
  • threefish512
  • twofish
  • xchacha
  • xchacha/12
  • xchacha/8
  • xor (not a real cipher, use only for testing)
  • xsalsa20
  • xsalsa20/12
  • xsalsa20/8
  • xtea

name can be a symbol in the keyword package or in the ironclad package; :aes for AES, ironclad:arcfour for RC4, and so forth.

mode describes the mode of operation for the cipher. Stream ciphers such as Arcfour can operate in only one mode, stream. Block ciphers such as AES and DES can operate in several different modes:

  • ecb
  • cbc
  • ofb
  • cfb (note that Ironclad’s CFB mode is n-bit CFB, where n is the block-length of the cipher)
  • cfb8 (this seems to be the mode other crypto packages call CFB)
  • ctr

mode should be a symbol in the keyword or ironclad packages; :stream, ironclad:ofb, and so forth. An error will be signaled if mode is not appropriate for the cipher name.

initialization-vector (IV) should be supplied only if mode requires one. initialization-vector should be a (simple-array (unsigned-byte 8) (*)). The supplied IV should be the same length as the block-length of name. The Chacha and Salsa20 stream ciphers also use an initialization vector (nonce). It should be 8 bytes long for Chacha and Salsa20, and 24 bytes long for XChacha and XSalsa20.

key is, of course, the key for the cipher. key should be a (simple-array (unsigned-byte 8) (*)).

If padding is supplied, the specified padding method will be used by encrypt and decrypt to handle short blocks when the :handle-final-block argument is supplied. padding will only be used if the mode is ECB or CBC. The possible values for padding are :pkcs7, :ansi-x923 and :iso-7816-4.

If the cipher can use a tweak (e.g. threefish), it can be specified with the tweak key parameter.

(encrypt cipher plaintext ciphertext &key plaintext-start plaintext-end ciphertext-start handle-final-block) => n-bytes-consumed, n-bytes-produced

Encrypts data according to cipher from plaintext starting at plaintext-start and continuing until plaintext-end. The encrypted data is placed in ciphertext starting at ciphertext-start.

(decrypt cipher ciphertext plaintext &key ciphertext-start ciphertext-end plaintext-start handle-final-block) => n-bytes-consumed, n-bytes-produced

Decrypts data according to cipher from ciphertext starting at ciphertext-start and continuing until ciphertext-end. The decrypted data is placed in plaintext starting at plaintext-start.

(encrypt-in-place cipher text &key start end) => n-bytes-consumed, n-bytes-produced
(decrypt-in-place cipher text &key start end) => n-bytes-consumed, n-bytes-produced

Encrypts or decrypts data in text between start and end “in-place” according to cipher. These functions are shorthand for:

(encrypt cipher text text :plaintext-start start :plaintext-end end :ciphertext-start start)
(decrypt cipher text text :ciphertext-start start :ciphertext-end end :plaintext-start start)

Note: encrypt-in-place and decrypt-in-place do not support a handle-final-block parameter as encrypt and decrypt do. If you need the functionality that handle-final-block provides, then you need to use encrypt and decrypt.

Note: n-bytes-consumed and n-bytes-produced may not always be equal to the length of the data specified in the call to encrypt-in-place or decrypt-in-place. This subtlely is also present in encrypt or decrypt.

(encrypt-message cipher message &key start end &allow-other-keys) => encrypted-message

Return the message between start and end encrypted with the cipher; the class of cipher determines the algorithm used to encrypt the message.

(decrypt-message cipher message &key start end &allow-other-keys) => decrypted-message

Return the message between start and end decrypted by the cipher; the class of cipher determines the algorithm used to decrypt the message.

Inquiry functions

(list-all-ciphers) => list

Returns a list of cipher-names that may be validly passed to make-cipher.

(cipher-supported-p name) => boolean

Returns t if name would be in the list returned by list-all-ciphers, nil otherwise.

(key-lengths cipher) => list

Return a list of valid key lengths for cipher.

(block-length cipher) => number

Return the number of octets cipher processes at a time. This function always returns 1 for stream ciphers.

Key stream position

Block ciphers in CTR mode and some stream ciphers have the ability to change the current position within the key stream in constant time instead of having to consume all the bytes until the desired position is reached.

(keystream-position cipher &optional position) => number or boolean

Return or change the current position within the key stream of a cipher. When position is not supplied, keystream-position returns the current position in the key stream, or nil if it can’t be determined. When position is supplied, the key stream position of the cipher is set to that position if possible. keystream-position returns t if the repositioning is performed successfully, or nil otherwise.

keystream-position can be used with the following ciphers:

  • all the block ciphers (aes, twofish, etc.) in CTR mode
  • chacha
  • chacha/12
  • chacha/8
  • salsa20
  • salsa20/12
  • salsa20/8
  • xchacha
  • xchacha/12
  • xchacha/8
  • xsalsa20
  • xsalsa20/12
  • xsalsa20/8

Digests

Digest functions, also known as hash functions, produce fixed-length output (a digest or hash) from a variable-length message. The simplest example of a digest function is one that adds up all the bytes in the message modulo 256. This digest function fails one test of a cryptographically secure hash function: it must be difficult to find a message with a given digest. It also fails the other test: it must be difficult to find two messages with the same digest.

Ironclad provides several cryptographically secure digest functions and several non-cryptographically secure digest functions.

Note: In the functions below, messages or parts thereof are provided as octet vectors; Ironclad has no facilities for producing digests of strings. If you need to obtain the digest of a string, then you need to figure out how to convert it to an octet vector first. This is a deliberate design decision. Characters are not equivalent to bytes. See your local Unicode guru for more details.

(make-digest digest-name &rest keys &key &allow-other-keys) => digester

Returns a digest object. digest-name is a keyword naming the algorithm you wish digester to use. The supported digest names can be found by calling list-all-digests. They are:

  • adler32
  • blake2
  • blake2/160
  • blake2/256
  • blake2/384
  • blake2s
  • blake2s/128
  • blake2s/160
  • blake2s/224
  • crc24
  • crc32
  • groestl
  • groestl/224
  • groestl/256
  • groestl/384
  • jh
  • jh/224
  • jh/256
  • jh/384
  • keccak
  • keccak/224
  • keccak/256
  • keccak/384
  • kupyna
  • kupyna/256
  • md2
  • md4
  • md5
  • ripemd-128
  • ripemd-160
  • sha1
  • sha224
  • sha256
  • sha3
  • sha3/224
  • sha3/256
  • sha3/384
  • sha384
  • sha512
  • shake128
  • shake256
  • skein1024
  • skein1024/384
  • skein1024/512
  • skein256
  • skein256/128
  • skein256/160
  • skein256/224
  • skein512
  • skein512/128
  • skein512/160
  • skein512/224
  • skein512/256
  • skein512/384
  • streebog
  • streebog/256
  • tiger
  • tree-hash
  • whirlpool

Like for make-cipher, digest-name should be a symbol in the keyword or ironclad packages.

Some algorithms (e.g. shake128 and shake256) can produce digests of any size. The size of the digest in bytes can be specified with the output-length key parameter:

(make-digest :shake256 :output-length 123)
(update-digest digester thing &key &allow-other-keys) => (values)

Updates the internal state of digester with the contents of thing. The exact method is determined by the type of thing.

There are several methods defined on this generic function that take a particular digester and a (simple-array (unsigned-byte 8) (*)) as well as the usual start and end keyword arguments. These methods update the state of digester with the subsequence of the array denoted by start and end. They are not listed here because there’s one method for every type of digest that Ironclad provides, and listing them would get very tedious for no benefit. An example should suffice.

(let ((digester (ironclad:make-digest :sha1))
      (array (make-array 16 :element-type '(unsigned-byte 8) :initial-element 0)))
  ;; Update with 16 zeroes.
  (ironclad:update-digest digester array)
  ;; Update with 8 ones.
  (fill array 1 :start 2 :end 10)
  (ironclad:update-digest digester array :start 2 :end 10))
(update-digest digester (stream stream) &key buffer start end &allow-other-keys) => digester

Update the internal state of digester with the contents of stream, which must respond to read-byte or read-sequence with a (simple-array (unsigned-byte 8) (*)) and return digester. It differs from digest-stream, below, in that you may need to digest data before or after the contents of stream (this happens, for instance, when signing the contents of some file).

(produce-digest digester &key digest digest-start) => digest

Return the digest of the data processed by digester so far.

If digest is provided, the computed digest will be placed into digest starting at digest-start. digest must be a (simple-array (unsigned-byte 8) (*)). An insufficient-buffer-space error will be signaled if there is insufficient space in digest.

High-level convenience functions

Several high-level convenience functions that encapsulate common sequences of make-digest, update-digest and produce-digest are provided by Ironclad as well. They come in two flavors: the first takes a digest name as would be provided to make-digest. The second way to call these functions is to provide an actual digest object as the first argument. So one can say:

(ironclad:digest-sequence :md5 *buffer*)

or, equivalently:

(let ((digester (ironclad:make-digest :md5)))
  (ironclad:digest-sequence digester *buffer*))

The second form comes in handy if you plan on reusing the digest object.

(digest-sequence digest-spec sequence &rest args &key start end digest digest-start) => digest

Returns the digest of the subsequence of sequence bounded by start and end, according to digest-name. sequence must be a (simple-array (unsigned-byte 8) (*)). digest and digest-start are as in produce-digest.

(digest-stream digest-spec stream &rest args &key buffer start end digest digest-start) => digest

Returns the digest of the contents of the stream specified by stream. read-byte must be a legal operation on stream and return an (unsigned-byte 8). In a similar fashion, read-sequence on stream must support reading into a (simple-array (unsigned-byte 8) (*)). digest and digest-start are as in produce-digest.

If buffer is provided, it must be a (simple-array (unsigned-byte 8) (*)); the portion of buffer between start and end will be used to read the data from the stream.

(digest-file digest-spec pathname &rest args &key buffer start end digest digest-start) => digest

Returns the digest of the contents of the file named by pathname. digest and digest-start are as in produce-digest.

If buffer is provided, it must be a (simple-array (unsigned-byte 8) (*)); the portion of buffer between start and end will be used to read the data from the stream.

Inquiry functions

(list-all-digests) => list

Returns a list whose elements may be validly passed to make-digest.

(digest-supported-p name) => boolean

Returns t if name would be in the list returned by list-all-digests, nil otherwise.

(digest-length digest) => number

Returns the length of the digest computed by digest, which may be a digest-name or a digest instance.

Miscellaneous

Ironclad digests are CLOS objects; the interesting thing about this for most purposes is that functions like reinitialize-instance are supported. This means one can write a fairly efficient clone of the md5sum program like so:

(defun digest-sum-files (digest-name &rest files)
  (unless files
    (error "no files given to digest"))
  (loop with buffer = (make-array 8192 :element-type '(unsigned-byte 8))
        with digest = (make-array (ironclad:digest-length digest-name)
                                  :element-type '(unsigned-byte 8))
       for file in files
       for digester = (ironclad:make-digest digest-name)
       then (reinitialize-instance digester)
       do (ironclad:digest-file digester file :buffer buffer :digest digest)
          (format t "~A ~A~%" (file-namestring file)
                  (ironclad:byte-array-to-hex-string digest))))

Tree hashes

Ironclad supports tree hashes, as described in Tree Hash EXchange format. You create tree hashes as if you were creating a digest:

(ironclad:make-digest :tree-hash)

By default, this creates a tree hash that uses the Tiger digest algorithm internally and a segment size of 1024. Since using the Tiger digest algorithm is so common, a convenience function that makes your intent obvious has also been provided:

(ironclad:make-tiger-tree-hash)

You may indicate that you wish to use a different algorithm than Tiger:

(ironclad:make-digest '(:treehash :digest :sha256))

Or you might wish to use a different segment size:

(ironclad:make-digest '(:tree-hash :block-length 16384))

There is currently no interface for obtaining the intermediate hashes computed while computing the final tree hash.

Message authentication codes

A message authentication code is a cryptographic function of some data and a user-specified key. Only a person knowing the key can recompute the MAC for the given message. A MAC is useful where maintaining data integrity is required, but the secrecy of the data is not paramount.

Ironclad provides different kinds of MACs:

  • HMAC, specified in RFC 2104
  • CMAC, specified in RFC 4493 and NIST document 800-38B
  • GMAC, specified in NIST document 800-38D
  • Blake2 and Blake2s MAC
  • Poly1305 MAC
  • Skein MAC
(make-mac mac-name key &rest args) => mac

Return a MAC object initialized with a secret key. mac-name is a keyword naming the algorithm you wish mac to use. The supported MACs can be found by calling list-all-macs. They are:

  • blake2-mac
  • blake2s-mac
  • cmac
  • gmac
  • hmac
  • poly1305
  • skein-mac

Like for make-digest, mac-name should be a symbol in the keyword or ironclad packages.

Some MACs take extra arguments that can be specified in args.

(make-mac :blake2-mac key &key digest-length)
(make-mac :blake2s-mac key &key digest-length)
(make-mac :cmac key cipher-name)
(make-mac :gmac key cipher-name initialization-vector)
(make-mac :hmac key digest-name)
(make-mac :poly1305 key)
(make-mac :skein-mac key &key block-length digest-length)

When making a Blake2 MAC, the length of the key passed to make-mac must be 64 bytes.

When making a Blake2s MAC, the length of the key passed to make-mac must be 32 bytes.

When making a CMAC, cipher-name must have a block-length of either 8, 16, 32, 64 or 128; this restriction is satisfied by many ciphers in Ironclad with the notable exception of stream ciphers. key must be an acceptable key for cipher-name.

When making a GMAC, cipher-name must have a block-length of 16. key must be an acceptable key for cipher-name. The length of initialization-vector must be 12 bytes.

When making a Poly1305, the length of the key passed to make-mac must be 32 bytes.

When making a Skein MAC, block-length can be 32 (to use the Skein256 hash function internally), 64 (to use Skein512) or 128 (to use Skein1024). digest-length can be any length you want the computed digest to be. By default, block-length is 64 and digest-length is 64.

MAC objects support reinitialize-instance:

(reinitialize-instance mac &rest initargs &key key &allow-other-keys) => mac

The :key argument is the secret key, as provided to make-mac.

(update-mac mac thing &key &allow-other-keys) => (values)

Updates the internal state of mac with the contents of thing. The exact method is determined by the type of thing.

There are several methods defined on this generic function that take a particular MAC and a (simple-array (unsigned-byte 8) (*)) as well as the usual start and end keyword arguments. These methods update the state of mac with the subsequence of the array denoted by start and end. They are not listed here because there’s one method for every type of MAC that Ironclad provides, and listing them would get very tedious for no benefit. An example should suffice.

(let* ((key (random-data 32))
       (mac (ironclad:make-mac :hmac key :sha256))
       (array (make-array 16 :element-type '(unsigned-byte 8) :initial-element 0)))
  ;; Update with 16 zeroes.
  (ironclad:update-mac mac array)
  ;; Update with 8 ones.
  (fill array 1 :start 2 :end 10)
  (ironclad:update-mac mac array :start 2 :end 10))
(produce-mac mac &key digest digest-start) => digest

Return the digest of the data processed by mac so far. The internal state of mac is not modified; this feature makes it possible to compute a “rolling MAC” of a document.

If digest is provided, the computed digest will be placed into digest starting at digest-start. digest must be a (simple-array (unsigned-byte 8) (*)). An insufficient-buffer-space error will be signaled if there is insufficient space in digest.

The length of the digest returned by produce-mac is determined by the kind of MAC and the extra arguments passed to make-mac:

  • blake2-mac: from 1 to 64 bytes (64 by default)
  • blake2s-mac: from 1 to 32 bytes (32 by default)
  • cmac: block-length of the cipher-name passed to make-mac
  • gmac: 16 bytes
  • hmac: digest-length of the digest-name passed to make-mac
  • poly1305: 16 bytes
  • skein-mac: digest-length passed to make-mac (64 by default)

Inquiry functions

(list-all-macs) => list

Returns a list whose elements may be validly passed to make-mac.

(mac-supported-p name) => boolean

Returns t if name would be in the list returned by list-all-macs, nil otherwise.

Authenticated encryption

(make-authenticated-encryption-mode name &rest args) => mode

Return an authenticated encryption object suitable for use for both encryption and decryption.

name denotes the mode to use. list-all-authenticated-encryption-modes will tell you the names of all the supported modes. They are:

  • eax (Encrypt then authenticate then translate)
  • etm (Encrypt then MAC)
  • gcm (Galois counter mode)

name can be a symbol in the keyword or ironclad packages.

args depends on the chosen authenticated encryption mode.

(make-authenticated-encryption-mode :eax &key tag cipher-name key initialization-vector)
(make-authenticated-encryption-mode :etm &key tag cipher mac)
(make-authenticated-encryption-mode :gcm &key tag cipher-name key initialization-vector)

If tag is specified, it will be used at the end of decryption (when the handle-final-block flag is t) to check the authenticity of the data. A bad-authentication-tag error will be signaled if the data is not authentic. If you don’t specify it, you will have to call produce-tag after decryption and check that the tags match (e.g. using constant-time-equal).

When using EAX, key must be a suitable key for the chosen cipher-name.

When using ETM, cipher must be a cipher object created by make-cipher. mac must be a mac object created by make-mac.

When using GCM, cipher-name must have a block-length of 16 bytes. key must be a suitable key for the chosen cipher. The length of initialization-vector must be 12 bytes.

(process-associated-data mode data &key start end) => (values)

Update the internal state of mode with the contents of data between start and end so that they are taken into consideration in the authentication tag.

An authenticated encryption object can be used with the encrypt, decrypt, encrypt-message and decrypt-message functions.

(encrypt mode plaintext ciphertext &key plaintext-start plaintext-end ciphertext-start handle-final-block)
(decrypt mode ciphertext plaintext &key ciphertext-start ciphertext-end plaintext-start handle-final-block)
(encrypt-message mode message &key start end associated-data associated-data-start associated-data-end)
(decrypt-message mode message &key start end associated-data associated-data-start associated-data-end)
(produce-tag mode &key tag tag-start) => tag

Return the authentication tag of the data processed by mode so far. If tag is provided, the computed tag will be placed into tag starting at tag-start. tag must be a (simple-array (unsigned-byte 8) (*)). An insufficient-buffer-space error will be signaled if there is insufficient space in tag.

Inquiry functions

(list-all-authenticated-encryption-modes) => list

Returns a list whose elements may be validly passed to make-authenticated-encryption-mode.

(authenticated-encryption-mode-supported-p name) => boolean

Returns t if name would be in the list returned by list-all-authenticated-encryption-modes nil otherwise.

Key derivation functions

Ironclad comes with a few key derivation functions:

  • Argon2i
  • PBKDF1
  • PBKDF2
  • Scrypt
(derive-key kdf passphrase salt iteration-count key-length) => digest

Given a key derivation function object (produced by make-kdf), a password and salt (both must be of type (simple-array (unsigned-byte 8) (*))), and number of iterations, returns the password digest as a byte array of length key-length.

Scrypt ignores the iteration-count parameter.

(make-kdf kind &key digest n r p block-count additional-key additional-data) => kdf

Returns a key derivation function instance (kind must either be ARGON2I, PBKDF1, PBKDF2 or SCRYPT-KDF). The Argon2i key derivation uses the block-count, additional-key and additional-data parameters (block-count is the number of 1 KiB memory blocks used by the function and it must be at least 8, additional-key and additional-data are optional). The PBKDF algorithms use digest. The Scrypt key derivation uses cost parameters N, r and p (N is a CPU cost parameter that must be a power of 2, r and p are memory cost parameters that must be defined such that r * p <= 2^30).

The default Scrypt parameters are N = 4096, r = 8, and p = 2. Please note that depending on the values of N and r, derive-key may not be able to allocate sufficient space for its temporary arrays.

PBKDF convenience functions

Ironclad comes with convenience functions for using PBKDF1 and PBKDF2 to store passwords.

(pbkdf2-hash-password password &key salt digest iterations) => password

Convenience function for hashing passwords using the PBKDF2 algorithm. Returns the derived hash of the password, and the original salt, as byte vectors.

(pbkdf2-hash-password-to-combined-string password &key salt digest iterations) => password

Convenience function for hashing passwords using the PBKDF2 algorithm. Returns the derived hash of the password as a single string that encodes the given salt and PBKDF2 algorithm parameters.

(pbkdf2-check-password password combined-salt-and-digest) => boolean

Given a password byte vector and a combined salt and digest string produced by pbkdf2-hash-password-to-combined-string, checks whether the password is valid.

Public key cryptography

Ironclad includes support for a few public key cryptography algorithms.

Encryption algorithms:

  • Elgamal
  • RSA

Signature algorithms:

  • DSA
  • Ed25519
  • Ed448
  • Elgamal
  • RSA

Diffie-Hellman key exchange:

  • Curve25519
  • Curve448
  • Elgamal

Key pair generation

(generate-key-pair kind &key num-bits &allow-other-keys) => private-key, public-key

Return a key pair according to kind. The generation of DSA, Elgamal and RSA key pairs can take some time. If kind is :dsa or :rsa, the num-bits key argument indicating the size of the keys to generate must be specified. If kind is :elgamal, num-bits must be specified unless compatible-with-key is specified, in which case the group parameters are taken from the specified key instead of being generated.

For example, if Alice wants to generate a key pair for a Diffie-Hellman exchange with Bob’s Elgamal key pair:

(generate-key-pair :elgamal :compatible-with-key bob-public-key)

Key construction

(make-public-key kind &key &allow-other-keys) => public-key

Return a public key according to kind. The &key arguments vary according to kind. The interesting bits are in the methods that specialize on kind, below.

(make-public-key :curve25519 &key y) => public-key
(make-public-key :curve448 &key y) => public-key
(make-public-key :dsa &key p q g y) => public-key
(make-public-key :ed25519 &key y) => public-key
(make-public-key :ed448 &key y) => public-key
(make-public-key :elgamal &key p g y) => public-key
(make-public-key :rsa &key e n) => public-key
(make-private-key kind &key &allow-other-keys) => private-key

Return a private key according to kind. The &key arguments vary according to kind. The interesting bits are in the methods that specialize on kind, below.

(make-private-key :curve25519 &key x y) => private-key
(make-private-key :curve448 &key x y) => private-key
(make-private-key :dsa &key p q g y x) => private-key
(make-private-key :ed25519 &key x y) => private-key
(make-private-key :ed448 &key x y) => private-key
(make-private-key :elgamal &key p g y x) => private-key
(make-private-key :rsa &key d n) => private-key

For Curve25519, Curve448, Ed25519 and Ed448 keys, the type of the parameters is (simple-array (unsigned-byte 8) (*)):

  • x, the secret key
  • y, the public key

For DSA and Elgamal keys, the type of the parameters is integer:

  • p, the prime number defining the DL group
  • q, the prime number defining the DL sub-group
  • g, the generator
  • y, the public key
  • x, the private key

For RSA keys, the type of the parameters is integer:

  • n, the modulus
  • e, the public key
  • d, the private key

Key destructuring

The destructure-public-key and destructure-private-key functions can be useful if you need to store keys somewhere for future use.

(destructure-public-key public-key) => plist

Return the elements of a public key in a plist. The indicators of the plist match the &key arguments of the make-public-key method.

(destructure-private-key private-key) => plist

Return the elements of a private key in a plist. The indicators of the plist match the &key arguments of the make-private-key method.

Digital signatures

(sign-message key message &key start end &allow-other-keys) => signature

Return a signature of message between start and end signed with key; the class of key determines the algorithm used to create the signature.

Note: The sign-message does not perform the hashing of the data. You should hash your data using your favorite hash function, and then use this hash as the message passed to sign-message.

(verify-signature key message signature &key start end &allow-other-keys) => boolean

Verify whether signature is a valid signature of message between start and end using key. Return t is the signature is valid and nil otherwise.

Padding

To be secure, RSA signature requires the message to be padded. The pss key parameter is provided to pad (or unpad) the message during signature (or verification) with the PSS scheme of PKCS-1. The value of the pss key parameter can be either a digest name or t (which will use the sha1 digest).

(sign-message rsa-private-key message :pss t) => signature
(verify-signature rsa-public-key message signature :pss t) => boolean

The functions pss-encode and pss-decode can also be used by hand if necessary.

Format of signatures

sign-message returns signatures as octet vectors. When the signature contains several values (e.g. the R and S values of DSA signatures), the octet vector is the concatenation of these values (e.g. the first half of the vector is the R value, the second half is the S value). You can use the make-signature and destructure-signature functions if you need access to the elements of a signature (e.g. to use a different kind of serialization).

(make-signature kind &key &allow-other-keys) => signature

Return an octet vector representing a signature. The &key arguments vary according to kind. The interesting bits are in the methods that specialize on kind, below.

(make-signature :dsa &key r s n-bits) => signature
(make-signature :ed25519 &key r s) => signature
(make-signature :ed448 &key r s) => signature
(make-signature :elgamal &key r s n-bits) => signature
(make-signature :rsa &key s n-bits) => signature

For Ed25519 and Ed448 signatures, the type of the parameters r and s is (simple-array (unsigned-byte 8) (*)).

For DSA and Elgamal signatures, the type of the parameters r, s and n-bits is integer.

For RSA signatures, the type of the parameters s and n-bits is integer.

(destructure-signature kind signature) => plist

Return the elements of a signature in a plist. The indicators of the plist match the &key arguments of the make-signature method.

Encryption and decryption

(encrypt-message key message &key start end &allow-other-keys) => encrypted-message

Return the message between start and end encrypted with the key; the class of key determines the algorithm used to encrypt the message.

(decrypt-message key message &key start end n-bits &allow-other-keys) => decrypted-message

Return the message between start and end decrypted by the key; the class of key determines the algorithm used to decrypt the message. n-bits can be used to indicate the expected size of the decrypted message (e.g. a small byte vector starting with zeros encrypted without padding, which is probably a bad idea, c.f. Padding section).

Padding

To be secure, RSA encryption requires the message to be padded. The oaep key parameter is provided to pad (or unpad) the message during encryption (or decryption) with the OAEP scheme of PKCS-1. The value of the oaep key parameter can be either a digest name or t (which will use the sha1 digest).

(encrypt-message rsa-public-key message :oaep t) => encrypted-message
(decrypt-message rsa-private-key message :oaep t) => decrypted-message

The functions oaep-encode and oaep-decode can also be used by hand if necessary.

Format of messages

encrypt-message returns encrypted messages as octet vectors. When the message contains several values (e.g. the C1 and C2 values of Elgamal messages), the octet vector is the concatenation of these values (e.g. the first half of the vector is the big-endian representation of the C1 value, the second half is the C2 value). You can use the make-message and destructure-message functions if you need access to the elements of a message (e.g. to use a different kind of serialization).

(make-message kind &key &allow-other-keys) => message

Return an octet vector representing a message. The &key arguments vary according to kind. The interesting bits are in the methods that specialize on kind, below.

(make-message :elgamal &key c1 c2 n-bits) => message
(make-message :rsa &key m n-bits) => message

For Elgamal messages, the type of the parameters c1, c2 and n-bits is integer.

For RSA signatures, the type of the parameters m and n-bits is integer.

(destructure-message kind message) => plist

Return the elements of a message in a plist. The indicators of the plist match the &key arguments of the make-message method.

Diffie-Hellman key exchange

(diffie-hellman private-key public-key) => bytes

Return a secret shared by two users Alice and Bob, computed from Alice’s private key and Bob’s public key (these keys must be compatible, i.e. have the same group parameters).

Pseudo-random number generation

The *prng* special variable indicates which pseudo-random number generator is used by default by functions that need to generate some random data. It defaults to a sensible OS-specific value.

The vast, vast vast number of users should just use the default os-prng (which uses /dev/urandom on Unix and CryptGenRandom on Windows). For users who need deterministic, high-quality-random-seeming numbers (e.g. for Monte Carlo simulations), fortuna-generator is provided. Finally, if you’re running on a platform without a decent PRNG (these are few and far between now), you may require the full fortuna-prng. When in doubt, use os-prng, which is the default.

(make-prng name &key seed) => prng

Create a pseudo-random number generator.

name denotes the style of PRNG to use. list-all-prngs will tell you the names of all supported PRNGs. Currently supported PRNGs are:

  • OS
  • Fortuna
  • Fortuna-generator

name can be a symbol in the keyword package or in the ironclad package.

seed is a seed descriptor. If nil, the PRNG will not be seeded (which may prevent it from generating output until it is seeded, depending on the PRNG in question). If :random then the PRNG will be seeded with the OS’s cryptographically-secure PRNG. If :urandom then the PRNG will be seeded with the OS’s fast-but-potentially-less-secure PRNG, if available (if not, will fallback to :random). If it is a pathname indicator, a seed will be read from the indicated file, then a new seed will be generated and written back to the file (over-writing the old seed). Finally, if it is a byte vector, it will be used to seed the PRNG.

You should very rarely need to call make-prng; the default OS-provided PRNG should be appropriate in nearly all cases.

(list-all-prngs) => list

List all known PRNG types.

(random-data num-bytes &optional prng) => bytes

Generate num-bytes bytes of random data from prng. Updates the state of the generator.

(random-bits num-bits &optional prng) => integer

Generate an integer with num-bits bits.

(strong-random limit &optional prng) => number

A drop-in replacement for common-lisp:random, strong-random generates a number (an integer if limit is an integer and a float if it is a float) between 0 and limit - 1 in an unbiased fashion.

(read-os-random-seed source &optional prng) => reseed-count

Read an OS-provided random seed (from /dev/urandom or /dev/random on Unix; CryptGenRandom on Windows) and reseed prng.

source may be :random, which indicates /dev/random or :urandom, which indicates /dev/urandom. On Windows, CryptGenRandom is always used.

(read-seed path &optional prng) => t

Read enough bytes from path to reseed prng, then generate a pseudo-random seed and write it back to path. If path doesn’t exist, calls read-os-random-seed to get a truly random seed from the OS. Note that reseeding does not reset the generator’s state to the seed value; rather, it combines the generator’s state with the seed to form a new state.

(write-seed path &optional prng) => t

Generate enough random data to reseed prng, then write it to path.

Example

(random-data 16)
=> #(61 145 133 130 220 200 90 86 0 101 62 169 0 40 101 78)

(crypto:strong-random 16)
=> 3

(crypto:random-bits 16)
=> 41546

Fortuna

You should only use the Fortuna PRNG if your OS does not provided a sufficiently-good PRNG. If you use a Unix or Unix-like OS (e.g. Linux), macOS or Windows, it does. Only use the Fortuna PRNG if you know for certain that you need it.

Fortuna is a cryptographically-secure random number presented by Ferguson, Schneier and Kohno in Cryptography Engineering. It is built around 32 entropy pools, which are used with decreasing frequency for each reseed (e.g. pool 0 is used in each reseed, pool 1 in every other reseed, pool 2 in every fourth reseed and so forth). Pools are seeded with data from up to 256 sources.

Each application should have one or more entropy sources (say, one for each OS random number source, one for the low bits of the current time, one for the output of a particular command or group of commands and so forth). A source should be used to add randomness to each pool in order, so source 0 should top up pool 0, then pool 1, and so forth up to pool 31, then loop back to pool 1 again. Be very careful to spread entropy across all 32 pools.

Fortuna automatically feeds entropy from the pools back into its random state when random-data is called, using a method designed to make it resistant to various avenues of attack; even in case of generator compromise it will return to a safe state within a bounded time.

For purposes of reseeding, Fortuna will not reseed until the first pool contains 128 bits of entropy; +min-pool-size+ sets the number of bytes this is; it defaults to a very conservative 128, meaning that by default each byte of event is assumed to contain a single bit of randomness.

It also will not reseed more than ten times per second.

(add-random-event source pool-id event &optional prng) => pool-length

Add entropy to prng.

source is an integer in the range 0-255 specifiying the event’s application-defined source.

pool-id is an integer in the range 0-31 specifying the pool to top up.

event is up to 32 bytes of data (for longer events, hash them down or break them up into chunks).

Gray streams

Ironclad includes support for several convenient stream abstractions based on Gray streams. Gray streams support in Ironclad is included for SBCL, CMUCL, OpenMCL/CCL, Lispworks, ABCL, ECL, Clisp and Allegro.

Octet streams

Octet streams are very similar to Common Lisp’s string-stream except they deal in octets instead of characters.

(make-octet-input-stream buffer &optional start end) => octet-input-stream

As make-string-input-stream, only with octets instead of characters.

(make-octet-output-stream) => octet-output-stream

As make-string-output-stream, only with octets instead of characters.

(get-output-stream-octets stream) => octet-vector

As get-output-stream-string, only with an octet output-steam instead of a string output-stream.

(with-octet-input-stream ((var buffer &optional (start 0) end) &body body))

Within body, var is bound to an octet input stream. Reading from var gives the bytes between the indexes start and end of buffer. The result of the last form of body is returned.

(with-octet-output-stream ((var) &body body)) => bytes

Within body, var is bound to an octet output stream. After all the forms in body have been executed, the data that has been written to var (and that hasn’t been consumed by a call to get-output-stream-octets within body) is returned.

Digest streams

Digest streams compute a digest of the data written to them according to a specific digest algorithm.

Example:

(defun frobbing-function (stream)
  ;; We want to compute a digest of the data being written to STREAM
  ;; without involving our callees in the process.
  (let* ((digesting-stream (crypto:make-digesting-stream :sha1))
         (stream (make-broadcast-stream stream digesting-stream)))
    ;; Feed data to STREAM.
    (frob-guts stream)
    ;; Do something with the digest computed.
    (... (crypto:produce-digest digesting-stream) ...)
    ...))
(make-digesting-stream digest &rest args) => stream

Make a stream that computes a digest of the data written to it according to the algorithm digest. The parameters that can be used by some algorithms can be specified as args. produce-digest may be used to obtain a digest of all the data written to the stream.

Note: Calling produce-digest on a digest stream does not alter the internal state of the digest.

(with-digesting-stream (var digest-name &rest args) &body body) => digest

Within body, var is bound to a digesting stream for the digest-name algorithm. After all the forms in body have been executed, the digest of the data that has been written to var is returned.

Cipher streams

Cipher streams encrypt or decrypt the data written to or read from them according to a specific cipher algorithm.

(make-encrypting-stream stream cipher mode key &key initialization-vector direction) => stream

Make a stream wrapped around the binary stream stream that encrypts data according to the algorithm cipher initialized with a mode, a key and an initialization-vector. If direction is :input, the data read from the created input stream is the encryption of the data coming from stream. If direction is :output, the data written to the created output stream is encrypted before being sent to stream.

(make-decrypting-stream stream cipher mode key &key initialization-vector direction) => stream

Make a stream wrapped around the binary stream stream that decrypts data according to the algorithm cipher initialized with a mode, a key and an initialization-vector. If direction is :input, the data read from the created input stream is the decryption of the data coming from stream. If direction is :output, the data written to the created output stream is decrypted before being sent to stream.

Note: Only stream ciphers and block ciphers in CTR, CFB, CFB8 or OFB mode are supported by make-encrypting-stream and make-decrypting-stream.

(with-encrypting-stream ((var stream cipher mode key &key initialization-vector direction) &body body))

Within body, var is bound to an encrypting stream. The result of the last form of body is returned.

(with-decrypting-stream ((var stream cipher mode key &key initialization-vector direction) &body body))

Within body, var is bound to a decrypting stream. The result of the last form of body is returned.

MAC streams

MAC streams compute a message authentication code of the data written to them according to a specific MAC algorithm.

(make-authenticating-stream mac key &rest args) => stream

Make a stream that computes a MAC of the data written to it according to the algorithm mac initialized with a key. The parameters used to create the MAC can be specified as args. produce-mac may be used to obtain a MAC of all the data written to the stream.

Note: Calling produce-mac on a MAC stream does not alter the internal state of the MAC.

Example: encrypt some data and compute a MAC of the ciphertext

(let* ((data ...)
       (output-stream ...)
       (encryption-key ...)
       (authentication-key ...)
       (iv ...)
       (mac-stream (make-authenticating-stream :hmac
                                               authentication-key
                                               :sha3))
       (stream (make-broadcast-stream output-stream mac-stream))
       (cipher-stream (make-encrypting-stream stream
                                              :chacha
                                              :stream
                                              encryption-key
                                              :initialization-vector iv)))
  (write-sequence data cipher-stream)
  ...
  (let ((mac (produce-mac mac-stream)))
    ...))
(with-authenticating-stream (var mac-name key &rest args) &body body) => mac

Within body, var is bound to an authenticating stream for the mac-name algorithm. After all the forms in body have been executed, the message authentication code of the data that has been written to var is returned.

Utility functions

(ub16ref/le vector index) => value
(ub32ref/le vector index) => value
(ub64ref/le vector index) => value

This family of functions accesses an unsigned 16-bit, 32-bit or 64-bit value stored in little-endian order starting at index in vector. vector must be a (simple-array (unsigned-byte 8) (*)). These functions are SETFable.

(ub16ref/be vector index) => value
(ub32ref/be vector index) => value
(ub64ref/be vector index) => value

As the above, only the value is stored in big-endian order.

(byte-array-to-hex-string vector &key start end element-type) => string
(hex-string-to-byte-array string &key start end) => string
(ascii-string-to-byte-array string &key start end) => vector

byte-array-to-hex-string converts the bytes of vector between start and end into a hexadecimal string. It is useful for converting digests to a more readable form. element-type indicates the element-type of the returned string.

hex-string-to-byte-array parses a substring of string delimited start and end of hexadecimal digits into a byte array.

ascii-string-to-byte-array is provided as a quick and dirty way to convert a string to a byte array suitable for feeding to update-digest or encrypt. Care should be taken to ensure that the provided string is actually an ASCII string. start and end have their usual interpretations.

(octets-to-integer octet-vec &key start end big-endian n-bits) => number
(integer-to-octets bignum &key n-bits big-endian) => vector

octets-to-integer converts the bytes of octet-vec between start and end to an integer as though the bytes denoted a number in base 256. big-endian is a boolean indicating whether the bytes are to be read in big-endian or little-endian order. n-bits specifies how many bits should be considered as significant in the resulting number.

integer-to-octets is the reverse operation.

(expt-mod n exponent modulus) => number
(expt-mod/unsafe n exponent modulus) => number

Raises n to the exponent power modulo modulus in a more efficient fashion than (mod (expt n exponent) modulus). expt-mod is using the Montgomery ladder algorithm to be more robust against timing attacks. expt-mod/unsafe runs faster than expt-mod but is not safe against timing attacks; don’t use it on secret data.

make-random-salt &optional size => bytes

Generate a byte vector of size (default 16) random bytes, suitable for use as a password salt.

constant-time-equal data1 data2 => boolean

Check whether the contents of the byte arrays data1 and data2 are the same. This function runs in constant time (for a given array length) to prevent timing attacks. It can be used to compare passwords or MACs.

Conditions

ironclad-error

All errors signaled by Ironclad are of this type. This type is a direct subtype of simple-error without any extra slots or options.

initialization-vector-not-supplied

This error is signaled by make-cipher when an initialization vector is not provided and the requested mode requires an initialization vector.

invalid-initialization-vector

This error is signaled when an invalid initialization vector is supplied to make-cipher (e.g. when the length of the initialization vector does not match the block length of the cipher).

invalid-key-length

This error is signaled when the key provided to make-cipher is not of an acceptable length for the requested cipher.

unsupported-cipher

This error is signaled when the cipher-name provided to make-cipher is not cipher-supported-p.

unsupported-mode

This error is signaled when the mode provided to make-cipher is not mode-supported-p.

unsupported-padding

This error is signaled when the padding provided to make-cipher is not supported.

unsupported-digest

This error is signaled when the digest-name provided to make-digest is not digest-supported-p.

unsupported-mac

This error is signaled when the mac-name provided to make-mac is not mac-supported-p.

insufficient-buffer-space

This error is signaled when Ironclad needs to stuff some data into a buffer (e.g. when the user provides digest to produce-digest and there is insufficient space).

key-not-supplied

This error is signaled when a :key argument is not provided to make-cipher.

unsupported-kdf

This error is signaled when an invalid KDF name is provided to make-kdf.

unsupported-scrypt-cost-factors

This error is signaled when invalid Scrypt cost factors are provided to make-kdf.

unsupported-argon2i-cost-factors

This error is signaled when invalid Argon2i parameters are provided to make-kdf.

invalid-padding

This error is signaled when padding in a block is determined to be invalid.

invalid-mac-parameter

This error is signaled when an invalid parameter is provided to make-mac.

invalid-signature-length

This error is signaled when a signature with an invalid length is provided to verify-signature or destructure-signature.

invalid-message-length

This error is signaled when a message with an invalid length is provided to encrypt-message, decrypt-message or destructure-message.

missing-key-parameter

This error is signaled when it is determined that a parameter is missing in a call to make-public-key or make-private-key.

missing-message-parameter

This error is signaled when it is determined that a parameter is missing in a call to make-message.

missing-signature-parameter

This error is signaled when it is determined that a parameter is missing in a call to make-signature.

incompatible-keys

This error is signaled when incompatible keys are provided to diffie-hellman.

invalid-curve-point

This error is signaled when trying to use an invalid curve point.

invalid-public-key-length

This error is signaled when a public key with an invalid length is provided to verify-signature.

oaep-decoding-error

This error is signaled when the OAEP decoding of a message fails.

unsupported-authenticated-encryption-mode

This error is signaled when an invalid mode name is provided to make-authenticated-encryption-mode.

bad-authentication-tag

This error is signaled when the verification of authenticity of a message fails.