1 | """ |
---|
2 | Hashing utilities. |
---|
3 | |
---|
4 | Ported to Python 3. |
---|
5 | """ |
---|
6 | |
---|
7 | from past.builtins import chr as byteschr |
---|
8 | |
---|
9 | import os |
---|
10 | import hashlib |
---|
11 | from allmydata.util.netstring import netstring |
---|
12 | |
---|
13 | # Be very very cautious when modifying this file. Almost any change will |
---|
14 | # cause a compatibility break, invalidating all outstanding URIs and making |
---|
15 | # any previously uploaded files become inaccessible. BE CONSERVATIVE AND TEST |
---|
16 | # AGAINST OLD DATA! |
---|
17 | |
---|
18 | # Various crypto values are this size: hash outputs (from SHA-256d), |
---|
19 | # randomly-generated secrets such as the lease secret, and symmetric encryption |
---|
20 | # keys. In the near future we will add DSA private keys, and salts of various |
---|
21 | # kinds. |
---|
22 | CRYPTO_VAL_SIZE = 32 |
---|
23 | |
---|
24 | |
---|
25 | class _SHA256d_Hasher(object): |
---|
26 | # use SHA-256d, as defined by Ferguson and Schneier: hash the output |
---|
27 | # again to prevent length-extension attacks |
---|
28 | def __init__(self, truncate_to=None): |
---|
29 | self.h = hashlib.sha256() |
---|
30 | self.truncate_to = truncate_to |
---|
31 | self._digest = None |
---|
32 | |
---|
33 | def update(self, data): |
---|
34 | assert isinstance(data, bytes) # no unicode |
---|
35 | self.h.update(data) |
---|
36 | |
---|
37 | def digest(self): |
---|
38 | if self._digest is None: |
---|
39 | h1 = self.h.digest() |
---|
40 | del self.h |
---|
41 | h2 = hashlib.sha256(h1).digest() |
---|
42 | if self.truncate_to: |
---|
43 | h2 = h2[:self.truncate_to] |
---|
44 | self._digest = h2 |
---|
45 | return self._digest |
---|
46 | |
---|
47 | |
---|
48 | def tagged_hasher(tag, truncate_to=None): |
---|
49 | hasher = _SHA256d_Hasher(truncate_to) |
---|
50 | hasher.update(netstring(tag)) |
---|
51 | return hasher |
---|
52 | |
---|
53 | |
---|
54 | def tagged_hash(tag, val, truncate_to=None): |
---|
55 | hasher = tagged_hasher(tag, truncate_to) |
---|
56 | hasher.update(val) |
---|
57 | return hasher.digest() |
---|
58 | |
---|
59 | |
---|
60 | def tagged_pair_hash(tag, val1, val2, truncate_to=None): |
---|
61 | s = _SHA256d_Hasher(truncate_to) |
---|
62 | s.update(netstring(tag)) |
---|
63 | s.update(netstring(val1)) |
---|
64 | s.update(netstring(val2)) |
---|
65 | return s.digest() |
---|
66 | |
---|
67 | # specific hash tags that we use |
---|
68 | |
---|
69 | |
---|
70 | # immutable |
---|
71 | STORAGE_INDEX_TAG = b"allmydata_immutable_key_to_storage_index_v1" |
---|
72 | BLOCK_TAG = b"allmydata_encoded_subshare_v1" |
---|
73 | UEB_TAG = b"allmydata_uri_extension_v1" |
---|
74 | PLAINTEXT_TAG = b"allmydata_plaintext_v1" |
---|
75 | CIPHERTEXT_TAG = b"allmydata_crypttext_v1" |
---|
76 | CIPHERTEXT_SEGMENT_TAG = b"allmydata_crypttext_segment_v1" |
---|
77 | PLAINTEXT_SEGMENT_TAG = b"allmydata_plaintext_segment_v1" |
---|
78 | CONVERGENT_ENCRYPTION_TAG = b"allmydata_immutable_content_to_key_with_added_secret_v1+" |
---|
79 | |
---|
80 | CLIENT_RENEWAL_TAG = b"allmydata_client_renewal_secret_v1" |
---|
81 | CLIENT_CANCEL_TAG = b"allmydata_client_cancel_secret_v1" |
---|
82 | FILE_RENEWAL_TAG = b"allmydata_file_renewal_secret_v1" |
---|
83 | FILE_CANCEL_TAG = b"allmydata_file_cancel_secret_v1" |
---|
84 | BUCKET_RENEWAL_TAG = b"allmydata_bucket_renewal_secret_v1" |
---|
85 | BUCKET_CANCEL_TAG = b"allmydata_bucket_cancel_secret_v1" |
---|
86 | |
---|
87 | # mutable |
---|
88 | MUTABLE_WRITEKEY_TAG = b"allmydata_mutable_privkey_to_writekey_v1" |
---|
89 | MUTABLE_WRITE_ENABLER_MASTER_TAG = b"allmydata_mutable_writekey_to_write_enabler_master_v1" |
---|
90 | MUTABLE_WRITE_ENABLER_TAG = b"allmydata_mutable_write_enabler_master_and_nodeid_to_write_enabler_v1" |
---|
91 | MUTABLE_PUBKEY_TAG = b"allmydata_mutable_pubkey_to_fingerprint_v1" |
---|
92 | MUTABLE_READKEY_TAG = b"allmydata_mutable_writekey_to_readkey_v1" |
---|
93 | MUTABLE_DATAKEY_TAG = b"allmydata_mutable_readkey_to_datakey_v1" |
---|
94 | MUTABLE_STORAGEINDEX_TAG = b"allmydata_mutable_readkey_to_storage_index_v1" |
---|
95 | |
---|
96 | # dirnodes |
---|
97 | DIRNODE_CHILD_WRITECAP_TAG = b"allmydata_mutable_writekey_and_salt_to_dirnode_child_capkey_v1" |
---|
98 | DIRNODE_CHILD_SALT_TAG = b"allmydata_dirnode_child_rwcap_to_salt_v1" |
---|
99 | |
---|
100 | |
---|
101 | def storage_index_hash(key): |
---|
102 | # storage index is truncated to 128 bits (16 bytes). We're only hashing a |
---|
103 | # 16-byte value to get it, so there's no point in using a larger value. We |
---|
104 | # use this same tagged hash to go from encryption key to storage index for |
---|
105 | # random-keyed immutable files and convergent-encryption immutabie |
---|
106 | # files. Mutable files use ssk_storage_index_hash(). |
---|
107 | return tagged_hash(STORAGE_INDEX_TAG, key, 16) |
---|
108 | |
---|
109 | |
---|
110 | def block_hash(data): |
---|
111 | return tagged_hash(BLOCK_TAG, data) |
---|
112 | |
---|
113 | |
---|
114 | def block_hasher(): |
---|
115 | return tagged_hasher(BLOCK_TAG) |
---|
116 | |
---|
117 | |
---|
118 | def uri_extension_hash(data): |
---|
119 | return tagged_hash(UEB_TAG, data) |
---|
120 | |
---|
121 | |
---|
122 | def uri_extension_hasher(): |
---|
123 | return tagged_hasher(UEB_TAG) |
---|
124 | |
---|
125 | |
---|
126 | def plaintext_hash(data): |
---|
127 | return tagged_hash(PLAINTEXT_TAG, data) |
---|
128 | |
---|
129 | |
---|
130 | def plaintext_hasher(): |
---|
131 | return tagged_hasher(PLAINTEXT_TAG) |
---|
132 | |
---|
133 | |
---|
134 | def crypttext_hash(data): |
---|
135 | return tagged_hash(CIPHERTEXT_TAG, data) |
---|
136 | |
---|
137 | |
---|
138 | def crypttext_hasher(): |
---|
139 | return tagged_hasher(CIPHERTEXT_TAG) |
---|
140 | |
---|
141 | |
---|
142 | def crypttext_segment_hash(data): |
---|
143 | return tagged_hash(CIPHERTEXT_SEGMENT_TAG, data) |
---|
144 | |
---|
145 | |
---|
146 | def crypttext_segment_hasher(): |
---|
147 | return tagged_hasher(CIPHERTEXT_SEGMENT_TAG) |
---|
148 | |
---|
149 | |
---|
150 | def plaintext_segment_hash(data): |
---|
151 | return tagged_hash(PLAINTEXT_SEGMENT_TAG, data) |
---|
152 | |
---|
153 | |
---|
154 | def plaintext_segment_hasher(): |
---|
155 | return tagged_hasher(PLAINTEXT_SEGMENT_TAG) |
---|
156 | |
---|
157 | |
---|
158 | KEYLEN = 16 |
---|
159 | IVLEN = 16 |
---|
160 | |
---|
161 | |
---|
162 | def convergence_hash(k, n, segsize, data, convergence): |
---|
163 | h = convergence_hasher(k, n, segsize, convergence) |
---|
164 | h.update(data) |
---|
165 | return h.digest() |
---|
166 | |
---|
167 | |
---|
168 | def _convergence_hasher_tag(k, n, segsize, convergence): |
---|
169 | """ |
---|
170 | Create the convergence hashing tag. |
---|
171 | |
---|
172 | :param int k: Required shares (in [1..256]). |
---|
173 | :param int n: Total shares (in [1..256]). |
---|
174 | :param int segsize: Maximum segment size. |
---|
175 | :param bytes convergence: The convergence secret. |
---|
176 | |
---|
177 | :return bytes: The bytestring to use as a tag in the convergence hash. |
---|
178 | """ |
---|
179 | assert isinstance(convergence, bytes) |
---|
180 | if k > n: |
---|
181 | raise ValueError( |
---|
182 | "k > n not allowed; k = {}, n = {}".format(k, n), |
---|
183 | ) |
---|
184 | if k < 1 or n < 1: |
---|
185 | # It doesn't make sense to have zero shares. Zero shares carry no |
---|
186 | # information, cannot encode any part of the application data. |
---|
187 | raise ValueError( |
---|
188 | "k, n < 1 not allowed; k = {}, n = {}".format(k, n), |
---|
189 | ) |
---|
190 | if k > 256 or n > 256: |
---|
191 | # ZFEC supports encoding application data into a maximum of 256 |
---|
192 | # shares. If we ignore the limitations of ZFEC, it may be fine to use |
---|
193 | # a configuration with more shares than that and it may be fine to |
---|
194 | # construct a convergence tag from such a configuration. Since ZFEC |
---|
195 | # is the only supported encoder, though, this is moot for now. |
---|
196 | raise ValueError( |
---|
197 | "k, n > 256 not allowed; k = {}, n = {}".format(k, n), |
---|
198 | ) |
---|
199 | param_tag = netstring(b"%d,%d,%d" % (k, n, segsize)) |
---|
200 | tag = CONVERGENT_ENCRYPTION_TAG + netstring(convergence) + param_tag |
---|
201 | return tag |
---|
202 | |
---|
203 | |
---|
204 | def convergence_hasher(k, n, segsize, convergence): |
---|
205 | tag = _convergence_hasher_tag(k, n, segsize, convergence) |
---|
206 | return tagged_hasher(tag, KEYLEN) |
---|
207 | |
---|
208 | |
---|
209 | def random_key(): |
---|
210 | return os.urandom(KEYLEN) |
---|
211 | |
---|
212 | |
---|
213 | def my_renewal_secret_hash(my_secret): |
---|
214 | return tagged_hash(my_secret, CLIENT_RENEWAL_TAG) |
---|
215 | |
---|
216 | |
---|
217 | def my_cancel_secret_hash(my_secret): |
---|
218 | return tagged_hash(my_secret, CLIENT_CANCEL_TAG) |
---|
219 | |
---|
220 | |
---|
221 | def file_renewal_secret_hash(client_renewal_secret, storage_index): |
---|
222 | return tagged_pair_hash(FILE_RENEWAL_TAG, |
---|
223 | client_renewal_secret, storage_index) |
---|
224 | |
---|
225 | |
---|
226 | def file_cancel_secret_hash(client_cancel_secret, storage_index): |
---|
227 | return tagged_pair_hash(FILE_CANCEL_TAG, |
---|
228 | client_cancel_secret, storage_index) |
---|
229 | |
---|
230 | |
---|
231 | def bucket_renewal_secret_hash(file_renewal_secret, peerid): |
---|
232 | assert len(peerid) == 20, "%s: %r" % (len(peerid), peerid) # binary! |
---|
233 | return tagged_pair_hash(BUCKET_RENEWAL_TAG, file_renewal_secret, peerid) |
---|
234 | |
---|
235 | |
---|
236 | def bucket_cancel_secret_hash(file_cancel_secret, peerid): |
---|
237 | assert len(peerid) == 20, "%s: %r" % (len(peerid), peerid) # binary! |
---|
238 | return tagged_pair_hash(BUCKET_CANCEL_TAG, file_cancel_secret, peerid) |
---|
239 | |
---|
240 | |
---|
241 | def _xor(a, b): |
---|
242 | return b"".join([byteschr(c ^ b) for c in bytes(a)]) |
---|
243 | |
---|
244 | |
---|
245 | def hmac(tag, data): |
---|
246 | tag = bytes(tag) # Make sure it matches Python 3 behavior |
---|
247 | ikey = _xor(tag, 0x36) |
---|
248 | okey = _xor(tag, 0x5c) |
---|
249 | h1 = hashlib.sha256(ikey + data).digest() |
---|
250 | h2 = hashlib.sha256(okey + h1).digest() |
---|
251 | return h2 |
---|
252 | |
---|
253 | |
---|
254 | def mutable_rwcap_key_hash(iv, writekey): |
---|
255 | return tagged_pair_hash(DIRNODE_CHILD_WRITECAP_TAG, iv, writekey, KEYLEN) |
---|
256 | |
---|
257 | |
---|
258 | def mutable_rwcap_salt_hash(writekey): |
---|
259 | return tagged_hash(DIRNODE_CHILD_SALT_TAG, writekey, IVLEN) |
---|
260 | |
---|
261 | |
---|
262 | def ssk_writekey_hash(privkey): |
---|
263 | return tagged_hash(MUTABLE_WRITEKEY_TAG, privkey, KEYLEN) |
---|
264 | |
---|
265 | |
---|
266 | def ssk_write_enabler_master_hash(writekey): |
---|
267 | return tagged_hash(MUTABLE_WRITE_ENABLER_MASTER_TAG, writekey) |
---|
268 | |
---|
269 | |
---|
270 | def ssk_write_enabler_hash(writekey, peerid): |
---|
271 | assert len(peerid) == 20, "%s: %r" % (len(peerid), peerid) # binary! |
---|
272 | wem = ssk_write_enabler_master_hash(writekey) |
---|
273 | return tagged_pair_hash(MUTABLE_WRITE_ENABLER_TAG, wem, peerid) |
---|
274 | |
---|
275 | |
---|
276 | def ssk_pubkey_fingerprint_hash(pubkey): |
---|
277 | return tagged_hash(MUTABLE_PUBKEY_TAG, pubkey) |
---|
278 | |
---|
279 | |
---|
280 | def ssk_readkey_hash(writekey): |
---|
281 | return tagged_hash(MUTABLE_READKEY_TAG, writekey, KEYLEN) |
---|
282 | |
---|
283 | |
---|
284 | def ssk_readkey_data_hash(IV, readkey): |
---|
285 | return tagged_pair_hash(MUTABLE_DATAKEY_TAG, IV, readkey, KEYLEN) |
---|
286 | |
---|
287 | |
---|
288 | def ssk_storage_index_hash(readkey): |
---|
289 | return tagged_hash(MUTABLE_STORAGEINDEX_TAG, readkey, KEYLEN) |
---|
290 | |
---|
291 | |
---|
292 | def timing_safe_compare(a, b): |
---|
293 | n = os.urandom(32) |
---|
294 | return bool(tagged_hash(n, a) == tagged_hash(n, b)) |
---|
295 | |
---|
296 | |
---|
297 | BACKUPDB_DIRHASH_TAG = b"allmydata_backupdb_dirhash_v1" |
---|
298 | |
---|
299 | |
---|
300 | def backupdb_dirhash(contents): |
---|
301 | return tagged_hash(BACKUPDB_DIRHASH_TAG, contents) |
---|
302 | |
---|
303 | |
---|
304 | def permute_server_hash(peer_selection_index, server_permutation_seed): |
---|
305 | return hashlib.sha1(peer_selection_index + server_permutation_seed).digest() |
---|