|
14 | 14 | # limitations under the License. |
15 | 15 | # |
16 | 16 |
|
| 17 | +import os |
17 | 18 | import pickle |
18 | 19 | import hmac |
19 | 20 | import hashlib |
|
24 | 25 |
|
25 | 26 |
|
26 | 27 | class SafePickle: |
27 | | - key = b'shared-key' |
28 | | - """ |
29 | | - Example: |
30 | | - >>> from bigdl.nano.utils.common import SafePickle |
31 | | - >>> with open(file_path, 'wb') as file: |
32 | | - >>> signature = SafePickle.dump(data, file, return_digest=True) |
33 | | - >>> with open(file_path, 'rb') as file: |
34 | | - >>> data = SafePickle.load(file, signature) |
35 | | - """ |
| 28 | + _key = None |
| 29 | + |
| 30 | + @classmethod |
| 31 | + def _get_key(cls): |
| 32 | + if cls._key is None: |
| 33 | + env_key = os.environ.get('BIGDL_SAFE_PICKLE_KEY') |
| 34 | + if env_key: |
| 35 | + cls._key = bytes.fromhex(env_key) |
| 36 | + else: |
| 37 | + cls._key = os.urandom(32) |
| 38 | + os.environ['BIGDL_SAFE_PICKLE_KEY'] = cls._key.hex() |
| 39 | + return cls._key |
| 40 | + |
36 | 41 | @classmethod |
37 | | - def dump(self, obj, file, return_digest=False, *args, **kwargs): |
| 42 | + def dump(cls, obj, file, return_digest=False, *args, **kwargs): |
| 43 | + """ |
| 44 | + Example: |
| 45 | + >>> from bigdl.nano.utils.common import SafePickle |
| 46 | + >>> with open(file_path, 'wb') as file: |
| 47 | + >>> SafePickle.dump(data, file) |
| 48 | + """ |
| 49 | + pickled_data = pickle.dumps(obj) |
| 50 | + file.write(pickled_data) |
| 51 | + digest = hmac.new(cls._get_key(), pickled_data, hashlib.sha256).hexdigest() |
38 | 52 | if return_digest: |
39 | | - pickled_data = pickle.dumps(obj) |
40 | | - file.write(pickled_data) |
41 | | - digest = hmac.new(self.key, pickled_data, hashlib.sha1).hexdigest() |
42 | 53 | return digest |
43 | | - else: |
44 | | - pickle.dump(obj, file, *args, **kwargs) |
| 54 | + sig_path = file.name + '.sig' |
| 55 | + with open(sig_path, 'w') as sig_file: |
| 56 | + sig_file.write(digest) |
45 | 57 |
|
46 | 58 | @classmethod |
47 | | - def load(self, file, digest=None, *args, **kwargs): |
| 59 | + def load(cls, file, digest=None, *args, **kwargs): |
| 60 | + """ |
| 61 | + Example: |
| 62 | + >>> from bigdl.nano.utils.common import SafePickle |
| 63 | + >>> with open(file_path, 'rb') as file: |
| 64 | + >>> data = SafePickle.load(file) |
| 65 | + """ |
| 66 | + content = file.read() |
| 67 | + |
| 68 | + if digest is None: |
| 69 | + sig_path = file.name + '.sig' |
| 70 | + if os.path.exists(sig_path): |
| 71 | + with open(sig_path, 'r') as sig_file: |
| 72 | + digest = sig_file.read().strip() |
| 73 | + |
48 | 74 | if digest: |
49 | | - content = file.read() |
50 | | - new_digest = hmac.new(self.key, content, hashlib.sha1).hexdigest() |
51 | | - if digest != new_digest: |
52 | | - invalidInputError(False, 'Pickle safe check failed') |
53 | | - file.seek(0) |
54 | | - return pickle.load(file, *args, **kwargs) |
| 75 | + new_digest = hmac.new(cls._get_key(), content, hashlib.sha256).hexdigest() |
| 76 | + if not hmac.compare_digest(digest, new_digest): |
| 77 | + invalidInputError(False, |
| 78 | + 'Pickle integrity check failed: ' |
| 79 | + 'file may have been tampered with') |
| 80 | + else: |
| 81 | + invalidInputError(False, |
| 82 | + 'No HMAC signature found for pickle file: ' |
| 83 | + 'cannot verify integrity') |
| 84 | + |
| 85 | + return pickle.loads(content, *args, **kwargs) |
0 commit comments