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