Porytiles
Loading...
Searching...
No Matches
stream_digest.hpp
Go to the documentation of this file.
1#pragma once
2
3#include <array>
4#include <cmath>
5#include <cstdint>
6#include <iomanip>
7#include <istream>
8#include <sstream>
9#include <string>
10#include <vector>
11
12namespace porytiles2 {
13
18 public:
30 [[nodiscard]] std::string digest(std::istream &stream) const
31 {
32 // Initialize MD5 state
33 uint32_t a = 0x67452301;
34 uint32_t b = 0xEFCDAB89;
35 uint32_t c = 0x98BADCFE;
36 uint32_t d = 0x10325476;
37
38 // Read stream into buffer
39 std::vector<uint8_t> data;
40 char ch;
41 while (stream.get(ch)) {
42 data.push_back(static_cast<uint8_t>(ch));
43 }
44
45 // Apply padding
46 const uint64_t original_bit_length = data.size() * 8;
47 data.push_back(0x80); // Append '1' bit (plus zeros)
48
49 // Pad with zeros until length ≡ 448 (mod 512)
50 while ((data.size() % 64) != 56) {
51 data.push_back(0x00);
52 }
53
54 // Append original length as 64-bit little-endian
55 for (int i = 0; i < 8; ++i) {
56 data.push_back((original_bit_length >> (i * 8)) & 0xFF);
57 }
58
59 // Process each 512-bit block
60 for (size_t offset = 0; offset < data.size(); offset += 64) {
61 process_block(data.data() + offset, a, b, c, d);
62 }
63
64 // Convert to hex string
65 return to_hex_string(a, b, c, d);
66 }
67
68 private:
69 // MD5 auxiliary functions
70 static uint32_t f(uint32_t x, uint32_t y, uint32_t z)
71 {
72 return (x & y) | (~x & z);
73 }
74 static uint32_t g(uint32_t x, uint32_t y, uint32_t z)
75 {
76 return (x & z) | (y & ~z);
77 }
78 static uint32_t h(uint32_t x, uint32_t y, uint32_t z)
79 {
80 return x ^ y ^ z;
81 }
82 static uint32_t i(uint32_t x, uint32_t y, uint32_t z)
83 {
84 return y ^ (x | ~z);
85 }
86
87 // Left rotate
88 static uint32_t rotate_left(uint32_t value, uint32_t shift)
89 {
90 return (value << shift) | (value >> (32 - shift));
91 }
92
93 // Process a single 512-bit block
94 static void process_block(const uint8_t *block, uint32_t &a, uint32_t &b, uint32_t &c, uint32_t &d)
95 {
96 // Convert block to 32-bit words (little-endian)
97 uint32_t x[16];
98 for (int j = 0; j < 16; ++j) {
99 x[j] = block[j * 4] | (block[j * 4 + 1] << 8) | (block[j * 4 + 2] << 16) | (block[j * 4 + 3] << 24);
100 }
101
102 // Save original state
103 uint32_t aa = a;
104 uint32_t bb = b;
105 uint32_t cc = c;
106 uint32_t dd = d;
107
108 // Round 1
109 for (int j = 0; j < 16; ++j) {
110 uint32_t k_val = static_cast<uint32_t>(std::floor(std::abs(std::sin(j + 1)) * 4294967296.0));
111 uint32_t f_val = f(b, c, d);
112 uint32_t temp = a + f_val + x[j] + k_val;
113 temp = rotate_left(temp, s_[j]);
114 a = d;
115 d = c;
116 c = b;
117 b = b + temp;
118 }
119
120 // Round 2
121 for (int j = 0; j < 16; ++j) {
122 uint32_t k_val = static_cast<uint32_t>(std::floor(std::abs(std::sin(j + 17)) * 4294967296.0));
123 uint32_t g_val = g(b, c, d);
124 uint32_t x_index = (1 + 5 * j) % 16;
125 uint32_t temp = a + g_val + x[x_index] + k_val;
126 temp = rotate_left(temp, s_[16 + j]);
127 a = d;
128 d = c;
129 c = b;
130 b = b + temp;
131 }
132
133 // Round 3
134 for (int j = 0; j < 16; ++j) {
135 uint32_t k_val = static_cast<uint32_t>(std::floor(std::abs(std::sin(j + 33)) * 4294967296.0));
136 uint32_t h_val = h(b, c, d);
137 uint32_t x_index = (5 + 3 * j) % 16;
138 uint32_t temp = a + h_val + x[x_index] + k_val;
139 temp = rotate_left(temp, s_[32 + j]);
140 a = d;
141 d = c;
142 c = b;
143 b = b + temp;
144 }
145
146 // Round 4
147 for (int j = 0; j < 16; ++j) {
148 uint32_t k_val = static_cast<uint32_t>(std::floor(std::abs(std::sin(j + 49)) * 4294967296.0));
149 uint32_t i_val = i(b, c, d);
150 uint32_t x_index = (7 * j) % 16;
151 uint32_t temp = a + i_val + x[x_index] + k_val;
152 temp = rotate_left(temp, s_[48 + j]);
153 a = d;
154 d = c;
155 c = b;
156 b = b + temp;
157 }
158
159 // Add this block's hash to result
160 a += aa;
161 b += bb;
162 c += cc;
163 d += dd;
164 }
165
166 // Convert final state to hex string
167 [[nodiscard]] static std::string to_hex_string(uint32_t a, uint32_t b, uint32_t c, uint32_t d)
168 {
169 std::stringstream ss;
170 ss << std::hex << std::setfill('0');
171
172 // Output in little-endian byte order
173 auto append_word = [&ss](uint32_t word) {
174 for (int i = 0; i < 4; ++i) {
175 ss << std::setw(2) << ((word >> (i * 8)) & 0xFF);
176 }
177 };
178
179 append_word(a);
180 append_word(b);
181 append_word(c);
182 append_word(d);
183
184 return ss.str();
185 }
186
187 // Shift amounts for each round
188 static constexpr std::array<uint32_t, 64> s_ = {
189 // Round 1
190 7,
191 12,
192 17,
193 22,
194 7,
195 12,
196 17,
197 22,
198 7,
199 12,
200 17,
201 22,
202 7,
203 12,
204 17,
205 22,
206 // Round 2
207 5,
208 9,
209 14,
210 20,
211 5,
212 9,
213 14,
214 20,
215 5,
216 9,
217 14,
218 20,
219 5,
220 9,
221 14,
222 20,
223 // Round 3
224 4,
225 11,
226 16,
227 23,
228 4,
229 11,
230 16,
231 23,
232 4,
233 11,
234 16,
235 23,
236 4,
237 11,
238 16,
239 23,
240 // Round 4
241 6,
242 10,
243 15,
244 21,
245 6,
246 10,
247 15,
248 21,
249 6,
250 10,
251 15,
252 21,
253 6,
254 10,
255 15,
256 21};
257};
258
259} // namespace porytiles2
Computes the MD5 digest of an input stream.
std::string digest(std::istream &stream) const
Computes the MD5 digest of an input stream.