source: trunk/src/allmydata/util/hashutil.py

Last change on this file was 53084f7, checked in by Alexandre Detiste <alexandre.detiste@…>, at 2024-02-27T23:49:07Z

remove more Python2 compatibility

  • Property mode set to 100644
File size: 9.1 KB
Line 
1"""
2Hashing utilities.
3
4Ported to Python 3.
5"""
6
7from past.builtins import chr as byteschr
8
9import os
10import hashlib
11from 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.
22CRYPTO_VAL_SIZE = 32
23
24
25class _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
48def tagged_hasher(tag, truncate_to=None):
49    hasher = _SHA256d_Hasher(truncate_to)
50    hasher.update(netstring(tag))
51    return hasher
52
53
54def tagged_hash(tag, val, truncate_to=None):
55    hasher = tagged_hasher(tag, truncate_to)
56    hasher.update(val)
57    return hasher.digest()
58
59
60def 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
71STORAGE_INDEX_TAG = b"allmydata_immutable_key_to_storage_index_v1"
72BLOCK_TAG = b"allmydata_encoded_subshare_v1"
73UEB_TAG = b"allmydata_uri_extension_v1"
74PLAINTEXT_TAG = b"allmydata_plaintext_v1"
75CIPHERTEXT_TAG = b"allmydata_crypttext_v1"
76CIPHERTEXT_SEGMENT_TAG = b"allmydata_crypttext_segment_v1"
77PLAINTEXT_SEGMENT_TAG = b"allmydata_plaintext_segment_v1"
78CONVERGENT_ENCRYPTION_TAG = b"allmydata_immutable_content_to_key_with_added_secret_v1+"
79
80CLIENT_RENEWAL_TAG = b"allmydata_client_renewal_secret_v1"
81CLIENT_CANCEL_TAG = b"allmydata_client_cancel_secret_v1"
82FILE_RENEWAL_TAG = b"allmydata_file_renewal_secret_v1"
83FILE_CANCEL_TAG = b"allmydata_file_cancel_secret_v1"
84BUCKET_RENEWAL_TAG = b"allmydata_bucket_renewal_secret_v1"
85BUCKET_CANCEL_TAG = b"allmydata_bucket_cancel_secret_v1"
86
87# mutable
88MUTABLE_WRITEKEY_TAG = b"allmydata_mutable_privkey_to_writekey_v1"
89MUTABLE_WRITE_ENABLER_MASTER_TAG = b"allmydata_mutable_writekey_to_write_enabler_master_v1"
90MUTABLE_WRITE_ENABLER_TAG = b"allmydata_mutable_write_enabler_master_and_nodeid_to_write_enabler_v1"
91MUTABLE_PUBKEY_TAG = b"allmydata_mutable_pubkey_to_fingerprint_v1"
92MUTABLE_READKEY_TAG = b"allmydata_mutable_writekey_to_readkey_v1"
93MUTABLE_DATAKEY_TAG = b"allmydata_mutable_readkey_to_datakey_v1"
94MUTABLE_STORAGEINDEX_TAG = b"allmydata_mutable_readkey_to_storage_index_v1"
95
96# dirnodes
97DIRNODE_CHILD_WRITECAP_TAG = b"allmydata_mutable_writekey_and_salt_to_dirnode_child_capkey_v1"
98DIRNODE_CHILD_SALT_TAG = b"allmydata_dirnode_child_rwcap_to_salt_v1"
99
100
101def 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
110def block_hash(data):
111    return tagged_hash(BLOCK_TAG, data)
112
113
114def block_hasher():
115    return tagged_hasher(BLOCK_TAG)
116
117
118def uri_extension_hash(data):
119    return tagged_hash(UEB_TAG, data)
120
121
122def uri_extension_hasher():
123    return tagged_hasher(UEB_TAG)
124
125
126def plaintext_hash(data):
127    return tagged_hash(PLAINTEXT_TAG, data)
128
129
130def plaintext_hasher():
131    return tagged_hasher(PLAINTEXT_TAG)
132
133
134def crypttext_hash(data):
135    return tagged_hash(CIPHERTEXT_TAG, data)
136
137
138def crypttext_hasher():
139    return tagged_hasher(CIPHERTEXT_TAG)
140
141
142def crypttext_segment_hash(data):
143    return tagged_hash(CIPHERTEXT_SEGMENT_TAG, data)
144
145
146def crypttext_segment_hasher():
147    return tagged_hasher(CIPHERTEXT_SEGMENT_TAG)
148
149
150def plaintext_segment_hash(data):
151    return tagged_hash(PLAINTEXT_SEGMENT_TAG, data)
152
153
154def plaintext_segment_hasher():
155    return tagged_hasher(PLAINTEXT_SEGMENT_TAG)
156
157
158KEYLEN = 16
159IVLEN = 16
160
161
162def 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
168def _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
204def convergence_hasher(k, n, segsize, convergence):
205    tag = _convergence_hasher_tag(k, n, segsize, convergence)
206    return tagged_hasher(tag, KEYLEN)
207
208
209def random_key():
210    return os.urandom(KEYLEN)
211
212
213def my_renewal_secret_hash(my_secret):
214    return tagged_hash(my_secret, CLIENT_RENEWAL_TAG)
215
216
217def my_cancel_secret_hash(my_secret):
218    return tagged_hash(my_secret, CLIENT_CANCEL_TAG)
219
220
221def 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
226def 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
231def 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
236def 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
241def _xor(a, b):
242    return b"".join([byteschr(c ^ b) for c in bytes(a)])
243
244
245def 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
254def mutable_rwcap_key_hash(iv, writekey):
255    return tagged_pair_hash(DIRNODE_CHILD_WRITECAP_TAG, iv, writekey, KEYLEN)
256
257
258def mutable_rwcap_salt_hash(writekey):
259    return tagged_hash(DIRNODE_CHILD_SALT_TAG, writekey, IVLEN)
260
261
262def ssk_writekey_hash(privkey):
263    return tagged_hash(MUTABLE_WRITEKEY_TAG, privkey, KEYLEN)
264
265
266def ssk_write_enabler_master_hash(writekey):
267    return tagged_hash(MUTABLE_WRITE_ENABLER_MASTER_TAG, writekey)
268
269
270def 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
276def ssk_pubkey_fingerprint_hash(pubkey):
277    return tagged_hash(MUTABLE_PUBKEY_TAG, pubkey)
278
279
280def ssk_readkey_hash(writekey):
281    return tagged_hash(MUTABLE_READKEY_TAG, writekey, KEYLEN)
282
283
284def ssk_readkey_data_hash(IV, readkey):
285    return tagged_pair_hash(MUTABLE_DATAKEY_TAG, IV, readkey, KEYLEN)
286
287
288def ssk_storage_index_hash(readkey):
289    return tagged_hash(MUTABLE_STORAGEINDEX_TAG, readkey, KEYLEN)
290
291
292def timing_safe_compare(a, b):
293    n = os.urandom(32)
294    return bool(tagged_hash(n, a) == tagged_hash(n, b))
295
296
297BACKUPDB_DIRHASH_TAG = b"allmydata_backupdb_dirhash_v1"
298
299
300def backupdb_dirhash(contents):
301    return tagged_hash(BACKUPDB_DIRHASH_TAG, contents)
302
303
304def permute_server_hash(peer_selection_index, server_permutation_seed):
305    return hashlib.sha1(peer_selection_index + server_permutation_seed).digest()
Note: See TracBrowser for help on using the repository browser.