I am in the quite fortunate position to say that my code for a simple SHA1 Hash generator seems to work well. Unfortunately I know that this Arduino Program runs with Little Endianness and the description on the method to generate a hash requires the original message length to be appended as Big Endian integer.
This means for the message char m[] = "Applecake"
I would have 9*8 bits, expressed as a 64-bit unsigned integer that is 0x0000 0000 0000 0048
. That means, stored with Little Endian, the memory would look like this: 0x0048 0000 0000 0000
.
As described in Section 4 of RFC 3174 Step c) I have to
Obtain the 2-word representation of l, the number of bits in the original message. If l < 2^32 then the first word is all zeroes. Append these two words to the padded message.
So with my memory as described above, I would have to convert it to Big Endian first and then append the lower 32 bits to the padded message.
The problem is, that if I do convert the Endianness of the length, which I know is Little Endian, I get the wrong padding and therefore the wrong hash.
Why is my code working without conversion of the Endianness?
Which limitations does my code have concerning the compatibility across different Arduinos, microcontrollers and compilers?
// initialize variables
h0 = 0x67452301;
h1 = 0xEFCDAB89;
h2 = 0x98BADCFE;
h3 = 0x10325476;
h4 = 0xC3D2E1F0;
// calculate the number of required cycles and create a blocks array
uint32_t numCycles = ((ml+65)/512)+1;
uint32_t blocks[numCycles*16] = {};
// copy message
uint32_t messageBytes = ml/8 + (ml%8!=0 ? 1 : 0);
for (uint32_t i = 0; i < messageBytes; i++) {
blocks[i/4] |= ((uint32_t) message[i]) << (8*(3-(i%4)));
}
// append the 1 bit
blocks[ml/32] |= ((uint32_t) 0b1) << (31-(ml%32));
// append the 64-bit big endian ml at the end
if (ml < 0x80000000)
blocks[(numCycles*16)-1] = (uint32_t) ml;
else {
blocks[(numCycles*16)-2] = (uint32_t) ml;
blocks[(numCycles*16)-1] = (uint32_t) (ml >> 32);
}
for (uint32_t iCycle = 0; iCycle < numCycles; iCycle++) {
// initalize locals
uint32_t w[80] = {};
uint32_t a = h0, b = h1, c = h2, d = h3, e = h4;
for (uint8_t i = 0; i < 80; i++) {
// convert words to big-endian and copy to 80-elem array
if (i < 16)
w[i] = blocks[(iCycle*16)+i];
else
w[i] = rotL((w[i-3]^w[i-8]^w[i-14]^w[i-16]), 1);
// run defined formulas
uint32_t f, k, temp;
if (i < 20) {
f = (b & c) | ((~b) & d);
k = 0x5A827999;
}
else if (i < 40) {
f = b ^ c ^ d;
k = 0x6ED9EBA1;
}
else if (i < 60) {
f = (b & c) | (b & d) | (c & d);
k = 0x8F1BBCDC;
}
else {
f = b ^ c ^ d;
k = 0xCA62C1D6;
}
temp = rotL(a, 5) + f + e + k + w[i];
e = d; d = c; c = rotL(b, 30); b = a; a = temp;
}
// write back the results
h0 += a; h1 += b; h2 += c; h3 += d; h4 += e;
}
// append the 64-bit big endian ml at the end
if (ml < 0x80000000)
blocks[(numCycles*16)-1] = (uint32_t) ml;
else {
blocks[(numCycles*16)-2] = (uint32_t) ml;
blocks[(numCycles*16)-1] = (uint32_t) (ml >> 32);
}
This puts the most-significant 32-bit value first and the least-significant 32-bit value second. That's half the reason your code works.
The other half is that while the 32-bit values are in little-endian form, you are reading their values on a little-endian platform. That will always give you the correct value. You never try to access the individual bytes of the 32-bit values, so which bytes goes where makes no difference.
User contributions licensed under CC BY-SA 3.0