Prep for pluggable compression+encryption
This commit is contained in:
parent
5a398efd9f
commit
284d11c660
3 changed files with 92 additions and 73 deletions
23
src/taoensso/nippy/compression.clj
Normal file
23
src/taoensso/nippy/compression.clj
Normal file
|
|
@ -0,0 +1,23 @@
|
||||||
|
(ns taoensso.nippy.compression
|
||||||
|
"Alpha - subject to change."
|
||||||
|
{:author "Peter Taoussanis"}
|
||||||
|
(:require [taoensso.nippy.utils :as utils]))
|
||||||
|
|
||||||
|
;;;; Interface
|
||||||
|
|
||||||
|
(defprotocol ICompressor
|
||||||
|
(header-id [compressor]) ; Unique, >0, <= 128
|
||||||
|
(compress ^bytes [compressor ba])
|
||||||
|
(decompress ^bytes [compressor ba]))
|
||||||
|
|
||||||
|
;;;; Default implementations
|
||||||
|
|
||||||
|
(deftype DefaultSnappyCompressor []
|
||||||
|
ICompressor
|
||||||
|
(header-id [_] 1)
|
||||||
|
(compress [_ ba] (org.iq80.snappy.Snappy/compress ba))
|
||||||
|
(decompress [_ ba] (org.iq80.snappy.Snappy/uncompress ba 0 (alength ^bytes ba))))
|
||||||
|
|
||||||
|
(def default-snappy-compressor
|
||||||
|
"Default org.iq80.snappy.Snappy compressor."
|
||||||
|
(DefaultSnappyCompressor.))
|
||||||
|
|
@ -1,24 +1,19 @@
|
||||||
(ns taoensso.nippy.crypto
|
(ns taoensso.nippy.encryption
|
||||||
"Alpha - subject to change.
|
"Alpha - subject to change.
|
||||||
Simple no-nonsense crypto with reasonable defaults. Because your Clojure data
|
Simple no-nonsense crypto with reasonable defaults. Because your Clojure data
|
||||||
deserves some privacy."
|
deserves some privacy."
|
||||||
{:author "Peter Taoussanis"}
|
{:author "Peter Taoussanis"}
|
||||||
(:require [clojure.string :as str]
|
(:require [taoensso.nippy.utils :as utils]))
|
||||||
[taoensso.nippy.utils :as utils]))
|
|
||||||
|
|
||||||
;;;; Interface
|
;;;; Interface
|
||||||
|
|
||||||
(defprotocol IEncrypter
|
(defprotocol IEncryptor
|
||||||
(gen-key ^javax.crypto.spec.SecretKeySpec [encrypter salt-ba pwd])
|
(header-id [encryptor]) ; Unique, >0, <= 128
|
||||||
(encrypt ^bytes [encrypter pwd ba])
|
(encrypt ^bytes [encryptor pwd ba])
|
||||||
(decrypt ^bytes [encrypter pwd ba]))
|
(decrypt ^bytes [encryptor pwd ba]))
|
||||||
|
|
||||||
(defrecord AES128Encrypter [key-work-factor key-cache])
|
;;;; Default digests, ciphers, etc.
|
||||||
|
|
||||||
;;;; Digests, ciphers, etc.
|
|
||||||
|
|
||||||
;; 128bit keys have good JVM availability and are
|
|
||||||
;; entirely sufficient, Ref. http://goo.gl/2YRQG
|
|
||||||
(def ^:private ^:const aes128-block-size (int 16))
|
(def ^:private ^:const aes128-block-size (int 16))
|
||||||
(def ^:private ^:const salt-size (int 16))
|
(def ^:private ^:const salt-size (int 16))
|
||||||
|
|
||||||
|
|
@ -36,10 +31,10 @@
|
||||||
(defn- sha512-key
|
(defn- sha512-key
|
||||||
"SHA512-based key generator. Good JVM availability without extra dependencies
|
"SHA512-based key generator. Good JVM availability without extra dependencies
|
||||||
(PBKDF2, bcrypt, scrypt, etc.). Decent security with multiple rounds."
|
(PBKDF2, bcrypt, scrypt, etc.). Decent security with multiple rounds."
|
||||||
[salt-ba ^String pwd key-work-factor]
|
^javax.crypto.spec.SecretKeySpec [salt-ba ^String pwd]
|
||||||
(loop [^bytes ba (let [pwd-ba (.getBytes pwd "UTF-8")]
|
(loop [^bytes ba (let [pwd-ba (.getBytes pwd "UTF-8")]
|
||||||
(if salt-ba (utils/ba-concat salt-ba pwd-ba) pwd-ba))
|
(if salt-ba (utils/ba-concat salt-ba pwd-ba) pwd-ba))
|
||||||
n (* (int Short/MAX_VALUE) key-work-factor)]
|
n (* (int Short/MAX_VALUE) (if salt-ba 5 64))]
|
||||||
(if-not (zero? n)
|
(if-not (zero? n)
|
||||||
(recur (.digest sha512-md ba) (dec n))
|
(recur (.digest sha512-md ba) (dec n))
|
||||||
(-> ba (java.util.Arrays/copyOf aes128-block-size)
|
(-> ba (java.util.Arrays/copyOf aes128-block-size)
|
||||||
|
|
@ -52,37 +47,60 @@
|
||||||
(time (sha512-key nil "hi" 128)) ; ~4500ms (paranoid)
|
(time (sha512-key nil "hi" 128)) ; ~4500ms (paranoid)
|
||||||
)
|
)
|
||||||
|
|
||||||
;;;; Default implementation
|
;;;; Default implementations
|
||||||
|
|
||||||
(extend-type AES128Encrypter
|
(defn- destructure-typed-pwd
|
||||||
IEncrypter
|
[typed-password]
|
||||||
(gen-key [{:keys [key-work-factor key-cache]} salt-ba pwd]
|
(letfn [(throw-ex []
|
||||||
;; Trade-off: salt-ba and key-cache mutually exclusive
|
(throw (Exception.
|
||||||
(utils/memoized key-cache sha512-key salt-ba pwd key-work-factor))
|
(str "Expected password form: "
|
||||||
|
"[<#{:salted :cached}> <password-string>].\n "
|
||||||
|
"See `default-aes128-encryptor` docstring for details!"))))]
|
||||||
|
(if-not (vector? typed-password)
|
||||||
|
(throw-ex)
|
||||||
|
(let [[type password] typed-password]
|
||||||
|
(if-not (#{:salted :cached} type)
|
||||||
|
(throw-ex)
|
||||||
|
[type password])))))
|
||||||
|
|
||||||
(encrypt [{:keys [key-cache] :as this} pwd data-ba]
|
(comment (destructure-typed-pwd [:salted "foo"]))
|
||||||
(let [salt? (not key-cache)
|
|
||||||
iv-ba (rand-bytes aes128-block-size)
|
(defrecord DefaultAES128Encryptor [key-cache]
|
||||||
salt-ba (when salt? (rand-bytes salt-size))
|
IEncryptor
|
||||||
prefix-ba (if-not salt? iv-ba (utils/ba-concat iv-ba salt-ba))
|
(header-id [_] 1)
|
||||||
key (gen-key this salt-ba pwd)
|
|
||||||
iv (javax.crypto.spec.IvParameterSpec. iv-ba)]
|
(encrypt [this typed-pwd data-ba]
|
||||||
|
(let [[type pwd] (destructure-typed-pwd typed-pwd)
|
||||||
|
salt? (= type :salted)
|
||||||
|
iv-ba (rand-bytes aes128-block-size)
|
||||||
|
salt-ba (when salt? (rand-bytes salt-size))
|
||||||
|
prefix-ba (if-not salt? iv-ba (utils/ba-concat iv-ba salt-ba))
|
||||||
|
key (utils/memoized (when-not salt? (:key-cache this))
|
||||||
|
sha512-key salt-ba pwd)
|
||||||
|
iv (javax.crypto.spec.IvParameterSpec. iv-ba)]
|
||||||
(.init aes128-cipher javax.crypto.Cipher/ENCRYPT_MODE key iv)
|
(.init aes128-cipher javax.crypto.Cipher/ENCRYPT_MODE key iv)
|
||||||
(utils/ba-concat prefix-ba (.doFinal aes128-cipher data-ba))))
|
(utils/ba-concat prefix-ba (.doFinal aes128-cipher data-ba))))
|
||||||
|
|
||||||
(decrypt [{:keys [key-cache] :as this} pwd ba]
|
(decrypt [this typed-pwd ba]
|
||||||
(let [salt? (not key-cache)
|
(let [[type pwd] (destructure-typed-pwd typed-pwd)
|
||||||
|
salt? (= type :salted)
|
||||||
prefix-size (+ aes128-block-size (if salt? salt-size 0))
|
prefix-size (+ aes128-block-size (if salt? salt-size 0))
|
||||||
[prefix-ba data-ba] (utils/ba-split ba prefix-size)
|
[prefix-ba data-ba] (utils/ba-split ba prefix-size)
|
||||||
[iv-ba salt-ba] (if-not salt? [prefix-ba nil]
|
[iv-ba salt-ba] (if-not salt? [prefix-ba nil]
|
||||||
(utils/ba-split prefix-ba aes128-block-size))
|
(utils/ba-split prefix-ba aes128-block-size))
|
||||||
key (gen-key this salt-ba pwd)
|
key (utils/memoized (when-not salt? (:key-cache this))
|
||||||
|
sha512-key salt-ba pwd)
|
||||||
iv (javax.crypto.spec.IvParameterSpec. iv-ba)]
|
iv (javax.crypto.spec.IvParameterSpec. iv-ba)]
|
||||||
(.init aes128-cipher javax.crypto.Cipher/DECRYPT_MODE key iv)
|
(.init aes128-cipher javax.crypto.Cipher/DECRYPT_MODE key iv)
|
||||||
(.doFinal aes128-cipher data-ba))))
|
(.doFinal aes128-cipher data-ba))))
|
||||||
|
|
||||||
(def aes128-salted
|
(def default-aes128-encryptor
|
||||||
"USE CASE: You want more than a small, finite number of passwords (e.g. each
|
"Alpha - subject to change.
|
||||||
|
Default 128bit AES encryptor with multi-round SHA-512 keygen.
|
||||||
|
|
||||||
|
Password form [:salted \"my-password\"]
|
||||||
|
---------------------------------------
|
||||||
|
USE CASE: You want more than a small, finite number of passwords (e.g. each
|
||||||
item encrypted will use a unique user-provided password).
|
item encrypted will use a unique user-provided password).
|
||||||
|
|
||||||
IMPLEMENTATION: Uses a relatively cheap key hash, but automatically salts
|
IMPLEMENTATION: Uses a relatively cheap key hash, but automatically salts
|
||||||
|
|
@ -94,13 +112,13 @@
|
||||||
particular key.
|
particular key.
|
||||||
|
|
||||||
Slower than `aes128-cached`, and easier to attack any particular key - but
|
Slower than `aes128-cached`, and easier to attack any particular key - but
|
||||||
keys are independent."
|
keys are independent.
|
||||||
(AES128Encrypter. 5 nil))
|
|
||||||
|
|
||||||
(def aes128-cached
|
Password form [:cached \"my-password\"]
|
||||||
"USE CASE: You want only a small, finite number of passwords (e.g. a limited
|
---------------------------------------
|
||||||
number of staff/admins, or you'll be using a single password to
|
USE CASE: You want only a small, finite number of passwords (e.g. a limited
|
||||||
encrypt many items).
|
number of staff/admins, or you'll be using a single password to
|
||||||
|
encrypt many items).
|
||||||
|
|
||||||
IMPLEMENTATION: Uses a _very_ expensive (but cached) key hash, and no salt.
|
IMPLEMENTATION: Uses a _very_ expensive (but cached) key hash, and no salt.
|
||||||
|
|
||||||
|
|
@ -112,37 +130,19 @@
|
||||||
|
|
||||||
Faster than `aes128-salted`, and harder to attack any particular key - but
|
Faster than `aes128-salted`, and harder to attack any particular key - but
|
||||||
increased danger if a key is somehow compromised."
|
increased danger if a key is somehow compromised."
|
||||||
(AES128Encrypter. 64 (atom {})))
|
(DefaultAES128Encryptor. (atom {})))
|
||||||
|
|
||||||
(defn- destructure-typed-password
|
;;;; Default implementation
|
||||||
"[<type> <password>] -> [Encrypter <password>]"
|
|
||||||
[typed-password]
|
|
||||||
(letfn [(throw-ex []
|
|
||||||
(throw (Exception.
|
|
||||||
(str "Expected password form: "
|
|
||||||
"[<#{:salted :cached}> <password-string>].\n "
|
|
||||||
"See `aes128-salted`, `aes128-cached` for details."))))]
|
|
||||||
(if-not (vector? typed-password)
|
|
||||||
(throw-ex)
|
|
||||||
(let [[type password] typed-password]
|
|
||||||
[(case type :salted aes128-salted :cached aes128-cached (throw-ex))
|
|
||||||
password]))))
|
|
||||||
|
|
||||||
(defn encrypt-aes128 [typed-password ba]
|
|
||||||
(let [[encrypter password] (destructure-typed-password typed-password)]
|
|
||||||
(encrypt encrypter password ba)))
|
|
||||||
|
|
||||||
(defn decrypt-aes128 [typed-password ba]
|
|
||||||
(let [[encrypter password] (destructure-typed-password typed-password)]
|
|
||||||
(decrypt encrypter password ba)))
|
|
||||||
|
|
||||||
(comment
|
(comment
|
||||||
(encrypt-aes128 "my-password" (.getBytes "Secret message")) ; Malformed
|
(def dae default-aes128-encryptor)
|
||||||
(time (gen-key aes128-salted nil "my-password"))
|
(def secret-ba (.getBytes "Secret message" "UTF-8"))
|
||||||
(time (gen-key aes128-cached nil "my-password"))
|
(encrypt dae "p" secret-ba) ; Malformed
|
||||||
(time (->> (.getBytes "Secret message" "UTF-8")
|
(time (encrypt dae [:salted "p"] secret-ba))
|
||||||
(encrypt-aes128 [:salted "p"])
|
(time (encrypt dae [:cached "p"] secret-ba))
|
||||||
(encrypt-aes128 [:cached "p"])
|
(time (->> secret-ba
|
||||||
(decrypt-aes128 [:cached "p"])
|
(encrypt dae [:salted "p"])
|
||||||
(decrypt-aes128 [:salted "p"])
|
(encrypt dae [:cached "p"])
|
||||||
|
(decrypt dae [:cached "p"])
|
||||||
|
(decrypt dae [:salted "p"])
|
||||||
(String.))))
|
(String.))))
|
||||||
|
|
@ -69,10 +69,6 @@
|
||||||
(comment (memoized nil +)
|
(comment (memoized nil +)
|
||||||
(memoized nil + 5 12))
|
(memoized nil + 5 12))
|
||||||
|
|
||||||
(defn compress-snappy ^bytes [^bytes ba] (org.iq80.snappy.Snappy/compress ba))
|
|
||||||
(defn uncompress-snappy ^bytes [^bytes ba] (org.iq80.snappy.Snappy/uncompress ba
|
|
||||||
0 (alength ba)))
|
|
||||||
|
|
||||||
(defn ba-concat ^bytes [^bytes ba1 ^bytes ba2]
|
(defn ba-concat ^bytes [^bytes ba1 ^bytes ba2]
|
||||||
(let [s1 (alength ba1)
|
(let [s1 (alength ba1)
|
||||||
s2 (alength ba2)
|
s2 (alength ba2)
|
||||||
|
|
@ -87,4 +83,4 @@
|
||||||
|
|
||||||
(comment (String. (ba-concat (.getBytes "foo") (.getBytes "bar")))
|
(comment (String. (ba-concat (.getBytes "foo") (.getBytes "bar")))
|
||||||
(let [[x y] (ba-split (.getBytes "foobar") 3)]
|
(let [[x y] (ba-split (.getBytes "foobar") 3)]
|
||||||
[(String. x) (String. y)]))
|
[(String. x) (String. y)]))
|
||||||
Loading…
Reference in a new issue