Skip to content

Commit ff80be4

Browse files
Md. Anisul Haquevil02github-actions[bot]Panquesito7realstealthninja
authored
feat: Add SHA-256 hashing algorithm (#2470)
* feat: add sha256 algorithm * Apply suggestions from code review Co-authored-by: Piotr Idzik <[email protected]> * fix: suggested changes * updating DIRECTORY.md * clang-format and clang-tidy fixes for de9c0b9 * fix: suggested changes * fix: suggested changes * clang-format and clang-tidy fixes for 86e65fe * fix: suggested changes Co-authored-by: Piotr Idzik <[email protected]> * Update hashing/sha256.cpp Co-authored-by: Piotr Idzik <[email protected]> * fix: suggested changes * fix: Apply suggestions from code review Co-authored-by: Piotr Idzik <[email protected]> * clang-format and clang-tidy fixes for a3d7333 * fix: suggested changes * fix: made some required changes * fix: suggested changes * fix: Apply suggestions from code review Co-authored-by: Piotr Idzik <[email protected]> * clang-format and clang-tidy fixes for 356be83 * fix: suggested changes * fix: added more tests * fix: Apply suggestions from code review Co-authored-by: Piotr Idzik <[email protected]> * fix: suggested changes * chore: apply suggestions from code review * chore: add print message after tests * clang-format and clang-tidy fixes for 48191bb * fix: suggested changes --------- Co-authored-by: Piotr Idzik <[email protected]> Co-authored-by: github-actions[bot] <[email protected]> Co-authored-by: David Leal <[email protected]> Co-authored-by: realstealthninja <[email protected]>
1 parent 1bfd46e commit ff80be4

File tree

2 files changed

+330
-0
lines changed

2 files changed

+330
-0
lines changed

DIRECTORY.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,7 @@
171171
* [Md5](https://github.com/TheAlgorithms/C-Plus-Plus/blob/HEAD/hashing/md5.cpp)
172172
* [Quadratic Probing Hash Table](https://github.com/TheAlgorithms/C-Plus-Plus/blob/HEAD/hashing/quadratic_probing_hash_table.cpp)
173173
* [Sha1](https://github.com/TheAlgorithms/C-Plus-Plus/blob/HEAD/hashing/sha1.cpp)
174+
* [Sha256](https://github.com/TheAlgorithms/C-Plus-Plus/blob/HEAD/hashing/sha256.cpp)
174175

175176
## Machine Learning
176177
* [A Star Search](https://github.com/TheAlgorithms/C-Plus-Plus/blob/HEAD/machine_learning/a_star_search.cpp)

hashing/sha256.cpp

Lines changed: 329 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,329 @@
1+
/**
2+
* @file
3+
* @author [Md. Anisul Haque](https://github.com/mdanisulh)
4+
* @brief Simple C++ implementation of the [SHA-256 Hashing Algorithm]
5+
* (https://en.wikipedia.org/wiki/SHA-2)
6+
*
7+
* @details
8+
* [SHA-2](https://en.wikipedia.org/wiki/SHA-2) is a set of cryptographic hash
9+
* functions that was designed by the
10+
* [NSA](https://en.wikipedia.org/wiki/National_Security_Agency) and first
11+
* published in 2001. SHA-256 is a part of the SHA-2 family. SHA-256 is widely
12+
* used for authenticating software packages and secure password hashing.
13+
*/
14+
15+
#include <array> /// For std::array
16+
#include <cassert> /// For assert
17+
#include <cstdint> /// For uint8_t, uint32_t and uint64_t data types
18+
#include <iomanip> /// For std::setfill and std::setw
19+
#include <iostream> /// For IO operations
20+
#include <sstream> /// For std::stringstream
21+
#include <utility> /// For std::move
22+
#include <vector> /// For std::vector
23+
24+
/**
25+
* @namespace hashing
26+
* @brief Hashing algorithms
27+
*/
28+
namespace hashing {
29+
/**
30+
* @namespace SHA-256
31+
* @brief Functions for the [SHA-256](https://en.wikipedia.org/wiki/SHA-2)
32+
* algorithm implementation
33+
*/
34+
namespace sha256 {
35+
/**
36+
* @class Hash
37+
* @brief Contains hash array and functions to update it and convert it to a
38+
* hexadecimal string
39+
*/
40+
class Hash {
41+
// Initialize array of hash values with first 32 bits of the fractional
42+
// parts of the square roots of the first 8 primes 2..19
43+
std::array<uint32_t, 8> hash = {0x6A09E667, 0xBB67AE85, 0x3C6EF372,
44+
0xA54FF53A, 0x510E527F, 0x9B05688C,
45+
0x1F83D9AB, 0x5BE0CD19};
46+
47+
public:
48+
void update(const std::array<uint32_t, 64> &blocks);
49+
std::string to_string() const;
50+
};
51+
52+
/**
53+
* @brief Rotates the bits of a 32-bit unsigned integer
54+
* @param n Integer to rotate
55+
* @param rotate Number of bits to rotate
56+
* @return uint32_t The rotated integer
57+
*/
58+
uint32_t right_rotate(uint32_t n, size_t rotate) {
59+
return (n >> rotate) | (n << (32 - rotate));
60+
}
61+
62+
/**
63+
* @brief Updates the hash array
64+
* @param blocks Message schedule array
65+
* @return void
66+
*/
67+
void Hash::update(const std::array<uint32_t, 64> &blocks) {
68+
// Initialize array of round constants with first 32 bits of the fractional
69+
// parts of the cube roots of the first 64 primes 2..311
70+
const std::array<uint32_t, 64> round_constants = {
71+
0x428A2F98, 0x71374491, 0xB5C0FBCF, 0xE9B5DBA5, 0x3956C25B, 0x59F111F1,
72+
0x923F82A4, 0xAB1C5ED5, 0xD807AA98, 0x12835B01, 0x243185BE, 0x550C7DC3,
73+
0x72BE5D74, 0x80DEB1FE, 0x9BDC06A7, 0xC19BF174, 0xE49B69C1, 0xEFBE4786,
74+
0x0FC19DC6, 0x240CA1CC, 0x2DE92C6F, 0x4A7484AA, 0x5CB0A9DC, 0x76F988DA,
75+
0x983E5152, 0xA831C66D, 0xB00327C8, 0xBF597FC7, 0xC6E00BF3, 0xD5A79147,
76+
0x06CA6351, 0x14292967, 0x27B70A85, 0x2E1B2138, 0x4D2C6DFC, 0x53380D13,
77+
0x650A7354, 0x766A0ABB, 0x81C2C92E, 0x92722C85, 0xA2BFE8A1, 0xA81A664B,
78+
0xC24B8B70, 0xC76C51A3, 0xD192E819, 0xD6990624, 0xF40E3585, 0x106AA070,
79+
0x19A4C116, 0x1E376C08, 0x2748774C, 0x34B0BCB5, 0x391C0CB3, 0x4ED8AA4A,
80+
0x5B9CCA4F, 0x682E6FF3, 0x748F82EE, 0x78A5636F, 0x84C87814, 0x8CC70208,
81+
0x90BEFFFA, 0xA4506CEB, 0xBEF9A3F7, 0xC67178F2};
82+
83+
// Initialize working variables
84+
auto a = hash[0];
85+
auto b = hash[1];
86+
auto c = hash[2];
87+
auto d = hash[3];
88+
auto e = hash[4];
89+
auto f = hash[5];
90+
auto g = hash[6];
91+
auto h = hash[7];
92+
93+
// Compression function main loop
94+
for (size_t block_num = 0; block_num < 64; ++block_num) {
95+
const auto s1 =
96+
right_rotate(e, 6) ^ right_rotate(e, 11) ^ right_rotate(e, 25);
97+
const auto ch = (e & f) ^ (~e & g);
98+
const auto temp1 =
99+
h + s1 + ch + round_constants[block_num] + blocks[block_num];
100+
const auto s0 =
101+
right_rotate(a, 2) ^ right_rotate(a, 13) ^ right_rotate(a, 22);
102+
const auto maj = (a & b) ^ (a & c) ^ (b & c);
103+
const auto temp2 = s0 + maj;
104+
105+
h = g;
106+
g = f;
107+
f = e;
108+
e = d + temp1;
109+
d = c;
110+
c = b;
111+
b = a;
112+
a = temp1 + temp2;
113+
}
114+
115+
// Update hash values
116+
hash[0] += a;
117+
hash[1] += b;
118+
hash[2] += c;
119+
hash[3] += d;
120+
hash[4] += e;
121+
hash[5] += f;
122+
hash[6] += g;
123+
hash[7] += h;
124+
}
125+
126+
/**
127+
* @brief Convert the hash to a hexadecimal string
128+
* @return std::string Final hash value
129+
*/
130+
std::string Hash::to_string() const {
131+
std::stringstream ss;
132+
for (size_t i = 0; i < 8; ++i) {
133+
ss << std::hex << std::setfill('0') << std::setw(8) << hash[i];
134+
}
135+
return ss.str();
136+
}
137+
138+
/**
139+
* @brief Computes size of the padded input
140+
* @param input Input string
141+
* @return size_t Size of the padded input
142+
*/
143+
std::size_t compute_padded_size(const std::size_t input_size) {
144+
if (input_size % 64 < 56) {
145+
return input_size + 64 - (input_size % 64);
146+
}
147+
return input_size + 128 - (input_size % 64);
148+
}
149+
150+
/**
151+
* @brief Returns the byte at position byte_num in in_value
152+
* @param in_value Input value
153+
* @param byte_num Position of byte to be returned
154+
* @return uint8_t Byte at position byte_num
155+
*/
156+
template <typename T>
157+
uint8_t extract_byte(const T in_value, const std::size_t byte_num) {
158+
if (sizeof(in_value) <= byte_num) {
159+
throw std::out_of_range("Byte at index byte_num does not exist");
160+
}
161+
return (in_value >> (byte_num * 8)) & 0xFF;
162+
}
163+
164+
/**
165+
* @brief Returns the character at pos after the input is padded
166+
* @param input Input string
167+
* @param pos Position of character to be returned
168+
* @return char Character at the index pos in the padded string
169+
*/
170+
char get_char(const std::string &input, std::size_t pos) {
171+
const auto input_size = input.length();
172+
if (pos < input_size) {
173+
return input[pos];
174+
}
175+
if (pos == input_size) {
176+
return '\x80';
177+
}
178+
const auto padded_input_size = compute_padded_size(input_size);
179+
if (pos < padded_input_size - 8) {
180+
return '\x00';
181+
}
182+
if (padded_input_size <= pos) {
183+
throw std::out_of_range("pos is out of range");
184+
}
185+
return static_cast<char>(
186+
extract_byte<size_t>(input_size * 8, padded_input_size - pos - 1));
187+
}
188+
189+
/**
190+
* @brief Creates the message schedule array
191+
* @param input Input string
192+
* @param byte_num Position of the first byte of the chunk
193+
* @return std::array<uint32_t, 64> Message schedule array
194+
*/
195+
std::array<uint32_t, 64> create_message_schedule_array(const std::string &input,
196+
const size_t byte_num) {
197+
std::array<uint32_t, 64> blocks{};
198+
199+
// Copy chunk into first 16 words of the message schedule array
200+
for (size_t block_num = 0; block_num < 16; ++block_num) {
201+
blocks[block_num] =
202+
(static_cast<uint8_t>(get_char(input, byte_num + block_num * 4))
203+
<< 24) |
204+
(static_cast<uint8_t>(get_char(input, byte_num + block_num * 4 + 1))
205+
<< 16) |
206+
(static_cast<uint8_t>(get_char(input, byte_num + block_num * 4 + 2))
207+
<< 8) |
208+
static_cast<uint8_t>(get_char(input, byte_num + block_num * 4 + 3));
209+
}
210+
211+
// Extend the first 16 words into remaining 48 words of the message schedule
212+
// array
213+
for (size_t block_num = 16; block_num < 64; ++block_num) {
214+
const auto s0 = right_rotate(blocks[block_num - 15], 7) ^
215+
right_rotate(blocks[block_num - 15], 18) ^
216+
(blocks[block_num - 15] >> 3);
217+
const auto s1 = right_rotate(blocks[block_num - 2], 17) ^
218+
right_rotate(blocks[block_num - 2], 19) ^
219+
(blocks[block_num - 2] >> 10);
220+
blocks[block_num] =
221+
blocks[block_num - 16] + s0 + blocks[block_num - 7] + s1;
222+
}
223+
224+
return blocks;
225+
}
226+
227+
/**
228+
* @brief Computes the final hash value
229+
* @param input Input string
230+
* @return std::string The final hash value
231+
*/
232+
std::string sha256(const std::string &input) {
233+
Hash h;
234+
// Process message in successive 512-bit (64-byte) chunks
235+
for (size_t byte_num = 0; byte_num < compute_padded_size(input.length());
236+
byte_num += 64) {
237+
h.update(create_message_schedule_array(input, byte_num));
238+
}
239+
return h.to_string();
240+
}
241+
} // namespace sha256
242+
} // namespace hashing
243+
244+
/**
245+
* @brief Self-test implementations
246+
* @returns void
247+
*/
248+
static void test_compute_padded_size() {
249+
assert(hashing::sha256::compute_padded_size(55) == 64);
250+
assert(hashing::sha256::compute_padded_size(56) == 128);
251+
assert(hashing::sha256::compute_padded_size(130) == 192);
252+
}
253+
254+
static void test_extract_byte() {
255+
assert(hashing::sha256::extract_byte<uint32_t>(512, 0) == 0);
256+
assert(hashing::sha256::extract_byte<uint32_t>(512, 1) == 2);
257+
bool exception = false;
258+
try {
259+
hashing::sha256::extract_byte<uint32_t>(512, 5);
260+
} catch (const std::out_of_range &) {
261+
exception = true;
262+
}
263+
assert(exception);
264+
}
265+
266+
static void test_get_char() {
267+
assert(hashing::sha256::get_char("test", 3) == 't');
268+
assert(hashing::sha256::get_char("test", 4) == '\x80');
269+
assert(hashing::sha256::get_char("test", 5) == '\x00');
270+
assert(hashing::sha256::get_char("test", 63) == 32);
271+
bool exception = false;
272+
try {
273+
hashing::sha256::get_char("test", 64);
274+
} catch (const std::out_of_range &) {
275+
exception = true;
276+
}
277+
assert(exception);
278+
}
279+
280+
static void test_right_rotate() {
281+
assert(hashing::sha256::right_rotate(128, 3) == 16);
282+
assert(hashing::sha256::right_rotate(1, 30) == 4);
283+
assert(hashing::sha256::right_rotate(6, 30) == 24);
284+
}
285+
286+
static void test_sha256() {
287+
struct TestCase {
288+
const std::string input;
289+
const std::string expected_hash;
290+
TestCase(std::string input, std::string expected_hash)
291+
: input(std::move(input)),
292+
expected_hash(std::move(expected_hash)) {}
293+
};
294+
const std::vector<TestCase> test_cases{
295+
TestCase(
296+
"",
297+
"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"),
298+
TestCase(
299+
"test",
300+
"9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08"),
301+
TestCase(
302+
"Hello World",
303+
"a591a6d40bf420404a011733cfb7b190d62c65bf0bcda32b57b277d9ad9f146e"),
304+
TestCase("Hello World!",
305+
"7f83b1657ff1fc53b92dc18148a1d65dfc2d4b1fa3d677284addd200126d9"
306+
"069")};
307+
for (const auto &tc : test_cases) {
308+
assert(hashing::sha256::sha256(tc.input) == tc.expected_hash);
309+
}
310+
}
311+
312+
static void test() {
313+
test_compute_padded_size();
314+
test_extract_byte();
315+
test_get_char();
316+
test_right_rotate();
317+
test_sha256();
318+
319+
std::cout << "All tests have successfully passed!\n";
320+
}
321+
322+
/**
323+
* @brief Main function
324+
* @returns 0 on exit
325+
*/
326+
int main() {
327+
test(); // Run self-test implementations
328+
return 0;
329+
}

0 commit comments

Comments
 (0)