[buddy/buddy-core "1.7.1"]
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
Since buddy-core is a young project there can be some API breakage.
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.
All hash algorithms are located in the buddy.core.hash
namespace.
Hash algorithm name | Digest 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 |
(require '[buddy.core.hash :as hash])
(require '[buddy.core.codecs :refer :all])
(hash/sha256 "foo bar")
;; -> #<byte[] [B@162a657e>
(-> (hash/sha256 "foo bar")
(bytes->hex))
;; -> "fbc1a9f858ea9e177916964bd88c3d37b91a1e84412765e29950777f265c4b75"
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).
;; 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. |
Buddy comes with three mac implementations: hmac, shmac and poly1305; and
all them are located under buddy.core.mac
namespace.
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.
;; 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"
(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 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.
(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"
Like with hash functions, you can use String, byte[], File, URL, URI and InputStream as input value for mac functions:
(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..."
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.
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
An RSA keypair is obviously used for RSA encryption/decryption, but it is also used for making digital signatures with RSA-derived algorithms.
(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 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
Like RSA keypairs, ECDSA is also used for making digital signatures and can be read like in the RSA examples.
(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 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
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, secp256k1, 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))
(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 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.
(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
;; 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-PKCS 1.5 |
|
ECDSA |
|
ECDSA algorithm requires EC type of asymentric key pair. |
Key derivation functions are often used in conjunction with non-secret parameters to derive one or more keys from a common secret value.
buddy comes with several of them:
Name | :alg value | Description |
---|---|---|
HKDF |
| HMAC-based Extract-and-Expand Key Derivation Function |
KDF1 |
| KDF v1 |
KDF2 |
| KDF v2 |
CMKDF |
| Counter-Mode key derivation function (as defined in NIST SP800-108) |
FMKDF |
| Feedback-Mode key derivation function (as defined in NIST SP800-108) |
DPIMKDF |
| Double-Pipeline Iteration Mode key derivation function (as defined in NIST SP800-108) |
PBKDF2 |
| Password-Based Key Derivation Function 2 (a.k.a. |
(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"
(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
|
This is a low-level kdf primitive and if you want a password hasher, please use
|
Ciphers support in buddy is available on buddy.core.crypto
namespace.
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.
Algorithm name | Keywords |
---|---|
AES |
|
Twofish |
|
Blowfish |
|
Additionally, for good security, is mandatory to combine a block cipher with some cipher mode of operation.
Algorithm name | Keywords |
---|---|
SIC (CTR) |
|
CBC |
|
OFB |
|
GCM |
|
currently buddy comes with limited number of ciphers and modes, but in near future more many more options should be added. |
(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 differ from block ciphers, in that they works with arbitrary length input and do not require any additional mode of operation.
Algorithm name | Keywords |
---|---|
ChaCha |
|
(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.
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
Padding schemes are often used for fill the empty bytes of byte array of data to an concrete blocksize.
Algorithm name | Keywords |
---|---|
Zero Byte |
|
PKCS7 |
|
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.
This library comes with helpers for generate random salts and cryptographically secure nonces:
(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.
(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.
This library comes with helpers for working with codecs (hex, base64, …) and byte arrays.
This is a brief list of available functions:
Namespace/Function | Description |
---|---|
| Converts a string into byte array |
| Converts byte array to string using UTF8 encoding |
| Converts byte array to hexadecimal string |
| Converts hexadecimal strings into byte array |
| Get byte array representation of long |
| Get long from byte array |
| Predicate for test byte arrays |
| Fill byte array with data |
| Create a new byte array as slice of other |
| Copy the byte array. |
| Constant time equals predicate for byte arrays |
| Concat two or more byte arrays |
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
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.
Surely not! Because there already exists one good library for that.
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.
Unlike Clojure and other Clojure contributed libraries buddy-core does not have many restrictions for contributions. Just open an issue or pull request.
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.
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
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, Stanislav Yurin, Arte Ebrahimi, Eduardo Borges & Dan McKinleyEdit on GitHub
cljdoc is a website building & hosting documentation for Clojure/Script libraries
× close