""" Hashing utilities. Ported to Python 3. """ from __future__ import absolute_import from __future__ import division from __future__ import print_function from __future__ import unicode_literals from future.utils import PY2 if PY2: # Don't import bytes to prevent leaking future's bytes. from builtins import filter, map, zip, ascii, chr, hex, input, next, oct, open, pow, round, super, dict, list, object, range, str, max, min, bytes as future_bytes # noqa: F401 else: future_bytes = bytes from past.builtins import chr as byteschr import os import hashlib from allmydata.util.netstring import netstring # Be very very cautious when modifying this file. Almost any change will # cause a compatibility break, invalidating all outstanding URIs and making # any previously uploaded files become inaccessible. BE CONSERVATIVE AND TEST # AGAINST OLD DATA! # Various crypto values are this size: hash outputs (from SHA-256d), # randomly-generated secrets such as the lease secret, and symmetric encryption # keys. In the near future we will add DSA private keys, and salts of various # kinds. CRYPTO_VAL_SIZE = 32 class _SHA256d_Hasher(object): # use SHA-256d, as defined by Ferguson and Schneier: hash the output # again to prevent length-extension attacks def __init__(self, truncate_to=None): self.h = hashlib.sha256() self.truncate_to = truncate_to self._digest = None def update(self, data): assert isinstance(data, bytes) # no unicode self.h.update(data) def digest(self): if self._digest is None: h1 = self.h.digest() del self.h h2 = hashlib.sha256(h1).digest() if self.truncate_to: h2 = h2[:self.truncate_to] self._digest = h2 return self._digest def tagged_hasher(tag, truncate_to=None): hasher = _SHA256d_Hasher(truncate_to) hasher.update(netstring(tag)) return hasher def tagged_hash(tag, val, truncate_to=None): hasher = tagged_hasher(tag, truncate_to) hasher.update(val) return hasher.digest() def tagged_pair_hash(tag, val1, val2, truncate_to=None): s = _SHA256d_Hasher(truncate_to) s.update(netstring(tag)) s.update(netstring(val1)) s.update(netstring(val2)) return s.digest() # specific hash tags that we use # immutable STORAGE_INDEX_TAG = b"allmydata_immutable_key_to_storage_index_v1" BLOCK_TAG = b"allmydata_encoded_subshare_v1" UEB_TAG = b"allmydata_uri_extension_v1" PLAINTEXT_TAG = b"allmydata_plaintext_v1" CIPHERTEXT_TAG = b"allmydata_crypttext_v1" CIPHERTEXT_SEGMENT_TAG = b"allmydata_crypttext_segment_v1" PLAINTEXT_SEGMENT_TAG = b"allmydata_plaintext_segment_v1" CONVERGENT_ENCRYPTION_TAG = b"allmydata_immutable_content_to_key_with_added_secret_v1+" CLIENT_RENEWAL_TAG = b"allmydata_client_renewal_secret_v1" CLIENT_CANCEL_TAG = b"allmydata_client_cancel_secret_v1" FILE_RENEWAL_TAG = b"allmydata_file_renewal_secret_v1" FILE_CANCEL_TAG = b"allmydata_file_cancel_secret_v1" BUCKET_RENEWAL_TAG = b"allmydata_bucket_renewal_secret_v1" BUCKET_CANCEL_TAG = b"allmydata_bucket_cancel_secret_v1" # mutable MUTABLE_WRITEKEY_TAG = b"allmydata_mutable_privkey_to_writekey_v1" MUTABLE_WRITE_ENABLER_MASTER_TAG = b"allmydata_mutable_writekey_to_write_enabler_master_v1" MUTABLE_WRITE_ENABLER_TAG = b"allmydata_mutable_write_enabler_master_and_nodeid_to_write_enabler_v1" MUTABLE_PUBKEY_TAG = b"allmydata_mutable_pubkey_to_fingerprint_v1" MUTABLE_READKEY_TAG = b"allmydata_mutable_writekey_to_readkey_v1" MUTABLE_DATAKEY_TAG = b"allmydata_mutable_readkey_to_datakey_v1" MUTABLE_STORAGEINDEX_TAG = b"allmydata_mutable_readkey_to_storage_index_v1" # dirnodes DIRNODE_CHILD_WRITECAP_TAG = b"allmydata_mutable_writekey_and_salt_to_dirnode_child_capkey_v1" DIRNODE_CHILD_SALT_TAG = b"allmydata_dirnode_child_rwcap_to_salt_v1" def storage_index_hash(key): # storage index is truncated to 128 bits (16 bytes). We're only hashing a # 16-byte value to get it, so there's no point in using a larger value. We # use this same tagged hash to go from encryption key to storage index for # random-keyed immutable files and convergent-encryption immutabie # files. Mutable files use ssk_storage_index_hash(). return tagged_hash(STORAGE_INDEX_TAG, key, 16) def block_hash(data): return tagged_hash(BLOCK_TAG, data) def block_hasher(): return tagged_hasher(BLOCK_TAG) def uri_extension_hash(data): return tagged_hash(UEB_TAG, data) def uri_extension_hasher(): return tagged_hasher(UEB_TAG) def plaintext_hash(data): return tagged_hash(PLAINTEXT_TAG, data) def plaintext_hasher(): return tagged_hasher(PLAINTEXT_TAG) def crypttext_hash(data): return tagged_hash(CIPHERTEXT_TAG, data) def crypttext_hasher(): return tagged_hasher(CIPHERTEXT_TAG) def crypttext_segment_hash(data): return tagged_hash(CIPHERTEXT_SEGMENT_TAG, data) def crypttext_segment_hasher(): return tagged_hasher(CIPHERTEXT_SEGMENT_TAG) def plaintext_segment_hash(data): return tagged_hash(PLAINTEXT_SEGMENT_TAG, data) def plaintext_segment_hasher(): return tagged_hasher(PLAINTEXT_SEGMENT_TAG) KEYLEN = 16 IVLEN = 16 def convergence_hash(k, n, segsize, data, convergence): h = convergence_hasher(k, n, segsize, convergence) h.update(data) return h.digest() def convergence_hasher(k, n, segsize, convergence): assert isinstance(convergence, bytes) param_tag = netstring(b"%d,%d,%d" % (k, n, segsize)) tag = CONVERGENT_ENCRYPTION_TAG + netstring(convergence) + param_tag return tagged_hasher(tag, KEYLEN) def random_key(): return os.urandom(KEYLEN) def my_renewal_secret_hash(my_secret): return tagged_hash(my_secret, CLIENT_RENEWAL_TAG) def my_cancel_secret_hash(my_secret): return tagged_hash(my_secret, CLIENT_CANCEL_TAG) def file_renewal_secret_hash(client_renewal_secret, storage_index): return tagged_pair_hash(FILE_RENEWAL_TAG, client_renewal_secret, storage_index) def file_cancel_secret_hash(client_cancel_secret, storage_index): return tagged_pair_hash(FILE_CANCEL_TAG, client_cancel_secret, storage_index) def bucket_renewal_secret_hash(file_renewal_secret, peerid): assert len(peerid) == 20, "%s: %r" % (len(peerid), peerid) # binary! return tagged_pair_hash(BUCKET_RENEWAL_TAG, file_renewal_secret, peerid) def bucket_cancel_secret_hash(file_cancel_secret, peerid): assert len(peerid) == 20, "%s: %r" % (len(peerid), peerid) # binary! return tagged_pair_hash(BUCKET_CANCEL_TAG, file_cancel_secret, peerid) def _xor(a, b): return b"".join([byteschr(c ^ b) for c in future_bytes(a)]) def hmac(tag, data): tag = bytes(tag) # Make sure it matches Python 3 behavior ikey = _xor(tag, 0x36) okey = _xor(tag, 0x5c) h1 = hashlib.sha256(ikey + data).digest() h2 = hashlib.sha256(okey + h1).digest() return h2 def mutable_rwcap_key_hash(iv, writekey): return tagged_pair_hash(DIRNODE_CHILD_WRITECAP_TAG, iv, writekey, KEYLEN) def mutable_rwcap_salt_hash(writekey): return tagged_hash(DIRNODE_CHILD_SALT_TAG, writekey, IVLEN) def ssk_writekey_hash(privkey): return tagged_hash(MUTABLE_WRITEKEY_TAG, privkey, KEYLEN) def ssk_write_enabler_master_hash(writekey): return tagged_hash(MUTABLE_WRITE_ENABLER_MASTER_TAG, writekey) def ssk_write_enabler_hash(writekey, peerid): assert len(peerid) == 20, "%s: %r" % (len(peerid), peerid) # binary! wem = ssk_write_enabler_master_hash(writekey) return tagged_pair_hash(MUTABLE_WRITE_ENABLER_TAG, wem, peerid) def ssk_pubkey_fingerprint_hash(pubkey): return tagged_hash(MUTABLE_PUBKEY_TAG, pubkey) def ssk_readkey_hash(writekey): return tagged_hash(MUTABLE_READKEY_TAG, writekey, KEYLEN) def ssk_readkey_data_hash(IV, readkey): return tagged_pair_hash(MUTABLE_DATAKEY_TAG, IV, readkey, KEYLEN) def ssk_storage_index_hash(readkey): return tagged_hash(MUTABLE_STORAGEINDEX_TAG, readkey, KEYLEN) def timing_safe_compare(a, b): n = os.urandom(32) return bool(tagged_hash(n, a) == tagged_hash(n, b)) BACKUPDB_DIRHASH_TAG = b"allmydata_backupdb_dirhash_v1" def backupdb_dirhash(contents): return tagged_hash(BACKUPDB_DIRHASH_TAG, contents) def permute_server_hash(peer_selection_index, server_permutation_seed): return hashlib.sha1(peer_selection_index + server_permutation_seed).digest()