SHA-2 (Secure Hash Algorithm 2) is a set of cryptographic hash functions designed by the United States National Security Agency (NSA) and first published in 2001.
- Wikipedia
In the last few days, I decided to do something for no reason other than the challenge. I decided I wanted to implement SHA256 into DayZ Standalone using EnScript. At first, I imagined this wouldn't be much of a challenge, that it would be a great way for me to learn both SHA256 and more EnScript. However, there were a lot more moving parts than I anticipated. Sorry for being light on some of the bitwise operations, I really don't have much experience writing about them and don't feel like trying to describe how they work in this post.
To start, I recommend you do get some exposure to DayZ modding. I am going to jump through and assume you understand the basic structure and syntax of it.
Setting off, the first thing I wanted to find were some examples of SHA256 implementations in a few different languages. I found these two online that I found really easy to understand. The C# one can be found here. The C one can be found here.
From these examples, we learn three important things.
- SHA256 uses a lot of bitwise operations, notably Xor
- We need to use unsigned integers
- We need to use unsigned chars
The reason I say "notably Xor" is because, although DayZ does have bitwise operators, it is lacking the xor operator. So the first thing we'll have to do is create our own Xor operation. I was in luck, this is a problem that has already been solved. I copied the xor implementation from here.
Here is that Xor function in EnScript:
The goal of this function is to completely recreate a bitwise xor on the 32-bit integers supplied, flipping the bits as necessary. Thankfully, in DayZ, our bitwise operations work as expected, and so this function does exactly recreate xor.
On to item #2, unsigned integers. DayZ has absolutely nothing for handling unsigned integers. So we'll have to recreate the functionality ourselves. Looking at the source code for SHA256, we can see there are only a few unsigned integer operations that we need to consider. Addition, Greater Than, and Right Shift.
For Addition, I found a Stack Overflow post with the exact functionality I need.
For Greater Than, I found a Stack Overflow post and borrowed just a small snippet of the example provided.
For Right Shift, this method I came up with on my own. Let me try to describe this issue in a bit more detail. DayZ uses signed integers, with signed integers the last bit is a flag for the sign, a 1 bit represents a negative number. When right shifting signed integers, the sign is kept, meaning the last bit is always 1, and every shift to the right will shift more 1s. Here is a great diagram that helped me understand it.
In this image, the b7 bit is being duplicated and shifted. For unsigned integers, this right shift does not do this duplication step. Instead, a 0 is placed into the bit we shift away from, much like the left shift shown above.
In order to recreate an unsigned right shift in DayZ, we need to undo this duplication step. To do this, we shift right once, and set the left most bit, our sign bit, to 0. From this point on, any further shifts to the right will place a 0 into the left bit, matching the functionality of unsigned integers.
Here is that functionality in DayZ:
After we shift right once, we use & 0x7FFFFFFF to flip only the very last bit to 0, every other bit will retain its value. From this point on, we shift right the remaining amount and return our value.
Here is the complete unsigned integer implementation. Note that you do see the usage of the byte object, I'll explain that next. You'll also see some of the functionality for SHA256, these you can find in the C# and C examples as well.
Lastly, we have to cover unsigned chars, or bytes. This one was rather simple, all we need to ensure when working with bytes is that we clamp the 32-bit integer to just 8 bits. The functionality below achieves this.
Okay, now that the major problems with our data are resolved, we need to come up with functionality for our ASCII encoding. We need to convert ASCII to bytes, bytes back to ASCII, and even come up with a way to render the hex string so we can see that sweet hash text.
While DayZ does have a function called ToAscii, it doesn't work. Thankfully, the Hash method does exactly what we'd expect the other method to do. So if we take a character like "A" and run Hash() on it, we'll get the ASCII decimal value of that character. We can then use our byte class to convert that integer into a byte! So now that we can get a byte from a character, we can get a byte array by looping over a string. Now we have to come up with the reverse, we need to take a hex value, like 0xFF and actually convert that to a string "FF". This is not implemented in DayZ, but we can easily handle this with some division, and a modulus.
Check out the Encoding Implementation to see the functions in detail:
Thankfully, now we can start writing our SHA256 implementation! We have all the arithmetic and bit logic that we need, and all of the string encoding that'll be necessary to hash strings. To start, we need a SHA256 Context object. This will contain all of our data that we're actually manipulating to calculate the hash. I added a constructor here to pre-initialize our arrays, to size, filled with 0s.
Now for the meat of the algorithm. I am not going to write up how SHA256 works, you can find another post for that level of detail. I would like to point out, scrolling to the bottom you can see the static functions that I wrote to act as a user-friendly front end for this implementation.
The entire SHA256 implementation that can be dropped into DayZ without requiring any dependencies can be found on my hastebin.
Update: I slightly reworked my uint and byte implementations and updated the entire code base. You can find that on this hastebin. This update lets us extend directly from the int datatype, so we can now do functionality like byte b = 0xFF.