From 868a8f65de3085a8b15898a9c8ed58aced117ce1 Mon Sep 17 00:00:00 2001 From: Peter Taoussanis Date: Sat, 8 Sep 2018 17:01:00 +0200 Subject: [PATCH] [Encryption] Rewrite encryption ns to build off new crypto ns --- src/taoensso/nippy/encryption.clj | 105 +++++++----------------------- 1 file changed, 23 insertions(+), 82 deletions(-) diff --git a/src/taoensso/nippy/encryption.clj b/src/taoensso/nippy/encryption.clj index aa3abd4..f5d2998 100644 --- a/src/taoensso/nippy/encryption.clj +++ b/src/taoensso/nippy/encryption.clj @@ -1,12 +1,8 @@ (ns taoensso.nippy.encryption "Simple no-nonsense crypto with reasonable defaults" - (:require [taoensso.encore :as enc])) - -;; Note that AES128 may be preferable to AES256 due to known attack -;; vectors specific to AES256, Ref. https://www.schneier.com/blog/archives/2009/07/another_new_aes.html -;; Or, for a counter argument, Ref. https://blog.agilebits.com/2013/03/09/guess-why-were-moving-to-256-bit-aes-keys/ - -;;;; Interface + (:require + [taoensso.encore :as enc] + [taoensso.nippy.crypto :as crypto])) (def standard-header-ids "These'll support :auto thaw" #{:aes128-sha512}) @@ -15,50 +11,6 @@ (encrypt ^bytes [encryptor pwd ba]) (decrypt ^bytes [encryptor pwd ba])) -;;;; Default digests, ciphers, etc. - -;; TODO Prefer GCM > CBC ("AES/GCM/NoPadding", Ref. https://goo.gl/jpZoj8 -(def ^:private aes-cipher* (enc/thread-local-proxy (javax.crypto.Cipher/getInstance "AES/CBC/PKCS5Padding"))) -(def ^:private sha512-md* (enc/thread-local-proxy (java.security.MessageDigest/getInstance "SHA-512"))) -(def ^:private prng* (enc/thread-local-proxy (java.security.SecureRandom/getInstance "SHA1PRNG"))) - -(defn- aes-cipher ^javax.crypto.Cipher [] (.get ^ThreadLocal aes-cipher*)) -(defn- sha512-md ^java.security.MessageDigest [] (.get ^ThreadLocal sha512-md*)) -(defn- prng ^java.security.SecureRandom [] (.get ^ThreadLocal prng*)) - -(def ^:private ^:const aes-block-size (.getBlockSize (aes-cipher))) -(def ^:private ^:const aes-iv-size aes-block-size #_12) ; 12 for GCM, Ref. https://goo.gl/c8mhqp -(def ^:private ^:const salt-size 16) - -(defn- rand-bytes [size] (let [ba (byte-array size)] (.nextBytes (prng) ba) ba)) - -;;;; Default key derivation (salt+password -> key) - -(defn- sha512-key - "SHA512-based key generator. Good JVM availability without extra dependencies - (PBKDF2, bcrypt, scrypt, etc.). Decent security when using many rounds." - - ([salt-ba pwd ] (sha512-key salt-ba pwd (* Short/MAX_VALUE (if salt-ba 5 64)))) - ([salt-ba pwd ^long n] - (let [md (sha512-md) - init-ba (let [pwd-ba (.getBytes ^String pwd "UTF-8")] - (if salt-ba (enc/ba-concat salt-ba pwd-ba) pwd-ba)) - ^bytes ba (enc/reduce-n (fn [acc in] (.digest md acc)) init-ba n)] - - (-> ba - (java.util.Arrays/copyOf aes-block-size) - (javax.crypto.spec.SecretKeySpec. "AES"))))) - -(comment - (enc/qb 10 - (sha512-key nil "hi" (* Short/MAX_VALUE 1)) ; ~40ms per hash (fast) - (sha512-key nil "hi" (* Short/MAX_VALUE 5)) ; ~180ms (default) - (sha512-key nil "hi" (* Short/MAX_VALUE 32)) ; ~1200ms (conservative) - (sha512-key nil "hi" (* Short/MAX_VALUE 128)) ; ~4500ms (paranoid) - )) - -;;;; Default implementations - (defn- throw-destructure-ex [typed-password] (throw (ex-info (str "Expected password form: " @@ -76,45 +28,32 @@ (comment (destructure-typed-pwd [:salted "foo"])) -(deftype AES128Encryptor [header-id keyfn cached-keyfn] +(deftype AES128Encryptor [header-id salted-key-fn cached-key-fn] IEncryptor (header-id [_] header-id) (encrypt [_ typed-pwd ba] (let [[type pwd] (destructure-typed-pwd typed-pwd) salt? (identical? type :salted) - iv-ba (rand-bytes aes-iv-size) - salt-ba (when salt? (rand-bytes salt-size)) - prefix-ba (if salt? (enc/ba-concat iv-ba salt-ba) iv-ba) - key-spec (if salt? - (keyfn salt-ba pwd) - (cached-keyfn salt-ba pwd)) - ;; param-spec (javax.crypto.spec.GCMParameterSpec. 128 iv-ba) - param-spec (javax.crypto.spec.IvParameterSpec. iv-ba) - cipher (aes-cipher)] + ?salt-ba (when salt? (crypto/rand-bytes 16)) + key-ba + (crypto/take-ba 16 ; 128 bit AES + (if-let [salt-ba ?salt-ba] + (salted-key-fn salt-ba pwd) + (cached-key-fn nil pwd)))] - (.init cipher javax.crypto.Cipher/ENCRYPT_MODE - ^javax.crypto.spec.SecretKeySpec key-spec param-spec) - (enc/ba-concat prefix-ba (.doFinal cipher ba)))) + (crypto/encrypt crypto/cipher-kit-aes-cbc + ?salt-ba key-ba ba))) (decrypt [_ typed-pwd ba] - (let [[type pwd] (destructure-typed-pwd typed-pwd) - salt? (identical? type :salted) - prefix-size (+ aes-iv-size (if salt? salt-size 0)) - [prefix-ba data-ba] (enc/ba-split ba prefix-size) - [iv-ba salt-ba] (if salt? - (enc/ba-split prefix-ba aes-iv-size) - [prefix-ba nil]) - key-spec (if salt? - (keyfn salt-ba pwd) - (cached-keyfn salt-ba pwd)) + (let [[type pwd] (destructure-typed-pwd typed-pwd) + salt? (identical? type :salted) + salt->key-fn + (if salt? + #(salted-key-fn % pwd) + #(cached-key-fn % pwd))] - ;; param-spec (javax.crypto.spec.GCMParameterSpec. 128 iv-ba) - param-spec (javax.crypto.spec.IvParameterSpec. iv-ba) - cipher (aes-cipher)] - - (.init cipher javax.crypto.Cipher/DECRYPT_MODE - ^javax.crypto.spec.SecretKeySpec key-spec param-spec) - (.doFinal cipher data-ba)))) + (crypto/decrypt crypto/cipher-kit-aes-cbc + (if salt? 16 0) salt->key-fn ba)))) (def aes128-encryptor "Default 128bit AES encryptor with many-round SHA-512 key-gen. @@ -152,7 +91,9 @@ Faster than `aes128-salted`, and harder to attack any particular key - but increased danger if a key is somehow compromised." - (AES128Encryptor. :aes128-sha512 sha512-key (enc/memoize_ sha512-key))) + (AES128Encryptor. :aes128-sha512 + (do (fn [ salt-ba pwd] (crypto/take-ba 16 (crypto/sha512-ba salt-ba pwd (* Short/MAX_VALUE 5))))) + (enc/memoize_ (fn [_salt-ba pwd] (crypto/take-ba 16 (crypto/sha512-ba nil pwd (* Short/MAX_VALUE 64))))))) ;;;; Default implementation