Coverage for /var/devmt/py/utils4_1.8.0/utils4/crypto.py: 100%
84 statements
« prev ^ index » next coverage.py v7.6.9, created at 2025-08-26 21:39 +0100
« prev ^ index » next coverage.py v7.6.9, created at 2025-08-26 21:39 +0100
1#!/usr/bin/env python3
2# -*- coding: utf-8 -*-
3"""
4:Purpose: This module provides a light wrapper around the ``base64``
5 and ``hashlib`` libraries to provide some additional
6 functionality.
8:Developer: J Berendt
9:Email: support@s3dev.uk
11:Comments: n/a
13:Example:
14 Example code use::
16 >>> from utils4.crypto import crypto
19 To obtain a quick MD5 hash::
21 >>> s = "The quick brown fox jumps over the lazy dog"
22 >>> output = crypto.md5(s)
23 >>> print(output)
25 9e107d9d372bb6826bd81d3542a419d6
28 To obtain a Base64 encoded MD5 hash::
30 >>> s = "The quick brown fox jumps over the lazy dog"
31 >>> output = crypto.b64md5(s)
32 >>> print(output)
34 OWUxMDdkOWQzNzJiYjY4MjZiZDgxZDM1NDJhNDE5ZDY=
37 For examples on checksumming a file, please refer to:
39 - :meth:`Crypto.checksum_crc32`
40 - :meth:`Crypto.checksum_md5`
41 - :meth:`Crypto.checksum_sha1`
42 - :meth:`Crypto.checksum_sha256`
43 - :meth:`Crypto.checksum_sha512`
45"""
46# pylint: disable=invalid-name
48import base64
49import hashlib
50import zlib
51from typing import Union
52from utils4 import convert
55class Crypto:
56 """Main class used for hashing and encoding.
58 This class acts as a simple wrapper around the ``base64`` and
59 ``hashlib`` libraries, providing additional functionality.
61 """
63 def b64(self, data: str, decode: bool=True) -> Union[bytes, str]:
64 """Create an encoded or decoded Base64 encryption.
66 Args:
67 data (str): String to be encoded. If a ``str`` data type is
68 received, it is encoded to ``bytes`` before encoding.
69 decode (bool, optional): Return a decoded string. Defaults to True.
71 Returns:
72 Union[bytes, str]: An encoded or decoded Base64 encrypted string.
74 """
75 data = self._encode(data)
76 b = base64.b64encode(data)
77 if decode:
78 b = b.decode()
79 return b
81 def b64md5(self, data: Union[iter, str], trunc: int=None) -> str:
82 """Create an optionally truncated Base64 encoded MD5 hash from a
83 string or array.
85 Args:
86 data (Union[iter, str]): A string or an iterable object containing
87 strings to be encoded.
88 trunc (int, optional): Truncate the Base64 string to (n)
89 characters. As string slicing is used, values such as ``-1``
90 are also valid. Defaults to None.
92 Returns:
93 str: An (optionally truncated) Base64 encoded MD5 hash of the
94 passed string or iterable.
96 """
97 s = ''.join(data).encode()
98 h = self.md5(s, decode=False)
99 b = self.b64(h, decode=True)
100 b = b[:trunc] if trunc else b
101 return b
103 @staticmethod
104 def checksum_crc32(path: str, return_integer: bool=False) -> Union[int, str]:
105 """Generate a 32-bit CRC32 checksum for the given file.
107 Args:
108 path (str): Full path to the file.
109 return_integer (bool, optional): Return the original unsigned
110 32-bit integer, rather than the hex string. Defaults to False.
112 Important:
113 This algorithm is *not* cryptographically strong and should not be
114 used for authentication or digital signatures; nor is it suitable
115 for use as a general hash algorithm.
117 -- zlib.crc32 `Documentation`_
119 .. _Documentation: https://docs.python.org/3/library/zlib.html#zlib.crc32
121 :Design:
122 This method breaks the file into 32768-byte chunks for more memory
123 efficient reading. Meaning this method has a maximum memory use
124 overhead of ~32K.
126 :Example:
128 Example for calculating the crc32 checksum for a file, returning a
129 hex string::
131 >>> from utils4.crypto import crypto
133 >>> crypto.checksum_crc32(path='/tmp/test.txt')
134 '2a30e66b'
137 Example for calculating the crc32 checksum for a file, returning
138 an integer::
140 >>> from utils4.crypto import crypto
142 >>> crypto.checksum_crc32(path='/tmp/test.txt', return_integer=True)
143 707847787
145 Returns:
146 Union[int, str]: If the ``return_integer`` value is ``False``
147 (default action), a CRC32 32-bit hex string (checksum string) of
148 the file's contents is returned. Otherwise, an unsigned 32-bit
149 integer is returned.
151 """
152 size = 1024*32 # 32K chunks
153 with open(path, 'rb') as f:
154 crcval = 0
155 chunk = f.read(size)
156 while len(chunk) > 0:
157 crcval = zlib.crc32(chunk, crcval)
158 chunk = f.read(size)
159 if return_integer:
160 rtn = crcval
161 else:
162 rtn = convert.int2hex(crcval & 0xFFFFFFFF)
163 return rtn
165 @staticmethod
166 def checksum_md5(path: str) -> str:
167 """Generate a 128-bit MD5 checksum for the given file.
169 Args:
170 path (str): Full path to the file.
172 :Design:
173 This method breaks the file into 32768-byte chunks
174 (64-bytes * 512 blocks) for more memory efficient reading;
175 taking advantage of the fact that MD5 uses 512-bit (64-byte) digest
176 blocks. Meaning this method has a maximum memory use overhead of
177 ~32K.
179 :Example:
181 Example calculating the MD5 checksum for a file::
183 >>> from utils4.crypto import crypto
185 >>> crypto.checksum_md5(path='/tmp/test.txt')
186 '9ec06901e8f25eb9810c5e0db88e7dcd'
188 Returns:
189 str: A 128-bit MD5 hex digest (checksum string) of the file's
190 contents.
192 """
193 md5 = hashlib.md5()
194 size = 64*512 # 32K chunks - 64-byte digest blocks (x512 blocks)
195 with open(path, 'rb') as f:
196 chunk = f.read(size)
197 while len(chunk) > 0:
198 md5.update(chunk)
199 chunk = f.read(size)
200 return md5.hexdigest()
202 @staticmethod
203 def checksum_sha1(path: str) -> str:
204 """Generate a 160-bit SHA1 checksum for the given file.
206 Args:
207 path (str): Full path to the file.
209 :Design:
210 This method breaks the file into 32768-byte chunks
211 (64-bytes * 512 blocks) for more memory efficient reading;
212 taking advantage of the fact that SHA1 uses 512-bit (64-byte)
213 digest blocks. Meaning this method has a maximum memory use
214 overhead of ~32K.
216 :Example:
218 Example calculating the SHA1 checksum for a file::
220 >>> from utils4.crypto import crypto
222 >>> crypto.checksum_sha1(path='/tmp/test.txt')
223 'e49a1493c637a24800119fb53ef7dbc580221e89'
225 Returns:
226 str: A 160-bit SHA1 hex digest (checksum string) of the file's
227 contents.
229 """
230 sha1 = hashlib.sha1()
231 size = 64*512 # 32K chunks - 64-byte digest blocks (x512 blocks)
232 with open(path, 'rb') as f:
233 chunk = f.read(size)
234 while len(chunk) > 0:
235 sha1.update(chunk)
236 chunk = f.read(size)
237 return sha1.hexdigest()
239 @staticmethod
240 def checksum_sha256(path: str) -> str:
241 """Generate a 256-bit SHA256 checksum for the given file.
243 Args:
244 path (str): Full path to the file.
246 :Design:
247 This method breaks the file into 32768-byte chunks
248 (64-bytes * 512 blocks) for more memory efficient reading;
249 taking advantage of the fact that SHA256 uses 512-bit (64-byte)
250 digest blocks. Meaning this method has a maximum memory use
251 overhead of ~32K.
253 :Example:
255 Example calculating the SHA256 checksum for a file::
257 >>> from utils4.crypto import crypto
259 >>> crypto.checksum_sha256(path='/tmp/test.txt')
260 'e899df8e51b60bf8a6ede73fe5c7b4267bf5e48937e848bac3c6efd906833821'
262 Returns:
263 str: A 256-bit SHA256 hex digest (checksum string) of the file's
264 contents.
266 """
267 sha256 = hashlib.sha256()
268 size = 64*512 # 32K chunks - 64-byte digest blocks (x512 blocks)
269 with open(path, 'rb') as f:
270 chunk = f.read(size)
271 while len(chunk) > 0:
272 sha256.update(chunk)
273 chunk = f.read(size)
274 return sha256.hexdigest()
276 @staticmethod
277 def checksum_sha512(path: str) -> str:
278 """Generate a 512-bit SHA512 checksum for the given file.
280 Args:
281 path (str): Full path to the file.
283 :Design:
284 This method breaks the file into 32768-byte chunks
285 (128-bytes * 256 blocks) for more memory efficient reading;
286 taking advantage of the fact that SHA512 uses 1024-bit (128-byte)
287 digest blocks. Meaning this method has a maximum memory use
288 overhead of ~32K.
290 :Example:
292 Example calculating the SHA512 checksum for a file::
294 >>> from utils4.crypto import crypto
296 >>> crypto.checksum_sha512(path='/tmp/test.txt')
297 ('247adcb6f5b284b3e45c9281171ba7a6'
298 '2502692ee9ee8020bd5827602972409f'
299 '9bdfc2ec7e5452223c19b3745d3f04e2'
300 '542ef0d0e075139d1ee3b5f678c9aaec') # Single string
302 Returns:
303 str: A 512-bit SHA512 hex digest (checksum string) of the file's
304 contents.
306 """
307 sha512 = hashlib.sha512()
308 size = 128*256 # 32K chunks - 128-byte digest blocks (x256 blocks)
309 with open(path, 'rb') as f:
310 chunk = f.read(size)
311 while len(chunk) > 0:
312 sha512.update(chunk)
313 chunk = f.read(size)
314 return sha512.hexdigest()
316 def md5(self, data: str, decode: bool=True) -> str:
317 """Create an optionally encoded or decoded MD5 hash.
319 Args:
320 data (str): String to be hashed. If a ``str`` data type
321 is passed, it is encoded to ``bytes`` before hashing.
322 decode (bool, optional): Return a decoded string. Defaults to True.
324 Returns:
325 str: An encoded or decoded MD5 hash, depending on the value passed
326 to the ``decode`` parameter.
328 """
329 data = self._encode(data)
330 h = hashlib.md5(data).hexdigest()
331 if not decode:
332 h = h.encode()
333 return h
335 @staticmethod
336 def _encode(data: Union[bytes, str]) -> bytes:
337 """Test if a string is ``str`` or ``bytes`` before processing.
339 Args:
340 data (Union[bytes, str]): String value to be encoded.
342 If the received ``data`` parameter is a ``str`` type, it is converted
343 to a ``bytes`` type and returned. If the string is already a ``bytes``
344 type, it is returned, unmodified.
346 Raises:
347 ValueError: If the ``data`` object is neither a ``str`` or
348 ``bytes`` type.
350 Returns:
351 bytes: A ``bytes`` encoded string.
353 """
354 if not isinstance(data, (bytes, str)):
355 raise ValueError('Expected a bytes or str type.')
356 data = data.encode() if isinstance(data, str) else data
357 return data
360crypto = Crypto()