Encryption: micro optimizations, housekeeping
This commit is contained in:
parent
0df6a7b0f3
commit
4c647465f5
3 changed files with 83 additions and 80 deletions
|
|
@ -27,7 +27,7 @@
|
|||
Read speed: very high.
|
||||
|
||||
A good general-purpose compressor."
|
||||
(->SnappyCompressor))
|
||||
(SnappyCompressor.))
|
||||
|
||||
(deftype LZMA2Compressor [compression-level]
|
||||
;; Compression level ∈ℕ[0,9] (low->high) with 6 LZMA2 default (we use 0)
|
||||
|
|
@ -54,7 +54,8 @@
|
|||
ba (byte-array len-decomp)
|
||||
xzs (org.tukaani.xz.XZInputStream. bais)]
|
||||
(.read xzs ba 0 len-decomp)
|
||||
(when (not= -1 (.read xzs)) ; Good practice as extra safety measure
|
||||
(if (== -1 (.read xzs)) ; Good practice as extra safety measure
|
||||
nil
|
||||
(throw (ex-info "LZMA2 Decompress failed: corrupt data?" {:ba ba})))
|
||||
ba)))
|
||||
|
||||
|
|
@ -66,7 +67,7 @@
|
|||
|
||||
A specialized compressor for large, low-write data in space-sensitive
|
||||
environments."
|
||||
(->LZMA2Compressor 0))
|
||||
(LZMA2Compressor. 0))
|
||||
|
||||
(deftype LZ4Compressor [^net.jpountz.lz4.LZ4Compressor compressor
|
||||
^net.jpountz.lz4.LZ4Decompressor decompressor]
|
||||
|
|
@ -110,16 +111,16 @@
|
|||
|
||||
Thanks to Max Penet (@mpenet) for our first implementation,
|
||||
Ref. https://github.com/mpenet/nippy-lz4"
|
||||
(->LZ4Compressor (.fastCompressor lz4-factory)
|
||||
(.fastDecompressor lz4-factory)))
|
||||
(LZ4Compressor. (.fastCompressor lz4-factory)
|
||||
(.fastDecompressor lz4-factory)))
|
||||
|
||||
(def lz4hc-compressor
|
||||
"Like `lz4-compressor` but trades some write speed for ratio."
|
||||
(->LZ4Compressor (.highCompressor lz4-factory)
|
||||
(.fastDecompressor lz4-factory)))
|
||||
(LZ4Compressor. (.highCompressor lz4-factory)
|
||||
(.fastDecompressor lz4-factory)))
|
||||
|
||||
(comment
|
||||
(def ba-bench (.getBytes (apply str (repeatedly 1000 rand)) "UTF-8"))
|
||||
(def ba-bench (.getBytes (apply str (repeatedly 1000 rand)) "UTF-8"))
|
||||
(defn bench1 [compressor]
|
||||
{:time (enc/bench 10000 {:nlaps-warmup 10000}
|
||||
(->> ba-bench (compress compressor) (decompress compressor)))
|
||||
|
|
|
|||
|
|
@ -13,17 +13,9 @@
|
|||
|
||||
;;;; Default digests, ciphers, etc.
|
||||
|
||||
(def ^:private aes128-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")))
|
||||
(def ^:private aes128-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- aes128-cipher ^javax.crypto.Cipher [] (.get ^ThreadLocal aes128-cipher*))
|
||||
(defn- sha512-md ^java.security.MessageDigest [] (.get ^ThreadLocal sha512-md*))
|
||||
|
|
@ -39,75 +31,81 @@
|
|||
(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 ^String pwd & [n]]
|
||||
[salt-ba ^String pwd]
|
||||
(let [md (sha512-md)]
|
||||
(loop [^bytes ba (let [pwd-ba (.getBytes pwd "UTF-8")]
|
||||
(if salt-ba (enc/ba-concat salt-ba pwd-ba) pwd-ba))
|
||||
;; n (or n (* (int Short/MAX_VALUE) (if salt-ba 5 64)))
|
||||
n (* (int Short/MAX_VALUE) (if salt-ba 5 64))]
|
||||
(if-not (zero? n)
|
||||
(recur (.digest md ba) (dec n))
|
||||
(-> ba (java.util.Arrays/copyOf aes128-block-size)
|
||||
(javax.crypto.spec.SecretKeySpec. "AES"))))))
|
||||
|
||||
([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 aes128-block-size)
|
||||
(javax.crypto.spec.SecretKeySpec. "AES")))))
|
||||
|
||||
(comment
|
||||
(time (sha512-key nil "hi" 1)) ; ~40ms per hash (fast)
|
||||
(time (sha512-key nil "hi" 5)) ; ~180ms (default)
|
||||
(time (sha512-key nil "hi" 32)) ; ~1200ms (conservative)
|
||||
(time (sha512-key nil "hi" 128)) ; ~4500ms (paranoid)
|
||||
)
|
||||
(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: "
|
||||
"[<#{:salted :cached}> <password-string>].\n "
|
||||
"See `default-aes128-encryptor` docstring for details!")
|
||||
{:typed-password typed-password})))
|
||||
|
||||
(defn- destructure-typed-pwd [typed-password]
|
||||
(let [throw-ex
|
||||
(fn [] (throw (ex-info
|
||||
(str "Expected password form: "
|
||||
"[<#{:salted :cached}> <password-string>].\n "
|
||||
"See `default-aes128-encryptor` docstring for details!")
|
||||
{:typed-password typed-password})))]
|
||||
(if-not (vector? typed-password) (throw-ex)
|
||||
(let [[type password] typed-password]
|
||||
(if-not (#{:salted :cached} type) (throw-ex)
|
||||
[type password])))))
|
||||
(if (vector? typed-password)
|
||||
(let [[type password] typed-password]
|
||||
(if (#{:salted :cached} type)
|
||||
[type password]
|
||||
(throw-destructure-ex typed-password)))
|
||||
(throw-destructure-ex typed-password)))
|
||||
|
||||
(comment (destructure-typed-pwd [:salted "foo"]))
|
||||
|
||||
(defrecord AES128Encryptor [key-gen key-cache]
|
||||
(defrecord AES128Encryptor [header-id keyfn cached-keyfn]
|
||||
IEncryptor
|
||||
(header-id [_] (if (identical? key-gen :sha512) :aes128-sha512 :aes128-other))
|
||||
(header-id [_] header-id)
|
||||
(encrypt [_ typed-pwd data-ba]
|
||||
(let [[type pwd] (destructure-typed-pwd typed-pwd)
|
||||
salt? (identical? type :salted)
|
||||
iv-ba (rand-bytes aes128-block-size)
|
||||
salt-ba (when salt? (rand-bytes salt-size))
|
||||
prefix-ba (if-not salt? iv-ba (enc/ba-concat iv-ba salt-ba))
|
||||
key-gen (if (identical? key-gen :sha512) sha512-key key-gen)
|
||||
key (if salt?
|
||||
(key-gen salt-ba pwd)
|
||||
(enc/memoized key-cache key-gen salt-ba pwd))
|
||||
prefix-ba (if salt? (enc/ba-concat iv-ba salt-ba) iv-ba)
|
||||
key (if salt?
|
||||
(keyfn salt-ba pwd)
|
||||
(cached-keyfn salt-ba pwd))
|
||||
iv (javax.crypto.spec.IvParameterSpec. iv-ba)
|
||||
cipher (aes128-cipher)]
|
||||
|
||||
(.init cipher javax.crypto.Cipher/ENCRYPT_MODE
|
||||
^javax.crypto.spec.SecretKeySpec key iv)
|
||||
^javax.crypto.spec.SecretKeySpec key iv)
|
||||
(enc/ba-concat prefix-ba (.doFinal cipher data-ba))))
|
||||
|
||||
(decrypt [_ typed-pwd ba]
|
||||
(let [[type pwd] (destructure-typed-pwd typed-pwd)
|
||||
salt? (= type :salted)
|
||||
prefix-size (+ aes128-block-size (if salt? salt-size 0))
|
||||
(let [[type pwd] (destructure-typed-pwd typed-pwd)
|
||||
salt? (identical? type :salted)
|
||||
prefix-size (+ aes128-block-size (if salt? salt-size 0))
|
||||
[prefix-ba data-ba] (enc/ba-split ba prefix-size)
|
||||
[iv-ba salt-ba] (if-not salt? [prefix-ba nil]
|
||||
(enc/ba-split prefix-ba aes128-block-size))
|
||||
key-gen (if (identical? key-gen :sha512) sha512-key key-gen)
|
||||
[iv-ba salt-ba] (if salt?
|
||||
(enc/ba-split prefix-ba aes128-block-size)
|
||||
[prefix-ba nil])
|
||||
key (if salt?
|
||||
(key-gen salt-ba pwd)
|
||||
(enc/memoized key-cache key-gen salt-ba pwd))
|
||||
iv (javax.crypto.spec.IvParameterSpec. iv-ba)
|
||||
(keyfn salt-ba pwd)
|
||||
(cached-keyfn salt-ba pwd))
|
||||
|
||||
iv (javax.crypto.spec.IvParameterSpec. iv-ba)
|
||||
cipher (aes128-cipher)]
|
||||
|
||||
(.init cipher javax.crypto.Cipher/DECRYPT_MODE
|
||||
^javax.crypto.spec.SecretKeySpec key iv)
|
||||
^javax.crypto.spec.SecretKeySpec key iv)
|
||||
(.doFinal cipher data-ba))))
|
||||
|
||||
(def aes128-encryptor
|
||||
|
|
@ -145,7 +143,8 @@
|
|||
|
||||
Faster than `aes128-salted`, and harder to attack any particular key - but
|
||||
increased danger if a key is somehow compromised."
|
||||
(->AES128Encryptor :sha512 (atom {})))
|
||||
|
||||
(AES128Encryptor. :aes128-sha512 sha512-key (enc/memoize_ sha512-key)))
|
||||
|
||||
;;;; Default implementation
|
||||
|
||||
|
|
@ -153,11 +152,14 @@
|
|||
(def dae aes128-encryptor)
|
||||
(def secret-ba (.getBytes "Secret message" "UTF-8"))
|
||||
(encrypt dae "p" secret-ba) ; Malformed
|
||||
(time (encrypt dae [:salted "p"] secret-ba))
|
||||
(time (encrypt dae [:cached "p"] secret-ba))
|
||||
(time (->> secret-ba
|
||||
(encrypt dae [:salted "p"])
|
||||
(encrypt dae [:cached "p"])
|
||||
(decrypt dae [:cached "p"])
|
||||
(decrypt dae [:salted "p"])
|
||||
(String.))))
|
||||
(enc/qb 10
|
||||
(encrypt dae [:salted "p"] secret-ba)
|
||||
(encrypt dae [:cached "p"] secret-ba))
|
||||
|
||||
(enc/qb 10
|
||||
(->> secret-ba
|
||||
(encrypt dae [:salted "p"])
|
||||
(encrypt dae [:cached "p"])
|
||||
(decrypt dae [:cached "p"])
|
||||
(decrypt dae [:salted "p"])
|
||||
(String.))))
|
||||
|
|
|
|||
|
|
@ -86,21 +86,21 @@
|
|||
(is (thrown? Exception ; No thaw extension yet
|
||||
(do (nippy/swap-custom-readers! (constantly {}))
|
||||
(nippy/extend-freeze MyType 1 [x s] (.writeUTF s (.data x)))
|
||||
(thaw (freeze (->MyType "val"))))))
|
||||
(is (do (nippy/extend-thaw 1 [s] (->MyType (.readUTF s)))
|
||||
(let [mt (->MyType "val")] (= (.data ^MyType mt)
|
||||
(.data ^MyType (thaw (freeze mt)))))))
|
||||
(thaw (freeze (MyType. "val"))))))
|
||||
(is (do (nippy/extend-thaw 1 [s] (MyType. (.readUTF s)))
|
||||
(let [mt (MyType. "val")] (= (.data ^MyType mt)
|
||||
(.data ^MyType (thaw (freeze mt)))))))
|
||||
|
||||
;;; Extend to custom Record
|
||||
(is (do (nippy/extend-freeze MyRec 2 [x s] (.writeUTF s (str "foo-" (:data x))))
|
||||
(nippy/extend-thaw 2 [s] (->MyRec (.readUTF s)))
|
||||
(= (->MyRec "foo-val") (thaw (freeze (->MyRec "val"))))))
|
||||
(nippy/extend-thaw 2 [s] (MyRec. (.readUTF s)))
|
||||
(= (MyRec. "foo-val") (thaw (freeze (MyRec. "val"))))))
|
||||
|
||||
;;; Keyword (prefixed) extensions
|
||||
(is
|
||||
(do (nippy/extend-freeze MyRec :nippy-tests/MyRec [x s] (.writeUTF s (:data x)))
|
||||
(nippy/extend-thaw :nippy-tests/MyRec [s] (->MyRec (.readUTF s)))
|
||||
(let [mr (->MyRec "val")] (= mr (thaw (freeze mr)))))))
|
||||
(nippy/extend-thaw :nippy-tests/MyRec [s] (MyRec. (.readUTF s)))
|
||||
(let [mr (MyRec. "val")] (= mr (thaw (freeze mr)))))))
|
||||
|
||||
;;;; Caching
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue