├── .gitignore ├── project.clj ├── test └── clj_crypto │ └── test │ ├── symmetric_algorithm.clj │ └── core.clj ├── README.md └── src └── clj_crypto ├── symmetric_algorithm.clj └── core.clj /.gitignore: -------------------------------------------------------------------------------- 1 | /pom.xml 2 | *jar 3 | /lib 4 | /classes 5 | /native 6 | /.lein-failures 7 | /checkouts 8 | .classpath 9 | .project 10 | target 11 | *~ 12 | /.lein-deps-sum 13 | /target 14 | pom.xml.asc 15 | /bin 16 | -------------------------------------------------------------------------------- /project.clj: -------------------------------------------------------------------------------- 1 | (defproject clj-crypto "1.0.3-SNAPSHOT" 2 | :description "Clj-crypto is a wrapper for Bouncy Castle which allows you to easily use cryptography in your clojure app." 3 | :dependencies [[org.bouncycastle/bcprov-jdk15on "1.55"] 4 | [commons-codec/commons-codec "1.5"] 5 | [org.clojure/tools.logging "0.3.0"]]) 6 | -------------------------------------------------------------------------------- /test/clj_crypto/test/symmetric_algorithm.clj: -------------------------------------------------------------------------------- 1 | (ns clj-crypto.test.symmetric-algorithm 2 | (:use [clj-crypto.symmetric-algorithm] 3 | [clojure.test]) 4 | (:import [java.security KeyPair])) 5 | 6 | (deftest password-encrypt-decrypt 7 | (let [password "password" 8 | data "secret text" 9 | algorithm des-algorithm 10 | encrypted-data (password-encrypt password data algorithm)] 11 | (is (not (= data encrypted-data)) "Text not encrypted.") 12 | (is (= data (password-decrypt password encrypted-data algorithm)) "Text not decrypted.")) 13 | (let [password "password blah blah blah blah blah blah blah blah" 14 | data "secret text" 15 | algorithm triple-des-algorithm 16 | encrypted-data (password-encrypt password data algorithm)] 17 | (is (not (= data encrypted-data)) "Text not encrypted.") 18 | (is (= data (password-decrypt password encrypted-data algorithm)) "Text not decrypted."))) 19 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # clj-crypto 2 | 3 | The clj-crypto library is a Clojure wrapper for the Java Bouncy Castle encryption library. 4 | 5 | 6 | ## Installation 7 | `clj-crypto` is available is available as a Maven artifact from 8 | ![Clojars Project](https://img.shields.io/clojars/v/clj-crypto.svg) 9 | 10 | To use, simply add 11 | `[clj-crypto "1.0.2"]` 12 | to your `project.clj` 13 | 14 | ## Usage 15 | 16 | ```clojure 17 | (:require [clj-crypto.core :as crypto]) 18 | ``` 19 | 20 | To read your pkcs12 certificate store: 21 | 22 | ```clojure 23 | (def keypair (crypto/get-key-pair-pkcs12 pkcs12-store passwd alias)) 24 | ``` 25 | This will use the 'Bouncy Castle' as your crypto-provider. 26 | If you want to use another, you can supply that as well like so: 27 | 28 | ```clojure 29 | (def keypair (crypto/get-key-pair-pkcs12 pkcs12-store passwd alias crypto/sun-provider)) ; use SunJSSE 30 | ``` 31 | To obtain your private-key from a key-pair, drop in to java, like so: 32 | 33 | ```clojure 34 | (def private-key (.getPrivate keypair) 35 | ``` 36 | 37 | To sign a message with your private key: 38 | 39 | ```clojure 40 | (crypto/sign private-key msg 41 | crypto/sha256-signature-algorithm 42 | crypto/default-provider) 43 | ``` 44 | 45 | Of course, you also have your standard functions for base64 encoding 46 | ```clojure 47 | (crypto/decode-base64 (crypto/encode-base64-as-str (.getBytes "sikrit"))) 48 | ``` 49 | 50 | 51 | ## License 52 | 53 | Copyright (C) 2012 Matt Courtney 54 | 55 | Distributed under the Eclipse Public License, the same as Clojure. 56 | -------------------------------------------------------------------------------- /src/clj_crypto/symmetric_algorithm.clj: -------------------------------------------------------------------------------- 1 | (ns clj-crypto.symmetric-algorithm 2 | (:require [clj-crypto.core :as core]) 3 | (:import [java.security MessageDigest] 4 | [javax.crypto SecretKeyFactory] 5 | [javax.crypto.spec DESedeKeySpec DESKeySpec])) 6 | 7 | ;http://stackoverflow.com/questions/339004/java-encrypt-decrypt-user-name-and-password-from-a-configuration-file 8 | 9 | (def des-algorithm "DES") 10 | (def triple-des-algorithm "DESede") ; Triple DES algorithm 11 | 12 | (def default-symmetrical-algorithm triple-des-algorithm) 13 | 14 | (defn prepare-password 15 | ([password] (prepare-password password 24)) 16 | ([password byte-length] 17 | (let [message-digest (MessageDigest/getInstance core/default-encrypt-password-algorithm)] 18 | (.reset message-digest) 19 | (into-array Byte/TYPE (take byte-length (.digest message-digest (core/get-data-bytes password))))))) 20 | 21 | (defn des-key-spec 22 | "Generates a des key spec from the given password." 23 | [password] 24 | (DESKeySpec. (prepare-password password 8))) 25 | 26 | (defn triple-des-key-spec 27 | "Generates a triple des key spec from the given password." 28 | [password] 29 | (DESedeKeySpec. (prepare-password password 24))) 30 | 31 | (defn des-key 32 | "Creates a des key from the given password." 33 | [password] 34 | (.generateSecret (SecretKeyFactory/getInstance des-algorithm) (des-key-spec password))) 35 | 36 | (defn triple-des-key 37 | "Creates a des key from the given password." 38 | [password] 39 | (.generateSecret (SecretKeyFactory/getInstance triple-des-algorithm) (triple-des-key-spec password))) 40 | 41 | (defprotocol SymmetricAlgorithm 42 | "A protocol for symmetric encryption algorithms." 43 | (algorithm [this] "Returns the algorithm string for this algorithm.") 44 | (encrypt [this password data] "Encrypts the given data with the given password.") 45 | (decrypt [this password data] "Decrypts the given data with the given password.")) 46 | 47 | (deftype DES [] 48 | SymmetricAlgorithm 49 | (algorithm [_] des-algorithm) 50 | (encrypt [_ password data] (core/encrypt (des-key password) data (core/create-cipher des-algorithm))) 51 | (decrypt [_ password data] (core/decrypt (des-key password) data (core/create-cipher des-algorithm)))) 52 | 53 | (deftype TripleDES [] 54 | SymmetricAlgorithm 55 | (algorithm [_] triple-des-algorithm) 56 | (encrypt [_ password data] (core/encrypt (triple-des-key password) data (core/create-cipher triple-des-algorithm))) 57 | (decrypt [_ password data] (core/decrypt (triple-des-key password) data (core/create-cipher triple-des-algorithm)))) 58 | 59 | (def des-algorithm-type (new DES)) 60 | (def triple-des-algorithm-type (new TripleDES)) 61 | 62 | (def symetric-algorithms 63 | { des-algorithm des-algorithm-type 64 | triple-des-algorithm triple-des-algorithm-type }) 65 | 66 | (defn find-symetric-algorithm [algorithm] 67 | (cond 68 | (string? algorithm) (get symetric-algorithms algorithm) 69 | (instance? SymmetricAlgorithm algorithm) algorithm 70 | :else (throw (RuntimeException. (str "Unknown algorithm type: " algorithm))))) 71 | 72 | (defn password-encrypt 73 | ([password data] (password-encrypt password data default-symmetrical-algorithm)) 74 | ([password data algorithm] 75 | (encrypt (find-symetric-algorithm algorithm) password data))) 76 | 77 | (defn password-decrypt 78 | ([password data] (password-decrypt password data default-symmetrical-algorithm)) 79 | ([password data algorithm] 80 | (decrypt (find-symetric-algorithm algorithm) password data))) 81 | -------------------------------------------------------------------------------- /test/clj_crypto/test/core.clj: -------------------------------------------------------------------------------- 1 | (ns clj-crypto.test.core 2 | (:refer-clojure :exclude [format]) 3 | (:use [clj-crypto.core] 4 | [clojure.test]) 5 | (:import [java.security KeyPair])) 6 | 7 | (deftest test-encrypt-decrypt 8 | (let [key-pair (generate-key-pair) 9 | data "secret text"] 10 | (is key-pair "key-pair is nil, but expected non-nil.") 11 | (let [encrypted-text (encrypt key-pair data)] 12 | (is encrypted-text "encrypted-text is nil, but expected non-nil.") 13 | (let [decrypted-text (decrypt key-pair encrypted-text)] 14 | (is decrypted-text "decrypted-text is nil, but expected non-nil.") 15 | (is (= decrypted-text data) (str "Text not decrypted.")))))) 16 | 17 | (deftest basic-password-protection 18 | (let [password "password" 19 | salt 2079324 20 | algorithm default-encrypt-password-algorithm 21 | n default-encrypt-password-n 22 | encrypted-password (encrypt-password-string password salt algorithm n)] 23 | (is (not (= encrypted-password password)) "Password not encrypted") 24 | (is (= encrypted-password (encrypt-password-string password salt algorithm n)) "Password check not valid.") 25 | (is (not (= encrypted-password (encrypt-password-string password salt algorithm 1))) "Multiple hash iterations do not help.") 26 | (is (not (= encrypted-password (encrypt-password-string (.substring password 0 4) salt algorithm n))) "Short password works when it shouldn't."))) 27 | 28 | (defn byte-not-equals [byte1 byte2] 29 | (not (= byte1 byte2))) 30 | 31 | (defn byte-array-equals [byte-array1 byte-array2] 32 | (and 33 | (= (count byte-array1) (count byte-array2)) 34 | (nil? (some identity (map byte-not-equals byte-array1 byte-array2))))) 35 | 36 | (defn key-equals? [key1 key2] 37 | (and 38 | (= (.getAlgorithm key1) (.getAlgorithm key2)) 39 | (= (.getFormat key1) (.getFormat key2)) 40 | (byte-array-equals (.getEncoded key1) (.getEncoded key2)))) 41 | 42 | (defn key-pair-equals? [key-pair1 key-pair2] 43 | (and 44 | (key-equals? (.getPublic key-pair1) (.getPublic key-pair2)) 45 | (key-equals? (.getPrivate key-pair1) (.getPrivate key-pair2)))) 46 | 47 | (deftest save-load-key-pairs 48 | (let [key-pair (generate-key-pair) 49 | key-pair-map (get-key-pair-map key-pair)] 50 | (is key-pair-map "Expected non-nil key-pair-map.") 51 | (is (map? key-pair-map) "key-pair-map must be a map") 52 | (is (contains? key-pair-map :public-key) "key-pair-map must contain the :public-key key") 53 | (is (contains? key-pair-map :private-key) "key-pair-map must contain the :private-key key") 54 | (let [public-key (:public-key key-pair-map) 55 | private-key (:private-key key-pair-map)] 56 | (is (map? public-key) "Public key must be a map.") 57 | (is (map? private-key) "private key must be a map.") 58 | (is (= (:algorithm public-key) default-algorithm) "The public key algorithm must be the default algorithm.") 59 | (is (= (:algorithm private-key) default-algorithm) "The private key algorithm must be the default algorithm.") 60 | (is (contains? public-key :bytes) "The public key map must contain the :bytes key.") 61 | (is (contains? private-key :bytes) "The private key map must contain the :bytes key.")) 62 | (let [decoded-key-pair (decode-key-pair key-pair-map)] 63 | (is decoded-key-pair "Expected non-nil decoded-key-pair") 64 | (is (instance? KeyPair decoded-key-pair) "decode-key-pair must return an object of type KeyPair.") 65 | (is (key-pair-equals? key-pair decoded-key-pair) "The decoded key is not equal to the original key.")))) 66 | 67 | (deftest signature 68 | (let [test-data "Test data to sign" 69 | key-pair (generate-key-pair) 70 | signature (sign key-pair test-data)] 71 | (is (verify-signature key-pair test-data signature)) 72 | (is (not (verify-signature (generate-key-pair) test-data signature))))) 73 | -------------------------------------------------------------------------------- /src/clj_crypto/core.clj: -------------------------------------------------------------------------------- 1 | (ns clj-crypto.core 2 | (:refer-clojure :exclude [format]) 3 | (:require [clojure.java.io]) 4 | (:import [org.apache.commons.codec.binary Base64] 5 | [org.bouncycastle.jce.provider BouncyCastleProvider] 6 | [java.security Key KeyFactory KeyPair KeyPairGenerator MessageDigest PrivateKey PublicKey Security Signature KeyStore] 7 | [java.security.spec PKCS8EncodedKeySpec X509EncodedKeySpec] 8 | [java.util Random] 9 | [java.io InputStream] 10 | [javax.crypto Cipher] 11 | [java.nio ByteBuffer])) 12 | 13 | (def default-algorithm "RSA") 14 | (def default-signature-algorithm "SHA1withRSA") 15 | (def sha256-signature-algorithm "SHA256withRSA") 16 | (def default-transformation "RSA/None/NoPadding") 17 | (def default-provider "BC") ; Bouncy Castle provider. 18 | (def sun-provider "SunJSSE") 19 | (def default-character-encoding "UTF8") 20 | (def default-key-size 2048) 21 | 22 | (def default-encrypt-password-algorithm "SHA-256") 23 | (def default-encrypt-password-n 1000) 24 | 25 | (Security/addProvider (new BouncyCastleProvider)) 26 | 27 | (defn encode-base64 [bindata] 28 | (Base64/encodeBase64 bindata)) 29 | 30 | (defn encode-base64-as-str [bindata] 31 | (Base64/encodeBase64String bindata)) 32 | 33 | (defn decode-base64 [^String base64str] 34 | (Base64/decodeBase64 base64str)) 35 | 36 | (defn generate-key-pair [& {:keys [key-size algorithm]}] 37 | (let [key-pair-generator (KeyPairGenerator/getInstance (or algorithm default-algorithm))] 38 | (.initialize key-pair-generator (or key-size default-key-size)) 39 | (.generateKeyPair key-pair-generator))) 40 | 41 | (defn private-key [key-pair] 42 | (.getPrivate key-pair)) 43 | 44 | (defn as-private-key [key] 45 | (cond 46 | (instance? KeyPair key) (private-key key) 47 | (instance? PrivateKey key) key 48 | true (throw (RuntimeException. (str "Don't know how to convert to private key: " key))))) 49 | 50 | (defn public-key [key-pair] 51 | (.getPublic key-pair)) 52 | 53 | (defn as-public-key [key] 54 | (cond 55 | (instance? KeyPair key) (public-key key) 56 | (instance? PublicKey key) key 57 | :else (throw (RuntimeException. (str "Don't know how to convert to public key: " key))))) 58 | 59 | (defn algorithm [key] 60 | (.getAlgorithm key)) 61 | 62 | (defn encoded [key] 63 | (.getEncoded key)) 64 | 65 | (defn format [key] 66 | (.getFormat key)) 67 | 68 | (defn create-cipher 69 | ([] (create-cipher default-transformation default-provider)) 70 | ([transformation] (create-cipher transformation default-provider)) 71 | ([transformation provider] 72 | (Cipher/getInstance transformation provider))) 73 | 74 | (defn integer-byte [integer byte-offset] 75 | (let [short-int (bit-and 0xff (bit-shift-right integer (* byte-offset 8)))] 76 | (if (< short-int 128) 77 | (byte short-int) 78 | (byte (- short-int 256))))) 79 | 80 | (defn integer-bytes [integer] 81 | (byte-array [(integer-byte integer 3) (integer-byte integer 2) (integer-byte integer 1) (integer-byte integer 0)])) 82 | 83 | (defn long-bytes [l] 84 | (let [buf (ByteBuffer/allocate (/ Long/SIZE 8))] 85 | (.putLong buf l) 86 | (.array buf))) 87 | 88 | (defn get-data-bytes [data] 89 | (cond 90 | (= Byte/TYPE (.getComponentType (class data))) data 91 | (string? data) (.getBytes data default-character-encoding) 92 | (instance? Integer data) (integer-bytes data) ; Must use instance since integer? includes Longs as well as Integers. 93 | (instance? Long data) (long-bytes data) 94 | :else (throw (RuntimeException. (str "Do not know how to convert a " (class data) " to a byte array."))))) 95 | 96 | (defn get-data-str [data] 97 | (if (instance? String data) 98 | data 99 | (String. data default-character-encoding))) 100 | 101 | (defn get-encrypt-key [key] 102 | (if (instance? KeyPair key) 103 | (.getPublic key) 104 | key)) 105 | 106 | (defn do-cipher [cipher mode key data] 107 | (.init cipher mode key) 108 | (.doFinal cipher (get-data-bytes data))) 109 | 110 | (defn encrypt 111 | ([key data] (encrypt key data (create-cipher))) 112 | ([key data cipher] 113 | (do-cipher cipher Cipher/ENCRYPT_MODE (get-encrypt-key key) data))) 114 | 115 | (defn get-decrypt-key [key] 116 | (if (instance? KeyPair key) 117 | (.getPrivate key) 118 | key)) 119 | 120 | (defn decrypt 121 | ([key data] (decrypt key data (create-cipher))) 122 | ([key data cipher] 123 | (get-data-str (do-cipher cipher Cipher/DECRYPT_MODE (get-decrypt-key key) data)))) 124 | 125 | ; Save and Load keypairs 126 | 127 | (defn get-public-key-map [public-key] 128 | { :algorithm (.getAlgorithm public-key) 129 | :bytes (.getEncoded (X509EncodedKeySpec. (.getEncoded public-key))) }) 130 | 131 | (defn get-private-key-map [private-key] 132 | { :algorithm (.getAlgorithm private-key) 133 | :bytes (.getEncoded (PKCS8EncodedKeySpec. (.getEncoded private-key))) }) 134 | 135 | (defn get-key-pair-map [key-pair] 136 | { :public-key (get-public-key-map (.getPublic key-pair)) 137 | :private-key (get-private-key-map (.getPrivate key-pair))}) 138 | 139 | (defn get-key-pair-pkcs12 140 | ([keystore ks-password entry-alias] (get-key-pair-pkcs12 keystore ks-password entry-alias default-provider)) 141 | ([keystore ks-password entry-alias provider] 142 | (cond 143 | (instance? String keystore) 144 | (with-open [fio (clojure.java.io/input-stream keystore)] 145 | (get-key-pair-pkcs12 fio ks-password entry-alias)) 146 | (instance? InputStream keystore) 147 | (let [ks (KeyStore/getInstance "PKCS12" provider)] 148 | (do (.load ks keystore (.toCharArray ks-password)) 149 | (KeyPair. (-> ks (.getCertificate entry-alias) (.getPublicKey)) 150 | (-> ks (.getKey entry-alias (.toCharArray ks-password)))))) 151 | :else 152 | (throw (RuntimeException. (str "Do not know how to load keystore from a " (class keystore))))))) 153 | 154 | (defn decode-public-key [public-key-map] 155 | (when public-key-map 156 | (when-let [key-bytes (:bytes public-key-map)] 157 | (when-let [algorithm (:algorithm public-key-map)] 158 | (.generatePublic (KeyFactory/getInstance algorithm) (X509EncodedKeySpec. key-bytes)))))) 159 | 160 | (defn decode-private-key [private-key-map] 161 | (.generatePrivate (KeyFactory/getInstance (:algorithm private-key-map)) 162 | (PKCS8EncodedKeySpec. (:bytes private-key-map)))) 163 | 164 | (defn decode-key-pair [key-pair-map] 165 | (KeyPair. (decode-public-key (:public-key key-pair-map)) (decode-private-key (:private-key key-pair-map)))) 166 | 167 | ; Signing 168 | 169 | (defn sign 170 | ([key data] (sign key data default-signature-algorithm default-provider)) 171 | ([key data algorithm provider] 172 | (let [private-key (as-private-key key) 173 | signature (Signature/getInstance algorithm provider)] 174 | (.initSign signature private-key) 175 | (.update signature (get-data-bytes data)) 176 | (.sign signature)))) 177 | 178 | (defn verify-signature 179 | ([key data signature] (verify-signature key data signature default-signature-algorithm default-provider)) 180 | ([key data signature algorithm provider] 181 | (let [public-key (as-public-key key) 182 | signature-obj (Signature/getInstance algorithm provider)] 183 | (.initVerify signature-obj public-key) 184 | (.update signature-obj (get-data-bytes data)) 185 | (.verify signature-obj (get-data-bytes signature))))) 186 | 187 | ; Basic password protection 188 | (defn 189 | create-salt [] 190 | (.nextInt (new Random))) 191 | 192 | (defn 193 | encrypt-password-string 194 | ([password salt] (encrypt-password-string password salt default-encrypt-password-algorithm default-encrypt-password-n)) 195 | ([password salt algorithm n] 196 | (let [message-digest (MessageDigest/getInstance algorithm)] 197 | (.reset message-digest) 198 | (.update message-digest (get-data-bytes salt)) 199 | (Base64/encodeBase64String 200 | (reduce (fn [data _] (.reset message-digest) (.digest message-digest data)) 201 | (get-data-bytes password) (range n)))))) 202 | --------------------------------------------------------------------------------