1# Copyright 2024 The Sigstore Authors
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7# http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
14
15"""Digests for memory objects.
16
17These can only compute hashes of objects residing in memory, after they get
18converted to bytes.
19
20Example usage:
21```python
22>>> hasher = SHA256()
23>>> hasher.update(b"abcd")
24>>> digest = hasher.compute()
25>>> digest.digest_hex
26'88d4266fd4e6338d13b845fcf289579d209c897823b9217da3e161936f031589'
27```
28
29Or, passing the data directly in the constructor:
30```python
31>>> hasher = SHA256(b"abcd")
32>>> digest = hasher.compute()
33>>> digest.digest_hex
34'88d4266fd4e6338d13b845fcf289579d209c897823b9217da3e161936f031589'
35```
36"""
37
38import hashlib
39
40import blake3
41from typing_extensions import override
42
43from model_signing._hashing import hashing
44
45
46class SHA256(hashing.StreamingHashEngine):
47 """A wrapper around `hashlib.sha256`."""
48
49 def __init__(self, initial_data: bytes = b""):
50 """Initializes an instance of a SHA256 hash engine.
51
52 Args:
53 initial_data: Optional initial data to hash.
54 """
55 self._hasher = hashlib.sha256(initial_data)
56
57 @override
58 def update(self, data: bytes) -> None:
59 self._hasher.update(data)
60
61 @override
62 def reset(self, data: bytes = b"") -> None:
63 self._hasher = hashlib.sha256(data)
64
65 @override
66 def compute(self) -> hashing.Digest:
67 return hashing.Digest(self.digest_name, self._hasher.digest())
68
69 @property
70 @override
71 def digest_name(self) -> str:
72 return "sha256"
73
74 @property
75 @override
76 def digest_size(self) -> int:
77 return self._hasher.digest_size
78
79
80class BLAKE2(hashing.StreamingHashEngine):
81 """A wrapper around `hashlib.blake2b`."""
82
83 def __init__(self, initial_data: bytes = b""):
84 """Initializes an instance of a BLAKE2 hash engine.
85
86 Args:
87 initial_data: Optional initial data to hash.
88 """
89 self._hasher = hashlib.blake2b(initial_data)
90
91 @override
92 def update(self, data: bytes) -> None:
93 self._hasher.update(data)
94
95 @override
96 def reset(self, data: bytes = b"") -> None:
97 self._hasher = hashlib.blake2b(data)
98
99 @override
100 def compute(self) -> hashing.Digest:
101 return hashing.Digest(self.digest_name, self._hasher.digest())
102
103 @property
104 @override
105 def digest_name(self) -> str:
106 return "blake2b"
107
108 @property
109 @override
110 def digest_size(self) -> int:
111 return self._hasher.digest_size
112
113
114class BLAKE3(hashing.StreamingHashEngine):
115 """A wrapper around `blake3.blake3`."""
116
117 def __init__(self, initial_data: bytes = b""):
118 """Initializes an instance of a BLAKE3 hash engine.
119
120 Args:
121 initial_data: Optional initial data to hash.
122 """
123 self._hasher = blake3.blake3(initial_data)
124
125 @override
126 def update(self, data: bytes) -> None:
127 self._hasher.update(data)
128
129 @override
130 def reset(self, data: bytes = b"") -> None:
131 self._hasher = blake3.blake3(data)
132
133 @override
134 def compute(self) -> hashing.Digest:
135 return hashing.Digest(self.digest_name, self._hasher.digest())
136
137 @property
138 @override
139 def digest_name(self) -> str:
140 return "blake3"
141
142 @property
143 @override
144 def digest_size(self) -> int:
145 return self._hasher.digest_size