-
Notifications
You must be signed in to change notification settings - Fork 1
/
cryptopack.cljc
161 lines (149 loc) · 7.19 KB
/
cryptopack.cljc
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
(ns uniformity.crypto.cryptopack
(:require [uniformity.random :refer [rand-bytes]]
[uniformity.util :as util]
[uniformity.crypto.core :as core]
[uniformity.internals.crypto.processing :as proc]
[uniformity.internals.validation :refer [compat-bytes?
compat-byte-array
compat-count]]
#?(:clj [clojure.core.async :refer [go]]
:cljs [cljs.core.async] :refer-macros [go])
#?(:clj [async-error.core :refer [go-try <?]]
:cljs [async-error.core :refer-macros [go-try <?]])))
(defn ^:private parse-key-args [aes-key password rsa-pubkey]
(go-try
(let [;; these ugly loops are here becaue async makes `map` calls buggy
aes-keys (cond (nil? aes-key) nil
(vector? aes-key) (loop [i 0
acc []]
(if (= i (count aes-key))
acc
(let [key (nth aes-key i)
key (proc/key-from-binary key)]
(recur (inc i)
(conj acc key)))))
:else (proc/key-from-binary aes-key))
password-keys (cond (nil? password) nil
(vector? password) (loop [i 0
acc []]
(if (= i (count password))
acc
(let [key (nth password i)
key (<? (proc/key-from-password key))]
(recur (inc i)
(conj acc key)))))
:else (<? (proc/key-from-password password)))
rsa-keys (cond (nil? rsa-pubkey) nil
(vector? rsa-pubkey) (loop [i 0
acc []]
(if (= i (count rsa-pubkey))
acc
(let [key (nth rsa-pubkey i)
key (proc/key-from-rsa key :public)]
(recur (inc i)
(conj acc key)))))
:else (proc/key-from-rsa rsa-pubkey :public))
keys (->> [aes-keys password-keys rsa-keys]
flatten
(filter some?))]
(when (empty? keys)
(throw (ex-info "No valid keys provided"
{:aes-key aes-key
:password password
rsa-pubkey rsa-pubkey})))
keys)))
(defn encrypt
"Encrypts plaintext with a random data encryption key.
DEK is protected with each of the provided key encryption keys
and stored in slots in the output.
`plaintext` parameter may be either a byte array, or a string,
which will be treated as UTF-8 bytes.
The following key types are supported, as either scalar values or vectors:
- :aes-key takes one or more byte arrays of length 16, 24, or 32
- :password takes one or more strings
- :rsa-pubkey takes one or more byte arrays of SPKI format RSA public keys
The following additional options are supported:
- :output must be one of :json, :msgpack, or :map (default)
- :padding may be a number between 1 and 255 for PKCS#7 padding of plaintext
Basic example:
(def password \"A strong password\")
(encrypt \"Attack at dawn\" :password password)
Advanced example:
(def backup-key (rand-bytes 16))
(encrypt \"hello world\"
:password [\"foo\" \"bar\"]
:aes-key backup-key
:output :msgpack
:padding 16)"
[plaintext
& {:keys [aes-key password rsa-pubkey output padding enc-key-size]
:or {output :map, padding nil, enc-key-size 128}}]
(go-try
(let [keys (<? (parse-key-args aes-key password rsa-pubkey))
plaintext (if (string? plaintext)
(util/str->utf8 plaintext)
plaintext)
plaintext (if padding
(proc/pkcs7-pad-bytes plaintext padding)
plaintext)
dek (rand-bytes (/ enc-key-size 8))
;; hack to get around async limitations
slots (loop [i 0
acc []]
(if (= i (count keys))
acc
(let [key (nth keys i)
encrypted (<? (proc/encrypt-dek dek key))]
(recur (inc i)
(conj acc encrypted)))))
ciphertext (<? (core/aes-gcm-encrypt plaintext dek))
cryptopack (-> ciphertext
(assoc :cipher :aes-gcm)
(assoc :key-slots slots))
pack-flags (if padding
#{:padded}
#{})
cryptopack (if (empty? pack-flags)
cryptopack
(assoc cryptopack :flags pack-flags))]
(cond (= :json output) (proc/cryptopack->json cryptopack)
(= :msgpack output) (proc/cryptopack->msgpack cryptopack)
(= :map output) cryptopack
:else (throw (ex-info "Invalid output type specified"
{:output output}))))))
(defn decrypt
"Decrypt ciphertext using one of the provided keys.
`ciphertext` parameter is a cryptopack in the form of either:
- a map
- a string (compacted and JSON-encoded)
- a byte array (compacted and messagepack-encoded)
Decryption key must be supplied as one of:
:aes-key some-byte-array
:password some-string
:rsa-privkey some-pkcs8-byte-array"
[ciphertext
& {:keys [aes-key password rsa-privkey output]
:or {output :bytes}}]
(go-try
(let [cryptopack (cond (string? ciphertext) (proc/json->cryptopack ciphertext)
(compat-bytes? ciphertext) (proc/msgpack->cryptopack ciphertext)
:else ciphertext)
flags (if (contains? cryptopack :flags)
(set (:flags cryptopack))
#{})
valid-key (<? (proc/find-valid-decrypt-key (:key-slots cryptopack)
aes-key password rsa-privkey))
plaintext (<? (core/aes-gcm-decrypt
(:ciphertext cryptopack)
valid-key
(:nonce cryptopack)))
plaintext (if (:padded flags)
(proc/pksc7-unpad-bytes plaintext)
plaintext)
output (if (nil? output)
:bytes
output)]
(cond (= output :string) (util/utf8->str plaintext)
(= output :bytes) plaintext
:else (throw (ex-info "Unrecognized output type"
{:output output}))))))