Liking cljdoc? Tell your friends :D

buddy-core - Cryptographic Api for Clojure

Introduction

Buddy core module is dedicated to cryptographic api.

Including:

  • cryptographic hash algorithms (digest)

  • key derivation algorithms (kdf)

  • digital signatures

  • message authentication (mac)

  • block ciphers

  • stream ciphers

  • padding schemes

  • nonces and salts

  • X.509 certificates

Project Maturity

Since buddy-core is a young project there can be some API breakage.

Install

The simplest way to use buddy-core in a clojure project, is by including it in the dependency vector on your project.clj file:

[buddy/buddy-core "1.7.1"]

Or deps.edn:

buddy/buddy-core {:mvn/version "1.7.1"}

And is tested under JDK>=8.

Hash algorithms (digest)

All hash algorithms are located in the buddy.core.hash namespace.

Table 1. Available hash algorithms
Hash algorithm nameDigest size

SHA1

160

SHA2

256, 384, 512

SHA3

256, 384, 512

MD5

128

Tiger

192

Blake2b

512

Skein

256, 512, 1024, arbitrary size

Whirlpool

512

RIPEMD128

128

RIPEMD160

160

RIPEMD256

256

RIPEMD320

320

Basic usage

Import namespace example:
(require '[buddy.core.hash :as hash])
(require '[buddy.core.codecs :refer :all])
Usage examples:
(hash/sha256 "foo bar")
;; -> #<byte[] [B@162a657e>

(-> (hash/sha256 "foo bar")
    (bytes->hex))
;; -> "fbc1a9f858ea9e177916964bd88c3d37b91a1e84412765e29950777f265c4b75"

Advanced usage

Hash functions are implemented using protocols and can be extended to other types. The default implementations come with support for file-like objects (File, URL, URI* and InputStream).

Make hash of file example:
;; Additional import for easy open files
(require '[clojure.java.io :as io])

(-> (hash/sha256 (io/input-stream "/tmp/some-file"))
    (bytes->hex))
;; -> "bba878639499c8449f69efbfc699413eebfaf41d4b7a7faa560bfaf7e93a43dd"

You can extend it for your own types using the buddy.core.hash/IDigest protocol:

(defprotocol Digest
  (-digest [data engine]))

Functions like sha256 are aliases for the more generic function digest.

Mac algorithms

Buddy comes with three mac implementations: hmac, shmac and poly1305; and all them are located under buddy.core.mac namespace.

HMac

Is a specific construction for calculating a message authentication code (MAC) involving a cryptographic hash function in combination with a secret cryptographic key.

Any cryptographic hash function, such as MD5 or SHA-1, may be used in the calculation of an HMAC; the resulting MAC algorithm is termed HMAC-MD5 or HMAC-SHA1 accordingly. The cryptographic strength of the HMAC depends upon the cryptographic strength of the underlying hash function, the size of its hash output, and on the size and quality of the key.

Example generate a hmac from plain string using sha256 digest
;; Import required namespaces
(require '[buddy.core.mac :as mac])
(require '[buddy.core.codecs :as codecs])

;; Generate sha256 hmac over string
(-> (mac/hash "foo bar" {:key "mysecretkey" :alg :hmac+sha256})
    (codecs/bytes->hex))
;; => "61849448bdbb67b39d609471eead667e65b0d1b9e01b1c3bf7aa56b83e9c8083"
Example verifying a hmac
(mac/verify "foo bar" (codecs/hex->bytes "61849448bdbb67b...")
            {:key "mysecretkey" :alg :hmac+sha256})
;; => true

The key parameter can be any type that implements the ByteArray protocol defined in the buddy.core.codecs namespace. It comes with default implementations for byte[] and java.lang.String and nil.

Poly1305

Poly1305 is a cryptographic message authentication code (MAC) written by Daniel J. Bernstein. It can be used to verify the data integrity and the authenticity of a message.

The security of Poly1305 is very close to the block cipher algorithm. As a result, the only way for an attacker to break Poly1305 is to break the cipher.

Poly1305 offers cipher replaceability. If anything goes wrong with one, it can be substituted by another with identical security guarantees.

Unlike hmac, it requires an initialization vector (IV). An IV is like a salt. It should be generated using a strong random number generator for security guarantees. Also, the IV should be of the same length as the chosen cipher block size.

Example using poly1305 mac algorithm for generate mac from string
(require '[buddy.core.codecs :as codecs])
(require '[buddy.core.mac :as mac])
(require '[buddy.core.nonce :as nonce])

(def key (nonce/random-bytes 32))
(def iv (nonce/random-bytes 32))

(-> (mac/hash "some-data" {:key key :iv iv :alg :poly1305+aes})
    (codecs/bytes->hex))
;; => "1976b1c490c306e7304a59dfacee4207"

The default specification talks about AES as default block cipher but the algorith in fact can work other block ciphers without any problem. So you can use serpent and twofish among the default aes:

(-> (mac/hash "some-data" {:key key :iv iv :alg :poly1305+twofish})
    (codecs/bytes->hex))
;; => "6e7304a59dfacee42071976b1c490c30"

Advanced Usage

Generate mac for file like objects

Like with hash functions, you can use String, byte[], File, URL, URI and InputStream as input value for mac functions:

Example generating hmac from input stream
(require '[clojure.java.io :as io])

;; Generate hmac for file
(-> (io/input-stream "/tmp/somefile")
    (mac/hash {:key "mysecretkey" :alg :hmac-sha256})
    (codecs/bytes->hex))
;; => "4cb793e600848da205323800..."

Low-Level Api

Behind the scenes of the high level api, a low level api is already defined with protocols and you can use it for your purposes:

(let [engine (mac/-engine {:alg :hnac+sha256})]
  (mac/-update engine (codecs/str->bytes "hello") 0 5)
  (codecs/bytes->hex (mac/-end engine)))
;; "924c4b82a56c0115eb9..."

This also applies to the rest of mac implementations found in buddy-core library.

Keys

Reading PEM formatted keys

Before explaining digital signatures, you need to read public/private keypairs and convert them to usable objects. Buddy has limited support for reading:

  • RSA keypair

  • ECDSA keypair

RSA Keypair

An RSA keypair is obviously used for RSA encryption/decryption, but it is also used for making digital signatures with RSA-derived algorithms.

Read keys
(require '[buddy.core.keys :as keys])

;; The last parameter is optional and is only mandatory
;; if a private key is encrypted.
(def privkey (keys/private-key "test/_files/privkey.3des.rsa.pem" "secret")
(def pubkey (keys/public-key "test/_files/pubkey.3des.rsa.pem"))
Generate a RSA Keypair using openssl.
# Generate AES-256 encrypted private key
openssl genrsa -aes256 -out privkey.pem 2048

# Generate public key from previously created private key.
openssl rsa -pubout -in privkey.pem -out pubkey.pem

ECDSA Keypair

Like RSA keypairs, ECDSA is also used for making digital signatures and can be read like in the RSA examples.

Read keys.
(require '[buddy.core.keys :as keys])

;; The last parameter is optional and is only mandatory
;; if a private key is encrypted.
(def privkey (keys/private-key "test/_files/privkey.ecdsa.pem" "secret")
(def pubkey (keys/public-key "test/_files/pubkey.ecdsa.pem"))
Generate a ECDSA Keypair using openssl.
# Generate a params file
openssl ecparam -name prime256v1 -out ecparams.pem

# Generate a private key from params file
openssl ecparam -in ecparams.pem -genkey -noout -out ecprivkey.pem

# Generate a public key from private key
openssl ec -in ecprivkey.pem -pubout -out ecpubkey.pem

Json Web Key (JWK)

A JSON Web Key (JWK) is a JavaScript Object Notation (JSON) data structure that represents a cryptographic key of different types.

buddy-core provides functions for reading and saving JCA keys in JWK format

Currently supported JWK key types are

  • RSA key pairs (No RSA-CRT support yet)

  • OKP key pairs (Ed25519)

  • EC key pairs (P-256, P-384, P-521 curves)

Example of JWS signing for Ed25519 keys

(require '[buddy.core.keys :as keys])

(def edkey {:kty "OKP",
            :crv "Ed25519",
            :d "nWGxne_9WmC6hEr0kuwsxERJxWl7MmkZcDusAxyuf2A",
            :x "11qYAYKxCrfVS_7TyWQHOg7hcvPapiMlrwIaaPcHURo"})

(def privkey (keys/jwk->private-key edkey))

You can also convert from PEM to JWK like this

(require '[buddy.core.keys :as keys])

(def prv (keys/private-key "private.pem"))
(def pub (keys/public-key "public.pem"))

;; JWK requires both public and private keys for export
(def jwk (keys/jwk prv pub))
(def jwk-pub (keys/public-key->jwk pub))

You can generate and save keys in JWK format like this

(require '[buddy.core.keys :as keys])
(import 'java.security.KeyPairGenerator)
(import 'java.security.SecureRandom)

(defn generate-keypair-ed25519
  []
  (let [kg (KeyPairGenerator/getInstance "EdDSA" "EdDSA")]
    (.initialize kg
                 256
                 ;; JDK8 only, use getInstance on JDK7 (make sure it's true random source)
                 (SecureRandom/getInstanceStrong))
    (.genKeyPair kg)))

(let [pair (generate-keypair-ed25519)]
  (keys/jwk (.getPrivate pair) (.getPublic pair)))

;; =>
;; {:kty "OKP",
;;  :crv "Ed25519",
;;  :d "5q3yhCdSDMj9Za9jJE0vhfExlTV8JeSe6XnfblAFkPY",
;;  :x "JbbhB16SaghHiGHx3FutVMfVTgu9-SCtZGfZyoDZSbQ"}

You can also calculate JWK thumbprint using jwk-thumbprint function

(require '[buddy.core.keys :as keys])
(require '[buddy.core.codecs :as codecs])

(def edkey {:kty "OKP",
            :crv "Ed25519",
            :d "nWGxne_9WmC6hEr0kuwsxERJxWl7MmkZcDusAxyuf2A",
            :x "11qYAYKxCrfVS_7TyWQHOg7hcvPapiMlrwIaaPcHURo"})

(-> (keys/jwk-thumbprint edkey)
    (codecs/bytes->hex))

;; => "90facafea9b1556698540f70c0117a22ea37bd5cf3ed3c47093c1707282b4b89"

Digital Signatures

Digital Signature algorithms has similar purposes that MAC but comes with some tradeoffs such as them provides additional security feature (Non-repudiation) with cost in the performance. You can read a great explanation about the differences with MAC here.

buddy-core comes with support for: rsassa-pss, rsassa-pkcs and ecdsa.

Example signing string using rsassa-pss+sha256
(require '[buddy.core.keys :as keys])
(require '[buddy.core.dsa :as dsa])

;; Read private key
(def privkey (keys/private-key "test/_files/privkey.3des.rsa.pem" "secret"))

;; Make signature
(def signature (dsa/sign "foo" {:key privkey :alg :rsassa-pss+sha256}))

;; Now signature contains a byte[] with signature of "foo" string
Example verifying signature
;; Read public key
(def pubkey (keys/public-key "test/_files/pubkey.3des.rsa.pem"))

;; Make verification
(dsa/verify "foo" signature {:key pubkey :alg :rsassa-pss+sha256})
;; => true

Here is a table with complete list of supported algorithms and its variants:

Algorithm name:alg keyword value

RSASSA-PSS

:rsassa-pss+sha256, :rsassa-pss+sha384, :rsassa-pss+sha512

RSASSA-PKCS 1.5

:rsassa-pkcs15+sha256, :rsassa-pkcs15+sha384, :rsassa-pkcs15+sha512

ECDSA

:ecdsa+sha256, :ecdsa+sha384, :ecdsa+sha512

ECDSA algorithm requires EC type of asymentric key pair.

Key Derivation Functions (KDF)

Key derivation functions are often used in conjunction with non-secret parameters to derive one or more keys from a common secret value.

buddy commes with several of them:

Table 2. Supported key derivation functions.
Name:alg valueDescription

HKDF

:hkdf+sha256, :hkdf+sha384, :hkdf+sha512

HMAC-based Extract-and-Expand Key Derivation Function

KDF1

:kdf1+sha256, :kdf1+sha384, :kdf1+sha512

KDF v1

KDF2

:kdf2+sha256, :kdf2+sha384, :kdf2+sha512

KDF v2

CMKDF

:cmkdf+sha256, :cmkdf+sha384, :cmkdf+sha512

Counter-Mode key derivation function (as defined in NIST SP800-108)

FMKDF

:fmkdf+sha256, :fmkdf+sha384, :fmkdf+sha512

Feedback-Mode key derivation function (as defined in NIST SP800-108)

DPIMKDF

:dpimkdf+sha256, :dpimkdf+sha384, :dpimkdf+sha512

Double-Pipeline Iteration Mode key derivation function (as defined in NIST SP800-108)

PBKDF2

:pbkdf2+sha256, :pbkdf2+sha384, :pbkdf2+sha512

Password-Based Key Derivation Function 2 (a.k.a. RSA PKCS #5 v2.0, also published in RFC 2898)

Example using KDF with HKDF key derivation function
(require '[buddy.core.codecs :as codecs])
(require '[buddy.core.kdf :as kdf])

;; Using hkdf derivation functions. It requires a
;; key, salt and optionally info field that can
;; contain any random data.

(def hkdf (kdf/engine {:alg :hkdf+sha256
                       :key "mysecret"
                       :salt "mysalt"}))

(-> (kdf/get-bytes hkdf 8)
    (codecs/bytes->hex))
;; => "0faba553152fce4f"


;; Or using different digest algorithm:

(def hkdf (kdf/engine {:alg :hkdf
                       :digest :blake2b-512
                       :key "test"
                       :salt "test"}))

(-> (kdf/get-bytes hkdf 8)
    (codecs/bytes->hex))
;; => "9d22728d54e549a6"
Example using PBKDF2 with sha256
(def pbkdf2 (kdf/engine {:key "my password"
                         :salt (nonce/random-bytes 8)
                         :alg :pbkdf2
                         :digest :sha256
                         :iterations 1}))

(-> (kdf/get-bytes pbkdf2 8)
    (codecs/bytes->hex))
;; => "26606ebf3a4bb4b3"

PBKDF2 works slightly different to the rest of KDF implementations. You should pass the number of iterations explicltly and get-bytes always returns the same value in contrast to the others where get-bytes works as consumer of infinite stream.

;; Note the same output for multiple requests:

(-> (kdf/get-bytes pbkdf2 8)
    (codecs/bytes->hex))
;; => "26606ebf3a4bb4b3"

(-> (kdf/get-bytes pbkdf2 8)
    (codecs/bytes->hex))
;; => "26606ebf3a4bb4b3"

;; Note that each request returns the next
;; bytes of the stream:

(-> (kdf/get-bytes hkdf 8)
    (codecs/bytes->hex))
;; => "d42edcfc40c860ce"

(-> (kdf/get-bytes hkdf 8)
    (codecs/bytes->hex))
;; => "353ce2240159c094"

This is a low-level kdf primitive and if you want a password hasher, please use buddy-hashers module instead of this.

Ciphers

Ciphers support in buddy is available on buddy.core.crypto namespace.

Block Ciphers

In cryptography, a block cipher is a deterministic algorithm operating on fixed-length groups of bits, called blocks, with an unvarying transformation that is specified by a symmetric key.

Table 3. This is a list of currently supported block ciphers in buddy
Algorithm nameKeywords

AES

:aes

Twofish

:twofish

Blowfish

:blowfish

Additionally, for good security, is mandatory to combine a block cipher with some cipher mode of operation.

Table 4. This is a list of currently supported of cipher mode of operation
Algorithm nameKeywords

SIC (CTR)

:ctr, :sic

CBC

:cbc

OFB

:ofb

GCM

:gcm

currently buddy comes with limited number of ciphers and modes, but in near future more many more options should be added.
Example encrypt
(require '[buddy.core.crypto :as crypto])
(require '[buddy.core.nonce :as nonce])
(require '[buddy.core.codecs :as codecs])

(let [eng   (crypto/block-cipher :twofish :cbc)
      iv16  (nonce/random-nonce 16)
      key32 (nonce/random-nonce 32)
      data  (codecs/hex->bytes "000000000000000000000000000000AA")]
  (crypto/init! eng {:key key32 :iv iv16 :op :encrypt})
  (crypto/process-block! eng data))

;; => #<byte[] [B@efadff9>

AEAD mode of operations also exposes additional function for caluclate the total size of the output including the authentication tag: output-size.

Stream Ciphers

Stream ciphers differ from block ciphers, in that they works with arbitrary length input and do not require any additional mode of operation.

Table 5. This is a list of currently supported of stream ciphers in buddy
Algorithm nameKeywords

ChaCha

:chacha

Example encrypt
(require '[buddy.core.crypto :as crypto])
(require '[buddy.core.codecs :as codecs])
(require '[buddy.core.nonce :as nonce])

(let [eng   (crypto/stream-cipher :chacha)
      iv8   (nonce/random-nonce 8)
      key32 (nonce/random-nonce 32)
      data  (codecs/hex->bytes "0011")]
  (crypto/init! eng {:key key32 :iv iv8 :op :encrypt})
  (crypto/process-bytes! eng data))

;; => #<byte[] [B@efadff9>
the iv and key size depends estrictly on cipher engine, in this case, chacha engine requires 8 bytes iv.
for decrypt, only change :op value to :decrypt

You can call crypto/initialize! any times as you want, it simply reinitializes the engine.

High level encryption schemes

Since version 0.6.0, buddy-core comes with high level crypto interface that allows user encrypt arbitrary length data using one of the well established encryption schemes.

The api consists in two simple functions. Let see an example of how to encrypt arbitrary length text and decrypt it:

(require '[buddy.core.crypto :as crypto])
(require '[buddy.core.codecs :as codecs])
(require '[buddy.core.nonce :as nonce])
(require '[buddy.core.hash :as hash])

(def original-text
  (codecs/to-bytes "Hello World."))

(def iv (nonce/random-bytes 16))   ;; 16 bytes random iv
(def key (hash/sha256 "mysecret")) ;; 32 bytes key

;; Encrypt the original-text content using previously
;; declared iv and key.
(def encrypted (crypto/encrypt original-text key iv
                               {:algorithm :aes128-cbc-hmac-sha256}))

;; And now, decrypt it using the same parameters:
(-> (crypto/decrypt encrypted key iv {:algorithm :aes128-cbc-hmac-sha256})
    (codecs/bytes->str))
;; => "Hello World."

This is a complete list of supported encryption schemes:

  • :aes128-cbc-hmac-sha256 (default)

  • :aes192-cbc-hmac-sha384

  • :aes256-cbc-hmac-sha512

  • :aes128-gcm

  • :aes192-gcm

  • :aes256-gcm

Paddings

Padding schemes are often used for fill the empty bytes of byte array of data to an concrete blocksize.

Table 6. This is a list of currently supported padding schemes
Algorithm nameKeywords

Zero Byte

:zerobyte

PKCS7

:pkcs7

TBC

:tbc

Let see an example on how to use it:

(require '[buddy.core.padding :as padding])
(require '[buddy.core.bytes :as bytes])

(def data (byte-array 10))

;; Fill the array with byte value 10
(bytes/fill! data 10)

;; Add padding to the byte array with offset value: 7
;; This is a side effect and it will mutate the data
;; byte array.

(vec (padding/pad! data 7 :pkcs7))
;; =>[10 10 10 10 10 10 10 3 3 3]

;; Also it has the side effect free version of it, that
;; returns a new byte array.

(vec (padding/pad data 7 :pkcs7))
;; =>[10 10 10 10 10 10 10 3 3 3]


;; Show the size of applied padding
(padding/count data :pkcs7)
;; => 3

;; Remove the padding
(vec (padding/unpad data 7 :pkcs7))
;; =>[10 10 10 10 10 10 10 0 0 0]

The default padding scheme is :pkcs7 and that parameter can be ommited.

Nonces and Salts

This library comes with helpers for generate random salts and cryptographically secure nonces:

Generate a cryptographically secure nonce
(require '[buddy.core.nonce :as nonce])

(vec (nonce/random-nonce 16))
;; => [0 0 1 75 -114 49 -91 107 67 -124 -49 -2 -96 100 42 18]

(vec (nonce/random-nonce 16))
;; => [0 0 1 75 -114 49 -88 -102 92 88 111 69 46 93 1 -86]

The random-nonce function returns a byte array with minimum length of 8 bytes, because is the size of the current time in miliseconds.

Generate a cryptographically secure salt
(require '[buddy.core.nonce :as nonce])

(vec (nonce/random-bytes 16))
;; =>[-50 20 -120 -38 -32 -121 -15 109 86 -99 85 -73 28 -92 -67 -64]

(vec (nonce/random-bytes 16))
;; => [84 -88 51 120 122 -30 78 -31 -96 -22 119 122 29 -54 -64 -73]

Like random-nonce function, random-bytes returns a byte array but it not have the limitation of minimum 8 bytes of size.

Codecs & Bytes

This library comes with helpers for working with codecs (hex, base64, …​) and byte arrays.

This is a brief list of available functions:

Table 7. Available hash algorithms
Namespace/FunctionDescription

buddy.core.codecs/str→bytes

Converts a string into byte array

buddy.core.codecs/bytes→str

Converts byte array to string using UTF8 encoding

buddy.core.codecs/bytes→hex

Converts byte array to hexadecimal string

buddy.core.codecs/hex→bytes

Converts hexadecimal strings into byte array

buddy.core.codecs/long→bytes

Get byte array representation of long

buddy.core.codecs/bytes→long

Get long from byte array

buddy.core.bytes/bytes?

Predicate for test byte arrays

buddy.core.bytes/fill!

Fill byte array with data

buddy.core.bytes/slice

Create a new byte array as slice of other

buddy.core.bytes/copy

Copy the byte array.

buddy.core.bytes/equals?

Constant time equals predicate for byte arrays

buddy.core.bytes/concat

Concat two or more byte arrays

X.509 Certificates

Support for basic certificate handling is available in the buddy.core.certificates namespace.

You can load certificates, check date validity, and check to see if a certificate is signed by a known public key.

(require '[buddy.core.certificates :as certs])

(def cert (certs/certificate "path/to/certificate.crt"))
;; => #object[org.bouncycastle.cert.X509CertificateHolder 0x2919034b "org.bouncycastle.cert.X509CertificateHolder@1612eab1"]
(certs/valid-on-date? cert)
;; => true if today is between not-before and not-after

(certs/verify-signature cert (certs/certificate "path/to/ca.crt"))
;; => true if cert is signed by public key in ca.crt

FAQ

Buddy has own cryptographic algorithms implementations?

Mainly no, I’m not cryptography expert and for this I rely on the to battle tested Bouncy Castle java library that’s dedicated to this purpose.

Buddy will support pgp?

Surely not! Because there already exists one good library for that.

Unexpected exceptions when application is run from uberjar?

This is known problem of BouncyCastle. This is because, some parts of buddy uses the BC provider that BouncyCastle exposes. And any security providers for the JDK should be signed. And if you repackage all dependencies of your application in one unique jar, it will not match the signature of BC provider, and then, jdk will silently rejects adding it.

Take care that only very small part of buddy-core is subject to this issue. Only the buddy.core.dsa and buddy.core.keys (partially) are using the security provider. So if you are using it, you will need to provide the bouncy castle dependency separatelly to your uberjar bundle.

A common approach for this case, is just put :uberjar-exclusions [#"org/bouncycastle"] on your :uberjar profile and then, download the bouncycastle jars and expose them in the classpath. If you are running your application directly from lein, you are not affected by this issue.

Developers Guide

Contributing

Unlike Clojure and other Clojure contributed libraries buddy-core does not have many restrictions for contributions. Just open an issue or pull request.

Philosophy

Five most important rules:

  • Beautiful is better than ugly.

  • Explicit is better than implicit.

  • Simple is better than complex.

  • Complex is better than complicated.

  • Readability counts.

All contributions to buddy-core should keep these important rules in mind.

Source Code

buddy-core is open source and can be found on github.

You can clone the public repository with this command:

git clone https://github.com/funcool/buddy-core

Run tests

For running tests just execute this:

lein test-all

License

buddy-core is licensed under Apache 2.0 License. You can see the complete text of the license on the root of the repository on LICENSE file.

Can you improve this documentation? These fine people already did:
Andrey Antukh, Ryan Fowler, David Harrigan, Mikhail Gusarov, Eduardo Borges & Dan McKinley
Edit on GitHub

cljdoc is a website building & hosting documentation for Clojure/Script libraries

× close