Created
June 30, 2024 12:53
-
-
Save ddjerqq/c292a6d45b4e803d4b49aa0aaf458c72 to your computer and use it in GitHub Desktop.
Implementation of sha256 in C#, with unsafe code blocks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System.Diagnostics; | |
using System.Text; | |
unsafe | |
{ | |
Sha256 sha256 = new Sha256(); | |
var payload = "aaa"; | |
byte[] data = Encoding.UTF8.GetBytes(payload); | |
fixed (byte* p = data) | |
sha256.Update(p, payload.Length); | |
var output = sha256.Digest(); | |
// convert to array with 64 len | |
byte[] hash = new byte[32]; | |
for (int i = 0; i < 32; i++) | |
{ | |
hash[i] = output[i]; | |
} | |
var hexDigest = BitConverter.ToString(hash).Replace("-", ""); | |
Console.WriteLine(hexDigest); | |
Debug.Assert(hexDigest == "9834876DCFB05CB167A5C24953EBA58C4AC89B1ADF57F28F2F9D09AF107EE8F0"); | |
} | |
public unsafe struct Sha256 | |
{ | |
private static readonly uint[] K = | |
[ | |
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, | |
]; | |
private fixed byte _data[64]; | |
private fixed uint _state[8]; | |
private uint _blocklen = 0; | |
private ulong _bitlen = 0; | |
public Sha256() | |
{ | |
_state[0] = 0x6a09e667; | |
_state[1] = 0xbb67ae85; | |
_state[2] = 0x3c6ef372; | |
_state[3] = 0xa54ff53a; | |
_state[4] = 0x510e527f; | |
_state[5] = 0x9b05688c; | |
_state[6] = 0x1f83d9ab; | |
_state[7] = 0x5be0cd19; | |
} | |
public void Update(byte* data, long size) | |
{ | |
for (long i = 0; i < size; i++) | |
{ | |
_data[_blocklen++] = data[i]; | |
if (_blocklen == 64) | |
{ | |
Transform(); | |
// end of the block | |
_bitlen += 512; | |
_blocklen = 0; | |
} | |
} | |
} | |
public byte* Digest() | |
{ | |
byte* hash = stackalloc byte[32]; | |
Pad(); | |
Revert(hash); | |
return hash; | |
} | |
private void Pad() | |
{ | |
ulong i = _blocklen; | |
byte end = _blocklen < 56 ? (byte)56 : (byte)64; | |
_data[i++] = 0x80; // Append a bit 1 | |
while (i < end) | |
{ | |
_data[i++] = 0x00; // Pad with zeros | |
} | |
if (_blocklen >= 56) | |
{ | |
Transform(); | |
for (var j = 0; j < 56; j++) | |
_data[j] = 0; | |
} | |
// Append to the padding the total message's length in bits and transform. | |
_bitlen += _blocklen * 8; | |
_data[63] = (byte)(_bitlen >> 0); | |
_data[62] = (byte)(_bitlen >> 8); | |
_data[61] = (byte)(_bitlen >> 16); | |
_data[60] = (byte)(_bitlen >> 24); | |
_data[59] = (byte)(_bitlen >> 32); | |
_data[58] = (byte)(_bitlen >> 40); | |
_data[57] = (byte)(_bitlen >> 48); | |
_data[56] = (byte)(_bitlen >> 56); | |
Transform(); | |
} | |
private void Transform() | |
{ | |
uint maj, xorA, ch, xorE, sum, newA, newE; | |
uint[] m = new uint[64]; | |
uint* state = stackalloc uint[8]; | |
// Split data in 32 bit blocks for the 16 first words | |
for (byte i = 0, j = 0; i < 16; i++, j += 4) | |
m[i] = (uint)((_data[j] << 24) | (_data[j + 1] << 16) | (_data[j + 2] << 8) | (_data[j + 3])); | |
// Remaining 48 blocks | |
for (byte k = 16; k < 64; k++) | |
m[k] = Sig1(m[k - 2]) + m[k - 7] + Sig0(m[k - 15]) + m[k - 16]; | |
for (byte i = 0; i < 8; i++) | |
state[i] = _state[i]; | |
for (byte i = 0; i < 64; i++) | |
{ | |
maj = Majority(state[0], state[1], state[2]); | |
xorA = Rotr(state[0], 2) ^ Rotr(state[0], 13) ^ Rotr(state[0], 22); | |
ch = Choose(state[4], state[5], state[6]); | |
xorE = Rotr(state[4], 6) ^ Rotr(state[4], 11) ^ Rotr(state[4], 25); | |
sum = m[i] + K[i] + state[7] + ch + xorE; | |
newA = xorA + maj + sum; | |
newE = state[3] + sum; | |
state[7] = state[6]; | |
state[6] = state[5]; | |
state[5] = state[4]; | |
state[4] = newE; | |
state[3] = state[2]; | |
state[2] = state[1]; | |
state[1] = state[0]; | |
state[0] = newA; | |
} | |
for (byte i = 0; i < 8; i++) | |
{ | |
_state[i] += state[i]; | |
} | |
} | |
private void Revert(byte* hash) | |
{ | |
// SHA uses big endian byte ordering | |
// Revert all bytes | |
for (byte i = 0; i < 4; i++) | |
{ | |
for (byte j = 0; j < 8; j++) | |
{ | |
hash[i + j * 4] = (byte)((_state[j] >> (24 - i * 8)) & 0x000000ff); | |
} | |
} | |
} | |
private static uint Rotr(uint x, int n) => (x >> n) | (x << (32 - n)); | |
private static uint Choose(uint e, uint f, uint g) => (e & f) ^ (~e & g); | |
private static uint Majority(uint a, uint b, uint c) => (a & (b | c)) | (b & c); | |
private static uint Sig0(uint x) => Rotr(x, 7) ^ Rotr(x, 18) ^ (x >> 3); | |
private static uint Sig1(uint x) => Rotr(x, 17) ^ Rotr(x, 19) ^ (x >> 10); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment