caesium is a modern cryptography library for Clojure. It is a direct jnr-ffi binding to libsodium, which in turn is a more convenient fork of the original NaCl library by djb.
NOTE: Install libsodium 1.0.18+ before trying to use caesium.
Here's a sample of how you can use secretbox:
(ns minimum-viable-secretbox
(:require [caesium.crypto.secretbox :as sb]))
(def key (sb/new-key!))
(def plaintext "Hello caesium!")
(def nonce (sb/int->nonce 0))
(def ciphertext (sb/encrypt key nonce (.getBytes plaintext)))
(def roundtrip (String. (sb/decrypt key nonce ciphertext)))
(assert (= plaintext roundtrip))
The most important documentation for caesium is actually the documentation for libsodium. Since it's all just relatively small wrappers around that, everything in it applies.
Here's an example of how you can use pwhash:
(ns pwhash-usage
(:require [caesium.crypto.pwhash :as pwhash]
[caesium.randombytes :as rb]
[caesium.byte-bufs :as bb]
[caesium.util :as u]
[caesium.crypto.secretbox :as sb]))
;; helper function for creating salts from integers. may be useful for deterministic
;; key derivation, incrementing subkeys from 0.
(def int->salt (partial u/n->bytes pwhash/saltbytes))
;; hashing passwords
(def password "example")
(def hashed-password (pwhash/pwhash-str password
pwhash/opslimit-sensitive
pwhash/memlimit-sensitive))
(assert (= 0 (pwhash/pwhash-str-verify hashed-password password)))
;; key derivation
(def salt (rb/randombytes pwhash/saltbytes)) ; changing salt means changed derived key
(def derived-key (pwhash/pwhash sb/keybytes
password
salt
pwhash/opslimit-sensitive
pwhash/memlimit-sensitive
pwhash/alg-default))
(def message (.getBytes "hello, world!"))
(def encrypted-message (sb/encrypt derived-key (sb/int->nonce 0) message))
(def decrypted-message (sb/decrypt derived-key (sb/int->nonce 0) encrypted-message))
(assert (bb/bytes= message decrypted-message))
Here is how you can create or update a repository secret for GitHub actions:
(require '[caesium.crypto.box])
(require '[clj-http.client :as http])
(require '[jsonista.core :as json])
(import '(java.util Base64))
(def public-key
"The public key of the repository of which you want to create or update a secret"
(let [payload (-> {:request-method :get
:url "https://api.github.com/repos/{owner}/{repo}/actions/secrets/public-key"
:basic-auth ["{user}" "{GITHUB_TOKEN}"]
:headers {"Content-Type" "application/json"
"Accept" "application/vnd.github.v3+json"}}
http/request
:body
json/read-value)
^String encoded-key (get payload "key")]
{:decoded-key (.decode (Base64/getDecoder) (.getBytes encoded-key))
:key-id (get payload "key_id")}))
(let [{:keys [^String decoded-key ^String key-id]} public-key
plaintext "MY_SECRET_VALUE"
cyphertext (caesium.crypto.box/box-seal
(byte-streams/to-byte-array plaintext)
decoded-key)]
(http/request
{:request-method :put
:url "https://api.github.com/repos/{owner}/{repo}/actions/secrets/{MY_SECRET}"
:body (json/write-value-as-string
{:encrypted_value (.encodeToString (Base64/getEncoder) cyphertext)
:key_id key-id})
:basic-auth ["{user}" "{GITHUB_TOKEN}"]
:headers {"Content-Type" "application/json"
"Accept" "application/vnd.github.v3+json"}}))
Instead of making specific claims about specific libraries which may become outdated, here are a few properties you may care about:
byte[]
and in some cases ByteBuffer
, never String
. This
gives you the option of zeroing byte arrays out once you're done. caesium
doesn't hide the no-magic C APIs from you; but you have to understand
libsodium to use them. The upside of that is that this library provides the
APIs necessary to use libsodium
safely; e.g. with locked buffers with
canaries, secure memset, et cetera.caesium tries to just give you the libsodium experience from Clojure. C
pseudo-namespaces are mapped to real Clojure namespaces. It usually maps fns to
predictable names; sodium_crypto_secretbox_open_easy
will be called
caesium.crypto.secretbox/open-easy
. Formally: take the C pseudo-namespace,
turn it into a real namespace, replace the leading sodium
with caesium,
replace underscores with dashes. Exceptions where this doesn't work out:
crypto_generichash
(which is also the pseudo-namespace for
e.g. crypto_generichash_init
). These would be available in the
caesium.crypto.generichash
namespace, as generichash
and init
. This is
also repeated for some functions where there is a small suffix, e.g. the
function name for the "easy secretbox opener" is secretbox-easy-open
, not
easy-open
.ByteBuffer
, while others
assume byte arrays, while others rely on reflection to call the right
thing. Other pairs of functions might expect you to produce the output
buffer, or manage the output buffer for you. Since these are only JVM-level
differences, these often need different names at the JVM/Clojure
level. (This is always done as a fairly descriptive suffix.)#define
constant available are accessible as
values, they don't need to be called. For example, you can access the
crypto_generichash_KEYBYTES_MIN
constant via the libsodium
size_t crypto_generichash_keybytes_min(void);
function, but in caesium, it's just
caesium.crypto.generichash/keybytes-min
(not a function you have to call).scalarmult
in libsodium has two
functions: one with the fixed base point and one with an explicit base
point; caesium just has one function with two arities.caesium uses semver.
Since this is a security-sensitive library, I will actively remove functions or APIs that have serious security problems, instead of simply documenting the problem.
Copyright © the caesium authors (see AUTHORS)
Distributed under the Eclipse Public License either version 1.0 or (at your option) any later version.