I am attempting to create a SHA256 hashing library for a project I am working on, and I started off writing the code completely in cpp. I then converted the cpp file to be compatible with Arduino by making a .h file and writing a little bit of code in the .ino file for Serial printing.
The cpp code outputs the correct hash (according to this site) when compiled with the g++ compiler and run from a mac computer, but the Arduino-compiled sketch run on an Arduino Nano does not.
Input: "abcdefg"
Correct hash / g++ output from mac: "7d1a54127b222502f5b79b5fb0803061152a44f92b37e23c6527baf665d4da9a"
Arduino Nano output:
"ee2ea23488de8c2cd89c5e70854573af"
The fact that the Nano output is 32 characters as opposed to 64 seems suspicious, but nothing in the calculation code is different between both cases. I tried putting the include statements for assorted standard library stuff in the Arduino cpp file even though they already come bundled with Arduino, and they did not make a difference. Could there possibly be a difference in the way the Arduino compiles the code (even though it is also g++)? If not, might there be a variation in the way certain code is performed on an Arduino as opposed to a Mac that caused these differing outputs?
Here is the .cpp code run from a Mac and compiled with g++:
#include <stdint.h>
#include <iostream>
#include <math.h>
using namespace std;
// Initialize hash values (first 32 bits of the fractional parts of the square roots of the first 8 primes)
uint32_t hsh[8] = {0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, 0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19};
// Initialize keys (first 32 bits of the fractional parts of the cube roots of the first 64 primes)
uint32_t k[64] = {
0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5,
0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174,
0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967,
0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85,
0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,
0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2
};
struct ChunkData {
uint32_t numOfChunks;
uint32_t* chunks;
};
// UTILITY FUNCTIONS
uint32_t rrot(uint32_t input, uint8_t shift) {
return (input >> shift) | (input << (32-shift));
}
// SHA256 FUNCTIONS
ChunkData preProcess(char* input) {
// Convert input to arrays containing 64 8bit ints each, all contained in a main array
uint32_t numOfArrays = ceil((float)(strlen(input)+9)/64); // padded 9 bytes for 128 and last 8 length ints
uint8_t mainArray [numOfArrays*64];
for (int i = 0; i < numOfArrays; i++) {
for (int j = 0; j < 64; j++) {
if ((i*64)+j < strlen(input)) {
mainArray[(i*64)+j] = input[(i*64)+j];
} else if ((i*64)+j == strlen(input)) {
mainArray[(i*64)+j] = 128;
} else {
mainArray[(i*64)+j] = 0;
}
}
}
// Set last 64 bits to big-endian integer representing length of input
static uint64_t inputBits = strlen(input) * 8;
for (int i = 0; i < 8; i++) {
mainArray[((numOfArrays-1)*64)+(63-i)] = (inputBits >> i * 8) & 255;
}
uint32_t chunks[(numOfArrays*64)+64];
for (int i = 0; i < numOfArrays; i++) {
for (int j = 0; j < 16; j++) {
uint32_t element = 0;
for (int l = 0; l < 4; l++) {
element = element + (mainArray[(i*64)+(j*4)+l] << ((3-l)*8));
}
chunks[(i*64)+j] = element;
}
for (int j = 16; j < 64; j++) {
uint32_t s0 = rrot(chunks[(i*64)+j-15], 7) ^ rrot(chunks[(i*64)+j-15], 18) ^ (chunks[(i*64)+j-15] >> 3);
uint32_t s1 = rrot(chunks[(i*64)+j-2], 17) ^ rrot(chunks[(i*64)+j-2], 19) ^ (chunks[(i*64)+j-2] >> 10);
chunks[(i*64)+j] = chunks[(i*64)+j-16] + s0 + chunks[(i*64)+j-7] + s1;
}
}
struct ChunkData cd = {numOfArrays, chunks};
return cd;
}
char* compress(uint32_t* chunks, uint32_t numOfChunks) {
for (int i = 0; i < numOfChunks; i++) {
uint32_t a=hsh[0], b=hsh[1], c=hsh[2], d=hsh[3], e=hsh[4], f=hsh[5], g=hsh[6], h=hsh[7];
for (int j = 0; j < 64; j++) {
uint32_t S1 = rrot(e, 6) ^ rrot(e, 11) ^ rrot(e, 25);
uint32_t ch = (e & f) ^ ((~e) & g);
uint32_t temp1 = h + S1 + ch + k[j] + chunks[(i*64)+j];
uint32_t S0 = rrot(a, 2) ^ rrot(a, 13) ^ rrot(a, 22);
uint32_t maj = (a & b) ^ (a & c) ^ (b & c);
uint32_t temp2 = S0 + maj;
h = g;
g = f;
f = e;
e = d + temp1;
d = c;
c = b;
b = a;
a = temp1 + temp2;
}
hsh[0] = hsh[0] + a;
hsh[1] = hsh[1] + b;
hsh[2] = hsh[2] + c;
hsh[3] = hsh[3] + d;
hsh[4] = hsh[4] + e;
hsh[5] = hsh[5] + f;
hsh[6] = hsh[6] + g;
hsh[7] = hsh[7] + h;
}
static char digest[65];
sprintf(digest,"%x%x%x%x%x%x%x%x",hsh[0],hsh[1],hsh[2],hsh[3],hsh[4],hsh[5],hsh[6],hsh[7]);
return digest;
}
int main() {
ChunkData processed = preProcess((char *)"abcdefg");
uint32_t* chunks = processed.chunks;
uint32_t numOfChunks = processed.numOfChunks;
static char* compressed = compress(chunks, numOfChunks);
cout << compressed << endl;
}
.ino code run on Arduino Nano
#include "SHA256.h"
SHA256 sha256;
void setup() {
Serial.begin(115200);
}
void loop() {
static char test[] = "abcdefg";
Serial.println(sha256.hash256(test));
delay(1000);
}
.cpp code run on Arduino Nano:
#include "SHA256.h"
#include "Arduino.h"
// Initialize hash values (first 32 bits of the fractional parts of the square roots of the first 8 primes)
uint32_t hsh[8] = {0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, 0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19};
// Initialize keys (first 32 bits of the fractional parts of the cube roots of the first 64 primes)
uint32_t k[64] = {
0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5,
0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174,
0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967,
0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85,
0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,
0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2
};
// UTILITY FUNCTIONS
uint32_t SHA256::rrot(uint32_t input, uint8_t shift) {
return (input >> shift) | (input << (32-shift));
}
// SHA256 FUNCTIONS
SHA256::ChunkData SHA256::preProcess(char* input) {
// Convert input to arrays containing 64 8bit ints each, all contained in a main array
uint32_t numOfArrays = ceil((float)(strlen(input)+9)/64); // padded 9 bytes for 128 and last 8 length ints
uint8_t mainArray [numOfArrays*64];
for (int i = 0; i < numOfArrays; i++) {
for (int j = 0; j < 64; j++) {
if ((i*64)+j < strlen(input)) {
mainArray[(i*64)+j] = input[(i*64)+j];
} else if ((i*64)+j == strlen(input)) {
mainArray[(i*64)+j] = 128;
} else {
mainArray[(i*64)+j] = 0;
}
}
}
// Set last 64 bits to big-endian integer representing length of input
static uint64_t inputBits = strlen(input) * 8;
for (int i = 0; i < 8; i++) {
mainArray[((numOfArrays-1)*64)+(63-i)] = (inputBits >> i * 8) & 255;
}
uint32_t chunks[(numOfArrays*64)+64];
for (int i = 0; i < numOfArrays; i++) {
for (int j = 0; j < 16; j++) {
uint32_t element = 0;
for (int l = 0; l < 4; l++) {
element = element + (mainArray[(i*64)+(j*4)+l] << ((3-l)*8));
}
chunks[(i*64)+j] = element;
}
for (int j = 16; j < 64; j++) {
uint32_t s0 = rrot(chunks[(i*64)+j-15], 7) ^ rrot(chunks[(i*64)+j-15], 18) ^ (chunks[(i*64)+j-15] >> 3);
uint32_t s1 = rrot(chunks[(i*64)+j-2], 17) ^ rrot(chunks[(i*64)+j-2], 19) ^ (chunks[(i*64)+j-2] >> 10);
chunks[(i*64)+j] = chunks[(i*64)+j-16] + s0 + chunks[(i*64)+j-7] + s1;
}
}
struct SHA256::ChunkData cd = {numOfArrays, chunks};
return cd;
}
char* SHA256::compress(uint32_t* chunks, uint32_t numOfChunks) {
for (int i = 0; i < numOfChunks; i++) {
uint32_t a=hsh[0], b=hsh[1], c=hsh[2], d=hsh[3], e=hsh[4], f=hsh[5], g=hsh[6], h=hsh[7];
for (int j = 0; j < 64; j++) {
uint32_t S1 = rrot(e, 6) ^ rrot(e, 11) ^ rrot(e, 25);
uint32_t ch = (e & f) ^ ((~e) & g);
uint32_t temp1 = h + S1 + ch + k[j] + chunks[(i*64)+j];
uint32_t S0 = rrot(a, 2) ^ rrot(a, 13) ^ rrot(a, 22);
uint32_t maj = (a & b) ^ (a & c) ^ (b & c);
uint32_t temp2 = S0 + maj;
h = g;
g = f;
f = e;
e = d + temp1;
d = c;
c = b;
b = a;
a = temp1 + temp2;
}
hsh[0] = hsh[0] + a;
hsh[1] = hsh[1] + b;
hsh[2] = hsh[2] + c;
hsh[3] = hsh[3] + d;
hsh[4] = hsh[4] + e;
hsh[5] = hsh[5] + f;
hsh[6] = hsh[6] + g;
hsh[7] = hsh[7] + h;
}
static char digest[65];
sprintf(digest,"%x%x%x%x%x%x%x%x",hsh[0],hsh[1],hsh[2],hsh[3],hsh[4],hsh[5],hsh[6],hsh[7]);
return digest;
}
char* SHA256::hash256(char* input) {
SHA256::ChunkData processed = preProcess((char *)"abcdefg");//input);
uint32_t* chunks = processed.chunks;
uint32_t numOfChunks = processed.numOfChunks;
static char* compressed = compress(chunks, numOfChunks);
return compressed;
}
.h code run on Arduino Nano:
#ifndef SHA256_h
#define SHA256_h
#include "Arduino.h"
class SHA256 {
public:
char* hash256(char* input);
char* testptr;
private:
struct ChunkData {
uint32_t numOfChunks;
uint32_t* chunks;
};
ChunkData preProcess(char* input);
char* compress(uint32_t chunks[], uint32_t numOfChunks);
uint32_t rrot(uint32_t input, uint8_t shift);
};
#endif
Here is a link to a diff check of the cpp files to show that only Arduino-porting related code was changed. Thank you to anyone who takes the time to reply to this question, and have a great day.