1""" 2A collection of hashing and encoding utils. 3""" 4 5import base64 6import hashlib 7import hmac 8import os 9import random 10 11import salt.utils.files 12import salt.utils.platform 13import salt.utils.stringutils 14from salt.utils.decorators.jinja import jinja_filter 15 16 17@jinja_filter("base64_encode") 18def base64_b64encode(instr): 19 """ 20 Encode a string as base64 using the "modern" Python interface. 21 22 Among other possible differences, the "modern" encoder does not include 23 newline ('\\n') characters in the encoded output. 24 """ 25 return salt.utils.stringutils.to_unicode( 26 base64.b64encode(salt.utils.stringutils.to_bytes(instr)), 27 encoding="utf8" if salt.utils.platform.is_windows() else None, 28 ) 29 30 31@jinja_filter("base64_decode") 32def base64_b64decode(instr): 33 """ 34 Decode a base64-encoded string using the "modern" Python interface. 35 """ 36 decoded = base64.b64decode(salt.utils.stringutils.to_bytes(instr)) 37 try: 38 return salt.utils.stringutils.to_unicode( 39 decoded, encoding="utf8" if salt.utils.platform.is_windows() else None 40 ) 41 except UnicodeDecodeError: 42 return decoded 43 44 45def base64_encodestring(instr): 46 """ 47 Encode a byte-like object as base64 using the "modern" Python interface. 48 49 Among other possible differences, the "modern" encoder includes 50 a newline ('\\n') character after every 76 characters and always 51 at the end of the encoded string. 52 """ 53 return salt.utils.stringutils.to_unicode( 54 base64.encodebytes(salt.utils.stringutils.to_bytes(instr)), 55 encoding="utf8" if salt.utils.platform.is_windows() else None, 56 ) 57 58 59def base64_decodestring(instr): 60 """ 61 Decode a base64-encoded byte-like object using the "modern" Python interface. 62 """ 63 bvalue = salt.utils.stringutils.to_bytes(instr) 64 decoded = base64.decodebytes(bvalue) 65 try: 66 return salt.utils.stringutils.to_unicode( 67 decoded, encoding="utf8" if salt.utils.platform.is_windows() else None 68 ) 69 except UnicodeDecodeError: 70 return decoded 71 72 73@jinja_filter("md5") 74def md5_digest(instr): 75 """ 76 Generate an md5 hash of a given string. 77 """ 78 return salt.utils.stringutils.to_unicode( 79 hashlib.md5(salt.utils.stringutils.to_bytes(instr)).hexdigest() 80 ) 81 82 83@jinja_filter("sha1") 84def sha1_digest(instr): 85 """ 86 Generate an sha1 hash of a given string. 87 """ 88 return hashlib.sha1(salt.utils.stringutils.to_bytes(instr)).hexdigest() 89 90 91@jinja_filter("sha256") 92def sha256_digest(instr): 93 """ 94 Generate a sha256 hash of a given string. 95 """ 96 return salt.utils.stringutils.to_unicode( 97 hashlib.sha256(salt.utils.stringutils.to_bytes(instr)).hexdigest() 98 ) 99 100 101@jinja_filter("sha512") 102def sha512_digest(instr): 103 """ 104 Generate a sha512 hash of a given string 105 """ 106 return salt.utils.stringutils.to_unicode( 107 hashlib.sha512(salt.utils.stringutils.to_bytes(instr)).hexdigest() 108 ) 109 110 111@jinja_filter("hmac") 112def hmac_signature(string, shared_secret, challenge_hmac): 113 """ 114 Verify a challenging hmac signature against a string / shared-secret 115 Returns a boolean if the verification succeeded or failed. 116 """ 117 msg = salt.utils.stringutils.to_bytes(string) 118 key = salt.utils.stringutils.to_bytes(shared_secret) 119 challenge = salt.utils.stringutils.to_bytes(challenge_hmac) 120 hmac_hash = hmac.new(key, msg, hashlib.sha256) 121 valid_hmac = base64.b64encode(hmac_hash.digest()) 122 return valid_hmac == challenge 123 124 125@jinja_filter("hmac_compute") 126def hmac_compute(string, shared_secret): 127 """ 128 Create an hmac digest. 129 """ 130 msg = salt.utils.stringutils.to_bytes(string) 131 key = salt.utils.stringutils.to_bytes(shared_secret) 132 hmac_hash = hmac.new(key, msg, hashlib.sha256).hexdigest() 133 return hmac_hash 134 135 136@jinja_filter("rand_str") 137@jinja_filter("random_hash") 138def random_hash(size=9999999999, hash_type=None): 139 """ 140 Return a hash of a randomized data from random.SystemRandom() 141 """ 142 if not hash_type: 143 hash_type = "md5" 144 hasher = getattr(hashlib, hash_type) 145 return hasher( 146 salt.utils.stringutils.to_bytes(str(random.SystemRandom().randint(0, size))) 147 ).hexdigest() 148 149 150@jinja_filter("file_hashsum") 151def get_hash(path, form="sha256", chunk_size=65536): 152 """ 153 Get the hash sum of a file 154 155 This is better than ``get_sum`` for the following reasons: 156 - It does not read the entire file into memory. 157 - It does not return a string on error. The returned value of 158 ``get_sum`` cannot really be trusted since it is vulnerable to 159 collisions: ``get_sum(..., 'xyz') == 'Hash xyz not supported'`` 160 """ 161 hash_type = hasattr(hashlib, form) and getattr(hashlib, form) or None 162 if hash_type is None: 163 raise ValueError("Invalid hash type: {}".format(form)) 164 165 with salt.utils.files.fopen(path, "rb") as ifile: 166 hash_obj = hash_type() 167 # read the file in in chunks, not the entire file 168 for chunk in iter(lambda: ifile.read(chunk_size), b""): 169 hash_obj.update(chunk) 170 return hash_obj.hexdigest() 171 172 173class DigestCollector: 174 """ 175 Class to collect digest of the file tree. 176 """ 177 178 def __init__(self, form="sha256", buff=0x10000): 179 """ 180 Constructor of the class. 181 :param form: 182 """ 183 self.__digest = hasattr(hashlib, form) and getattr(hashlib, form)() or None 184 if self.__digest is None: 185 raise ValueError("Invalid hash type: {}".format(form)) 186 self.__buff = buff 187 188 def add(self, path): 189 """ 190 Update digest with the file content by path. 191 192 :param path: 193 :return: 194 """ 195 with salt.utils.files.fopen(path, "rb") as ifile: 196 for chunk in iter(lambda: ifile.read(self.__buff), b""): 197 self.__digest.update(chunk) 198 199 def digest(self): 200 """ 201 Get digest. 202 203 :return: 204 """ 205 206 return salt.utils.stringutils.to_str(self.__digest.hexdigest() + os.linesep) 207