OXYGENHUB
◆ main openmsx / src / utils / sha1.cc
10.4 KiB · C++ · last changed chore(): initial commit 1 month ago source blame history raw
1
/*
2
Based on:
3
100% free public domain implementation of the SHA-1 algorithm
4
by Dominik Reichl <[email protected]>
5
6
Refactored in C++ style as part of openMSX
7
by Maarten ter Huurne and Wouter Vermaelen.
8
9
=== Test Vectors (from FIPS PUB 180-1) ===
10
11
"abc"
12
A9993E36 4706816A BA3E2571 7850C26C 9CD0D89D
13
14
"abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq"
15
84983E44 1C3BD26E BAAE4AA1 F95129E5 E54670F1
16
17
A million repetitions of "a"
18
34AA973C D4C4DAA4 F61EEB2B DBAD2731 6534016F
19
*/
20
21
#include "sha1.hh"
22
23
#include "MSXException.hh"
24
25
#include "endian.hh"
26
#include "narrow.hh"
27
#include "ranges.hh"
28
#include "xrange.hh"
29
30
#include <algorithm>
31
#include <bit>
32
#include <cassert>
33
#include <cstring>
34
#ifdef __SSE2__
35
#include <emmintrin.h> // SSE2
36
#endif
37
38
namespace openmsx {
39
40
// Rotate x bits to the left
41
[[nodiscard]] static constexpr uint32_t rol32(uint32_t value, int bits)
42
{
43
	return (value << bits) | (value >> (32 - bits));
44
}
45
46
class WorkspaceBlock {
47
private:
48
	std::array<uint32_t, 16> data;
49
50
	[[nodiscard]] uint32_t next0(int i)
51
	{
52
		data[i] = Endian::readB32(&data[i]);
53
		return data[i];
54
	}
55
	[[nodiscard]] uint32_t next(int i)
56
	{
57
		return data[i & 15] = rol32(
58
			data[(i + 13) & 15] ^ data[(i + 8) & 15] ^
59
			data[(i +  2) & 15] ^ data[ i      & 15]
60
			, 1);
61
	}
62
63
public:
64
	explicit WorkspaceBlock(std::span<const uint8_t, 64> buffer);
65
66
	// SHA-1 rounds
67
	void r0(uint32_t v, uint32_t& w, uint32_t x, uint32_t y, uint32_t& z, int i)
68
	{
69
		z += ((w & (x ^ y)) ^ y) + next0(i) + 0x5A827999 + rol32(v, 5);
70
		w = rol32(w, 30);
71
	}
72
	void r1(uint32_t v, uint32_t& w, uint32_t x, uint32_t y, uint32_t& z, int i)
73
	{
74
		z += ((w & (x ^ y)) ^ y) + next(i) + 0x5A827999 + rol32(v, 5);
75
		w = rol32(w, 30);
76
	}
77
	void r2(uint32_t v, uint32_t& w, uint32_t x, uint32_t y, uint32_t& z, int i)
78
	{
79
		z += (w ^ x ^ y) + next(i) + 0x6ED9EBA1 + rol32(v, 5);
80
		w = rol32(w, 30);
81
	}
82
	void r3(uint32_t v, uint32_t& w, uint32_t x, uint32_t y, uint32_t& z, int i)
83
	{
84
		z += (((w | x) & y) | (w & x)) + next(i) + 0x8F1BBCDC + rol32(v, 5);
85
		w = rol32(w, 30);
86
	}
87
	void r4(uint32_t v, uint32_t& w, uint32_t x, uint32_t y, uint32_t& z, int i)
88
	{
89
		z += (w ^ x ^ y) + next(i) + 0xCA62C1D6 + rol32(v, 5);
90
		w = rol32(w, 30);
91
	}
92
};
93
94
WorkspaceBlock::WorkspaceBlock(std::span<const uint8_t, 64> buffer)
95
{
96
	memcpy(data.data(), buffer.data(), sizeof(data));
97
}
98
99
100
// class Sha1Sum
101
102
Sha1Sum::Sha1Sum()
103
{
104
	clear();
105
}
106
107
Sha1Sum::Sha1Sum(std::string_view hex)
108
{
109
	parse(hex);
110
}
111
112
void Sha1Sum::parse(std::string_view hex)
113
{
114
	if (hex.size() != 40) {
115
		throw MSXException("Invalid sha1, should be exactly 40 digits long: ", hex);
116
	}
117
	parse40(subspan<40>(hex));
118
}
119
120
#ifdef __SSE2__
121
// emulate some missing unsigned-8-bit comparison functions
122
[[nodiscard]] static inline __m128i _mm_cmpge_epu8(__m128i a, __m128i b)
123
{
124
	return _mm_cmpeq_epi8(_mm_max_epu8(a, b), a);
125
}
126
127
[[nodiscard]] static inline __m128i _mm_cmple_epu8(__m128i a, __m128i b)
128
{
129
	return _mm_cmpge_epu8(b, a);
130
}
131
132
// load 64-bit (possibly unaligned) and swap bytes
133
[[nodiscard]] static inline int64_t loadSwap64(const char* s)
134
{
135
	return narrow_cast<int64_t>(Endian::read_UA_B64(s));
136
}
137
138
#else
139
140
[[nodiscard]] static constexpr unsigned hex(char x, const char* str)
141
{
142
	if (('0' <= x) && (x <= '9')) return x - '0';
143
	if (('a' <= x) && (x <= 'f')) return x - 'a' + 10;
144
	if (('A' <= x) && (x <= 'F')) return x - 'A' + 10;
145
	throw MSXException("Invalid sha1, digits should be 0-9, a-f: ",
146
	                   std::string_view(str, 40));
147
}
148
#endif
149
150
void Sha1Sum::parse40(std::span<const char, 40> str)
151
{
152
#ifdef __SSE2__
153
	// SSE2 version
154
155
	// load characters
156
	__m128i s0 = _mm_set_epi64x(loadSwap64(&str[ 8]),     loadSwap64(&str[ 0]));
157
	__m128i s1 = _mm_set_epi64x(loadSwap64(&str[24]),     loadSwap64(&str[16]));
158
	__m128i s2 = _mm_set_epi64x('0' * 0x0101010101010101, loadSwap64(&str[32]));
159
160
	// chars - '0'
161
	__m128i cc0 = _mm_set1_epi8(char(-'0'));
162
	__m128i s0_0 = _mm_add_epi8(s0, cc0);
163
	__m128i s1_0 = _mm_add_epi8(s1, cc0);
164
	__m128i s2_0 = _mm_add_epi8(s2, cc0);
165
166
	// (chars | 32) - 'a'  (convert uppercase 'A'-'F' into lower case)
167
	__m128i c32 = _mm_set1_epi8(32);
168
	__m128i cca = _mm_set1_epi8(char(-'a'));
169
	__m128i s0_a = _mm_add_epi8(_mm_or_si128(s0, c32), cca);
170
	__m128i s1_a = _mm_add_epi8(_mm_or_si128(s1, c32), cca);
171
	__m128i s2_a = _mm_add_epi8(_mm_or_si128(s2, c32), cca);
172
173
	// was in range '0'-'9'?
174
	__m128i c9  = _mm_set1_epi8(9);
175
	__m128i c0_0 = _mm_cmple_epu8(s0_0, c9);
176
	__m128i c1_0 = _mm_cmple_epu8(s1_0, c9);
177
	__m128i c2_0 = _mm_cmple_epu8(s2_0, c9);
178
179
	// was in range 'a'-'f'?
180
	__m128i c5  = _mm_set1_epi8(5);
181
	__m128i c0_a = _mm_cmple_epu8(s0_a, c5);
182
	__m128i c1_a = _mm_cmple_epu8(s1_a, c5);
183
	__m128i c2_a = _mm_cmple_epu8(s2_a, c5);
184
185
	// either '0'-'9' or 'a'-f' must be in range for all chars
186
	__m128i ok0 = _mm_or_si128(c0_0, c0_a);
187
	__m128i ok1 = _mm_or_si128(c1_0, c1_a);
188
	__m128i ok2 = _mm_or_si128(c2_0, c2_a);
189
	__m128i ok = _mm_and_si128(_mm_and_si128(ok0, ok1), ok2);
190
	if (_mm_movemask_epi8(ok) != 0xffff) [[unlikely]] {
191
		throw MSXException("Invalid sha1, digits should be 0-9, a-f: ",
192
		                   std::string_view(str.data(), 40));
193
	}
194
195
	// '0'-'9' to numeric value (or zero)
196
	__m128i d0_0 = _mm_and_si128(s0_0, c0_0);
197
	__m128i d1_0 = _mm_and_si128(s1_0, c1_0);
198
	__m128i d2_0 = _mm_and_si128(s2_0, c2_0);
199
200
	// 'a'-'f' to numeric value (or zero)
201
	__m128i c10 = _mm_set1_epi8(10);
202
	__m128i d0_a = _mm_and_si128(_mm_add_epi8(s0_a, c10), c0_a);
203
	__m128i d1_a = _mm_and_si128(_mm_add_epi8(s1_a, c10), c1_a);
204
	__m128i d2_a = _mm_and_si128(_mm_add_epi8(s2_a, c10), c2_a);
205
206
	// combine  0-9 / 10-15  into  0-15
207
	__m128i d0 = _mm_or_si128(d0_0, d0_a);
208
	__m128i d1 = _mm_or_si128(d1_0, d1_a);
209
	__m128i d2 = _mm_or_si128(d2_0, d2_a);
210
211
	// compact bytes to nibbles
212
	__m128i c00ff = _mm_set1_epi16(0x00ff);
213
	__m128i e0 = _mm_and_si128(_mm_or_si128(d0, _mm_srli_epi16(d0, 4)), c00ff);
214
	__m128i e1 = _mm_and_si128(_mm_or_si128(d1, _mm_srli_epi16(d1, 4)), c00ff);
215
	__m128i e2 = _mm_and_si128(_mm_or_si128(d2, _mm_srli_epi16(d2, 4)), c00ff);
216
	__m128i f0 = _mm_packus_epi16(e0, e0);
217
	__m128i f1 = _mm_packus_epi16(e1, e1);
218
	__m128i f2 = _mm_packus_epi16(e2, e2);
219
220
	// store result
221
	_mm_storeu_si128(std::bit_cast<__m128i*>(a.data()), _mm_unpacklo_epi64(f0, f1));
222
	a[4] = _mm_cvtsi128_si32(f2);
223
#else
224
	// equivalent c++ version
225
	const char* p = str.data();
226
	for (auto& ai : a) {
227
		unsigned t = 0;
228
		repeat(8, [&] {
229
			t <<= 4;
230
			t |= hex(*p++, str.data());
231
		});
232
		ai = t;
233
	}
234
#endif
235
}
236
237
[[nodiscard]] static constexpr char digit(unsigned x)
238
{
239
	return narrow<char>((x < 10) ? (x + '0') : (x - 10 + 'a'));
240
}
241
void Sha1Sum::toBuffer(std::span<char, 40> buf) const
242
{
243
	size_t i = 0;
244
	for (const auto& ai : a) {
245
		for (int j = 28; j >= 0; j -= 4) {
246
			buf[i++] = digit((ai >> j) & 0xf);
247
		}
248
	}
249
}
250
251
bool Sha1Sum::empty() const
252
{
253
	return std::ranges::all_of(a, [](auto& e) { return e == 0; });
254
}
255
void Sha1Sum::clear()
256
{
257
	std::ranges::fill(a, 0);
258
}
259
260
261
// class SHA1
262
263
SHA1::SHA1()
264
{
265
	// SHA1 initialization constants
266
	m_state.a[0] = 0x67452301;
267
	m_state.a[1] = 0xEFCDAB89;
268
	m_state.a[2] = 0x98BADCFE;
269
	m_state.a[3] = 0x10325476;
270
	m_state.a[4] = 0xC3D2E1F0;
271
}
272
273
void SHA1::transform(std::span<const uint8_t, 64> buffer)
274
{
275
	WorkspaceBlock block(buffer);
276
277
	// Copy m_state[] to working vars
278
	uint32_t a = m_state.a[0];
279
	uint32_t b = m_state.a[1];
280
	uint32_t c = m_state.a[2];
281
	uint32_t d = m_state.a[3];
282
	uint32_t e = m_state.a[4];
283
284
	// 4 rounds of 20 operations each. Loop unrolled
285
	block.r0(a,b,c,d,e, 0); block.r0(e,a,b,c,d, 1); block.r0(d,e,a,b,c, 2);
286
	block.r0(c,d,e,a,b, 3); block.r0(b,c,d,e,a, 4); block.r0(a,b,c,d,e, 5);
287
	block.r0(e,a,b,c,d, 6); block.r0(d,e,a,b,c, 7); block.r0(c,d,e,a,b, 8);
288
	block.r0(b,c,d,e,a, 9); block.r0(a,b,c,d,e,10); block.r0(e,a,b,c,d,11);
289
	block.r0(d,e,a,b,c,12); block.r0(c,d,e,a,b,13); block.r0(b,c,d,e,a,14);
290
	block.r0(a,b,c,d,e,15); block.r1(e,a,b,c,d,16); block.r1(d,e,a,b,c,17);
291
	block.r1(c,d,e,a,b,18); block.r1(b,c,d,e,a,19); block.r2(a,b,c,d,e,20);
292
	block.r2(e,a,b,c,d,21); block.r2(d,e,a,b,c,22); block.r2(c,d,e,a,b,23);
293
	block.r2(b,c,d,e,a,24); block.r2(a,b,c,d,e,25); block.r2(e,a,b,c,d,26);
294
	block.r2(d,e,a,b,c,27); block.r2(c,d,e,a,b,28); block.r2(b,c,d,e,a,29);
295
	block.r2(a,b,c,d,e,30); block.r2(e,a,b,c,d,31); block.r2(d,e,a,b,c,32);
296
	block.r2(c,d,e,a,b,33); block.r2(b,c,d,e,a,34); block.r2(a,b,c,d,e,35);
297
	block.r2(e,a,b,c,d,36); block.r2(d,e,a,b,c,37); block.r2(c,d,e,a,b,38);
298
	block.r2(b,c,d,e,a,39); block.r3(a,b,c,d,e,40); block.r3(e,a,b,c,d,41);
299
	block.r3(d,e,a,b,c,42); block.r3(c,d,e,a,b,43); block.r3(b,c,d,e,a,44);
300
	block.r3(a,b,c,d,e,45); block.r3(e,a,b,c,d,46); block.r3(d,e,a,b,c,47);
301
	block.r3(c,d,e,a,b,48); block.r3(b,c,d,e,a,49); block.r3(a,b,c,d,e,50);
302
	block.r3(e,a,b,c,d,51); block.r3(d,e,a,b,c,52); block.r3(c,d,e,a,b,53);
303
	block.r3(b,c,d,e,a,54); block.r3(a,b,c,d,e,55); block.r3(e,a,b,c,d,56);
304
	block.r3(d,e,a,b,c,57); block.r3(c,d,e,a,b,58); block.r3(b,c,d,e,a,59);
305
	block.r4(a,b,c,d,e,60); block.r4(e,a,b,c,d,61); block.r4(d,e,a,b,c,62);
306
	block.r4(c,d,e,a,b,63); block.r4(b,c,d,e,a,64); block.r4(a,b,c,d,e,65);
307
	block.r4(e,a,b,c,d,66); block.r4(d,e,a,b,c,67); block.r4(c,d,e,a,b,68);
308
	block.r4(b,c,d,e,a,69); block.r4(a,b,c,d,e,70); block.r4(e,a,b,c,d,71);
309
	block.r4(d,e,a,b,c,72); block.r4(c,d,e,a,b,73); block.r4(b,c,d,e,a,74);
310
	block.r4(a,b,c,d,e,75); block.r4(e,a,b,c,d,76); block.r4(d,e,a,b,c,77);
311
	block.r4(c,d,e,a,b,78); block.r4(b,c,d,e,a,79);
312
313
	// Add the working vars back into m_state[]
314
	m_state.a[0] += a;
315
	m_state.a[1] += b;
316
	m_state.a[2] += c;
317
	m_state.a[3] += d;
318
	m_state.a[4] += e;
319
}
320
321
// Use this function to hash in binary data and strings
322
void SHA1::update(std::span<const uint8_t> data)
323
{
324
	assert(!m_finalized);
325
	uint32_t j = m_count & 63;
326
327
	size_t len = data.size();
328
	m_count += len;
329
330
	size_t i;
331
	if ((j + len) > 63) {
332
		i = 64 - j;
333
		copy_to_range(data.subspan(0, i), subspan(m_buffer, j));
334
		transform(m_buffer);
335
		for (/**/; i + 63 < len; i += 64) {
336
			transform(subspan<64>(data, i));
337
		}
338
		j = 0;
339
	} else {
340
		i = 0;
341
	}
342
	copy_to_range(data.subspan(i, len - i), subspan(m_buffer, j));
343
}
344
345
void SHA1::finalize()
346
{
347
	assert(!m_finalized);
348
349
	uint32_t j = m_count & 63;
350
	m_buffer[j++] = 0x80;
351
	if (j > 56) {
352
		std::ranges::fill(subspan(m_buffer, j, 64 - j), 0);
353
		transform(m_buffer);
354
		j = 0;
355
	}
356
	std::ranges::fill(subspan(m_buffer, j, 56 - j), 0);
357
	Endian::B64 finalCount(8 * m_count); // convert number of bytes to bits
358
	memcpy(&m_buffer[56], &finalCount, 8);
359
	transform(m_buffer);
360
361
	m_finalized = true;
362
}
363
364
Sha1Sum SHA1::digest()
365
{
366
	if (!m_finalized) finalize();
367
	return m_state;
368
}
369
370
Sha1Sum SHA1::calc(std::span<const uint8_t> data)
371
{
372
	SHA1 sha1;
373
	sha1.update(data);
374
	return sha1.digest();
375
}
376
377
} // namespace openmsx