SHA1Hash
A custom utility class for calculating SHA1 hashes of strings and dynamic byte arrays. It cannot be used in UnrealEngine1 games due to their lack of dynamic arrays.
This implementation is specially optimized for UnrealScript and probably won't perform well when ported to Java, C/C++ or other languages. Important performance optimizations include the use of class-global variables and the lack of out parameters, because those are not "by reference" in UnrealScript!
Note that the bottle neck of this implementation – string to byte conversion – still is there and there's no real workaround for that yet. Passing large strings around takes some time, and they always need to be passed at least to the Mid() function.
Usage
For now the class can only be used statically:
local array<byte> SomeData; local string Result; // fill SomeData with byte values Result = class'SHA1Hash'.static.GetArrayHashString(SomeData);
local string SomeData; local string Result; // fill SomeData with a string Result = class'SHA1Hash'.static.GetStringHashString(SomeData);
A new version is being worked on that allows the programmer to create an instance of the class as "SHA1 context" for more performance-friendly handling of more complex values.
Structs
SHA1Result
A struct for holding the SHA 1 hash value.
- int A, B, C, D, E
- The five integers making up the 160 bits of the hash.
Methods
Static Methods
- SHA1Result GetArrayHash(array<byte> InData)
- Returns the SHA1 hash of the specified byte array as a SHA1Result.
- string GetArrayHashString(array<byte> InData)
- Returns the SHA1 hash of the specified byte array as a string.
- SHA1Result GetStringHash(string InData)
- Returns the SHA1 hash of the specified string as a SHA1Result.
- string GetStringHashString(string InData)
- Returns the SHA1 hash of the specified string as a string.
- string GetHashString(SHA1Result Hash)
- Returns the string representation of the SHA1 hash.
- string BigEndianToHex(int i)
- Returns the big endian string representation of the integer value.
- GetHashBytes(out array<byte> HashBytes, SHA1Result Hash)
- Converts the specified SHA1 hash to a byte array and appends that to HashBytes
Code
/****************************************************************************** SHA1 hash implementation for UnrealScript by Wormbo. Feel free to modify and optimize for your needs. ******************************************************************************/ class SHA1Hash extends Object; struct SHA1Result { var int A,B,C,D,E; }; /** @ignore */ var private SHA1Result StaticHashValue; /** @ignore */ var private array<byte> StaticData; //============================================================================= // Instant hash functions - probably not suitable for large input data //============================================================================= static final function SHA1Result GetStringHash(string In) { local int StrLen, i; StrLen = Len(In); default.StaticData.Length = StrLen; for (i = 0; i < StrLen; i++) { default.StaticData[i] = Asc(Mid(In, i, 1)); } StaticProcessChunks(); return default.StaticHashValue; } static final function string GetStringHashString(string In) { local int StrLen, i; StrLen = Len(In); default.StaticData.Length = StrLen; for (i = 0; i < StrLen; i++) { default.StaticData[i] = Asc(Mid(In, i, 1)); } StaticProcessChunks(); return BigEndianToHex(default.StaticHashValue.A) $ BigEndianToHex(default.StaticHashValue.B) $ BigEndianToHex(default.StaticHashValue.C) $ BigEndianToHex(default.StaticHashValue.D) $ BigEndianToHex(default.StaticHashValue.E); } static final function SHA1Result GetArrayHash(array<byte> In) { default.StaticData = In; StaticProcessChunks(); return default.StaticHashValue; } static final function string GetArrayHashString(array<byte> In) { default.StaticData = In; StaticProcessChunks(); return BigEndianToHex(default.StaticHashValue.A) $ BigEndianToHex(default.StaticHashValue.B) $ BigEndianToHex(default.StaticHashValue.C) $ BigEndianToHex(default.StaticHashValue.D) $ BigEndianToHex(default.StaticHashValue.E); } static final function string GetHashString(SHA1Result Hash) { return BigEndianToHex(Hash.A) $ BigEndianToHex(Hash.B) $ BigEndianToHex(Hash.C) $ BigEndianToHex(Hash.D) $ BigEndianToHex(Hash.E); } // Shambler: Limited use, this appends to rather than returning an array because I had special use for it static final function GetHashBytes(out array<byte> HashBytes, SHA1Result Hash) { local int i; i = HashBytes.Length; HashBytes.Length = HashBytes.Length + 20; HashBytes[i] = (Hash.A >> 24) & 0xff; HashBytes[i+1] = (Hash.A >> 16) & 0xff; HashBytes[i+2] = (Hash.A >> 8) & 0xff; HashBytes[i+3] = Hash.A & 0xff; HashBytes[i+4] = (Hash.B >> 24) & 0xff; HashBytes[i+5] = (Hash.B >> 16) & 0xff; HashBytes[i+6] = (Hash.B >> 8) & 0xff; HashBytes[i+7] = Hash.B & 0xff; HashBytes[i+8] = (Hash.C >> 24) & 0xff; HashBytes[i+9] = (Hash.C >> 16) & 0xff; HashBytes[i+10] = (Hash.C >> 8) & 0xff; HashBytes[i+11] = Hash.C & 0xff; HashBytes[i+12] = (Hash.D >> 24) & 0xff; HashBytes[i+13] = (Hash.D >> 16) & 0xff; HashBytes[i+14] = (Hash.D >> 8) & 0xff; HashBytes[i+15] = Hash.D & 0xff; HashBytes[i+16] = (Hash.E >> 24) & 0xff; HashBytes[i+17] = (Hash.E >> 16) & 0xff; HashBytes[i+18] = (Hash.E >> 8) & 0xff; HashBytes[i+19] = Hash.E & 0xff; } //============================================================================= // Internal stuff for static instant hashing functions //============================================================================= static final function string BigEndianToHex(int i) { const hex = "0123456789abcdef"; return Mid(hex, i >> 28 & 0xf, 1) $ Mid(hex, i >> 24 & 0xf, 1) $ Mid(hex, i >> 20 & 0xf, 1) $ Mid(hex, i >> 16 & 0xf, 1) $ Mid(hex, i >> 12 & 0xf, 1) $ Mid(hex, i >> 8 & 0xf, 1) $ Mid(hex, i >> 4 & 0xf, 1) $ Mid(hex, i & 0xf, 1); } private static final function StaticProcessChunks() { local int i, chunk, temp; local int A, B, C, D, E; local array<int> w; i = default.StaticData.Length; if (i % 64 < 56) default.StaticData.Length = default.StaticData.Length + 64 - i % 64; else default.StaticData.Length = default.StaticData.Length + 128 - i % 64; default.StaticData[i] = 0x80; default.StaticData[default.StaticData.Length - 5] = (i >>> 29); default.StaticData[default.StaticData.Length - 4] = (i >>> 21); default.StaticData[default.StaticData.Length - 3] = (i >>> 13); default.StaticData[default.StaticData.Length - 2] = (i >>> 5); default.StaticData[default.StaticData.Length - 1] = (i << 3); default.StaticHashValue.A = 0x67452301; default.StaticHashValue.B = 0xEFCDAB89; default.StaticHashValue.C = 0x98BADCFE; default.StaticHashValue.D = 0x10325476; default.StaticHashValue.E = 0xC3D2E1F0; while (chunk * 64 < default.StaticData.Length) { w.Length = 80; for (i = 0; i < 16; i++) { w[i] = (default.StaticData[chunk * 64 + i * 4] << 24) | (default.StaticData[chunk * 64 + i * 4 + 1] << 16) | (default.StaticData[chunk * 64 + i * 4 + 2] << 8) | default.StaticData[chunk * 64 + i * 4 + 3]; } for (i = 16; i < 80; i++) { temp = w[i - 3] ^ w[i - 8] ^ w[i - 14] ^ w[i - 16]; w[i] = (temp << 1) | (temp >>> 31); } // initialize hash value for this chunk A = default.StaticHashValue.A; B = default.StaticHashValue.B; C = default.StaticHashValue.C; D = default.StaticHashValue.D; E = default.StaticHashValue.E; // round 1 E += ((A << 5) | (A >>> -5)) + (D ^ (B & (C ^ D))) + w[ 0] + 0x5A827999; B = (B << 30) | (B >>> -30); D += ((E << 5) | (E >>> -5)) + (C ^ (A & (B ^ C))) + w[ 1] + 0x5A827999; A = (A << 30) | (A >>> -30); C += ((D << 5) | (D >>> -5)) + (B ^ (E & (A ^ B))) + w[ 2] + 0x5A827999; E = (E << 30) | (E >>> -30); B += ((C << 5) | (C >>> -5)) + (A ^ (D & (E ^ A))) + w[ 3] + 0x5A827999; D = (D << 30) | (D >>> -30); A += ((B << 5) | (B >>> -5)) + (E ^ (C & (D ^ E))) + w[ 4] + 0x5A827999; C = (C << 30) | (C >>> -30); E += ((A << 5) | (A >>> -5)) + (D ^ (B & (C ^ D))) + w[ 5] + 0x5A827999; B = (B << 30) | (B >>> -30); D += ((E << 5) | (E >>> -5)) + (C ^ (A & (B ^ C))) + w[ 6] + 0x5A827999; A = (A << 30) | (A >>> -30); C += ((D << 5) | (D >>> -5)) + (B ^ (E & (A ^ B))) + w[ 7] + 0x5A827999; E = (E << 30) | (E >>> -30); B += ((C << 5) | (C >>> -5)) + (A ^ (D & (E ^ A))) + w[ 8] + 0x5A827999; D = (D << 30) | (D >>> -30); A += ((B << 5) | (B >>> -5)) + (E ^ (C & (D ^ E))) + w[ 9] + 0x5A827999; C = (C << 30) | (C >>> -30); E += ((A << 5) | (A >>> -5)) + (D ^ (B & (C ^ D))) + w[10] + 0x5A827999; B = (B << 30) | (B >>> -30); D += ((E << 5) | (E >>> -5)) + (C ^ (A & (B ^ C))) + w[11] + 0x5A827999; A = (A << 30) | (A >>> -30); C += ((D << 5) | (D >>> -5)) + (B ^ (E & (A ^ B))) + w[12] + 0x5A827999; E = (E << 30) | (E >>> -30); B += ((C << 5) | (C >>> -5)) + (A ^ (D & (E ^ A))) + w[13] + 0x5A827999; D = (D << 30) | (D >>> -30); A += ((B << 5) | (B >>> -5)) + (E ^ (C & (D ^ E))) + w[14] + 0x5A827999; C = (C << 30) | (C >>> -30); E += ((A << 5) | (A >>> -5)) + (D ^ (B & (C ^ D))) + w[15] + 0x5A827999; B = (B << 30) | (B >>> -30); D += ((E << 5) | (E >>> -5)) + (C ^ (A & (B ^ C))) + w[16] + 0x5A827999; A = (A << 30) | (A >>> -30); C += ((D << 5) | (D >>> -5)) + (B ^ (E & (A ^ B))) + w[17] + 0x5A827999; E = (E << 30) | (E >>> -30); B += ((C << 5) | (C >>> -5)) + (A ^ (D & (E ^ A))) + w[18] + 0x5A827999; D = (D << 30) | (D >>> -30); A += ((B << 5) | (B >>> -5)) + (E ^ (C & (D ^ E))) + w[19] + 0x5A827999; C = (C << 30) | (C >>> -30); // round 2 E += ((A << 5) | (A >>> -5)) + (B ^ C ^ D) + w[20] + 0x6ED9EBA1; B = (B << 30) | (B >>> -30); D += ((E << 5) | (E >>> -5)) + (A ^ B ^ C) + w[21] + 0x6ED9EBA1; A = (A << 30) | (A >>> -30); C += ((D << 5) | (D >>> -5)) + (E ^ A ^ B) + w[22] + 0x6ED9EBA1; E = (E << 30) | (E >>> -30); B += ((C << 5) | (C >>> -5)) + (D ^ E ^ A) + w[23] + 0x6ED9EBA1; D = (D << 30) | (D >>> -30); A += ((B << 5) | (B >>> -5)) + (C ^ D ^ E) + w[24] + 0x6ED9EBA1; C = (C << 30) | (C >>> -30); E += ((A << 5) | (A >>> -5)) + (B ^ C ^ D) + w[25] + 0x6ED9EBA1; B = (B << 30) | (B >>> -30); D += ((E << 5) | (E >>> -5)) + (A ^ B ^ C) + w[26] + 0x6ED9EBA1; A = (A << 30) | (A >>> -30); C += ((D << 5) | (D >>> -5)) + (E ^ A ^ B) + w[27] + 0x6ED9EBA1; E = (E << 30) | (E >>> -30); B += ((C << 5) | (C >>> -5)) + (D ^ E ^ A) + w[28] + 0x6ED9EBA1; D = (D << 30) | (D >>> -30); A += ((B << 5) | (B >>> -5)) + (C ^ D ^ E) + w[29] + 0x6ED9EBA1; C = (C << 30) | (C >>> -30); E += ((A << 5) | (A >>> -5)) + (B ^ C ^ D) + w[30] + 0x6ED9EBA1; B = (B << 30) | (B >>> -30); D += ((E << 5) | (E >>> -5)) + (A ^ B ^ C) + w[31] + 0x6ED9EBA1; A = (A << 30) | (A >>> -30); C += ((D << 5) | (D >>> -5)) + (E ^ A ^ B) + w[32] + 0x6ED9EBA1; E = (E << 30) | (E >>> -30); B += ((C << 5) | (C >>> -5)) + (D ^ E ^ A) + w[33] + 0x6ED9EBA1; D = (D << 30) | (D >>> -30); A += ((B << 5) | (B >>> -5)) + (C ^ D ^ E) + w[34] + 0x6ED9EBA1; C = (C << 30) | (C >>> -30); E += ((A << 5) | (A >>> -5)) + (B ^ C ^ D) + w[35] + 0x6ED9EBA1; B = (B << 30) | (B >>> -30); D += ((E << 5) | (E >>> -5)) + (A ^ B ^ C) + w[36] + 0x6ED9EBA1; A = (A << 30) | (A >>> -30); C += ((D << 5) | (D >>> -5)) + (E ^ A ^ B) + w[37] + 0x6ED9EBA1; E = (E << 30) | (E >>> -30); B += ((C << 5) | (C >>> -5)) + (D ^ E ^ A) + w[38] + 0x6ED9EBA1; D = (D << 30) | (D >>> -30); A += ((B << 5) | (B >>> -5)) + (C ^ D ^ E) + w[39] + 0x6ED9EBA1; C = (C << 30) | (C >>> -30); // round 3 E += ((A << 5) | (A >>> -5)) + ((B & C) | (D & (B | C))) + w[40] + 0x8F1BBCDC; B = (B << 30) | (B >>> -30); D += ((E << 5) | (E >>> -5)) + ((A & B) | (C & (A | B))) + w[41] + 0x8F1BBCDC; A = (A << 30) | (A >>> -30); C += ((D << 5) | (D >>> -5)) + ((E & A) | (B & (E | A))) + w[42] + 0x8F1BBCDC; E = (E << 30) | (E >>> -30); B += ((C << 5) | (C >>> -5)) + ((D & E) | (A & (D | E))) + w[43] + 0x8F1BBCDC; D = (D << 30) | (D >>> -30); A += ((B << 5) | (B >>> -5)) + ((C & D) | (E & (C | D))) + w[44] + 0x8F1BBCDC; C = (C << 30) | (C >>> -30); E += ((A << 5) | (A >>> -5)) + ((B & C) | (D & (B | C))) + w[45] + 0x8F1BBCDC; B = (B << 30) | (B >>> -30); D += ((E << 5) | (E >>> -5)) + ((A & B) | (C & (A | B))) + w[46] + 0x8F1BBCDC; A = (A << 30) | (A >>> -30); C += ((D << 5) | (D >>> -5)) + ((E & A) | (B & (E | A))) + w[47] + 0x8F1BBCDC; E = (E << 30) | (E >>> -30); B += ((C << 5) | (C >>> -5)) + ((D & E) | (A & (D | E))) + w[48] + 0x8F1BBCDC; D = (D << 30) | (D >>> -30); A += ((B << 5) | (B >>> -5)) + ((C & D) | (E & (C | D))) + w[49] + 0x8F1BBCDC; C = (C << 30) | (C >>> -30); E += ((A << 5) | (A >>> -5)) + ((B & C) | (D & (B | C))) + w[50] + 0x8F1BBCDC; B = (B << 30) | (B >>> -30); D += ((E << 5) | (E >>> -5)) + ((A & B) | (C & (A | B))) + w[51] + 0x8F1BBCDC; A = (A << 30) | (A >>> -30); C += ((D << 5) | (D >>> -5)) + ((E & A) | (B & (E | A))) + w[52] + 0x8F1BBCDC; E = (E << 30) | (E >>> -30); B += ((C << 5) | (C >>> -5)) + ((D & E) | (A & (D | E))) + w[53] + 0x8F1BBCDC; D = (D << 30) | (D >>> -30); A += ((B << 5) | (B >>> -5)) + ((C & D) | (E & (C | D))) + w[54] + 0x8F1BBCDC; C = (C << 30) | (C >>> -30); E += ((A << 5) | (A >>> -5)) + ((B & C) | (D & (B | C))) + w[55] + 0x8F1BBCDC; B = (B << 30) | (B >>> -30); D += ((E << 5) | (E >>> -5)) + ((A & B) | (C & (A | B))) + w[56] + 0x8F1BBCDC; A = (A << 30) | (A >>> -30); C += ((D << 5) | (D >>> -5)) + ((E & A) | (B & (E | A))) + w[57] + 0x8F1BBCDC; E = (E << 30) | (E >>> -30); B += ((C << 5) | (C >>> -5)) + ((D & E) | (A & (D | E))) + w[58] + 0x8F1BBCDC; D = (D << 30) | (D >>> -30); A += ((B << 5) | (B >>> -5)) + ((C & D) | (E & (C | D))) + w[59] + 0x8F1BBCDC; C = (C << 30) | (C >>> -30); // round 4 E += ((A << 5) | (A >>> -5)) + (B ^ C ^ D) + w[60] + 0xCA62C1D6; B = (B << 30) | (B >>> -30); D += ((E << 5) | (E >>> -5)) + (A ^ B ^ C) + w[61] + 0xCA62C1D6; A = (A << 30) | (A >>> -30); C += ((D << 5) | (D >>> -5)) + (E ^ A ^ B) + w[62] + 0xCA62C1D6; E = (E << 30) | (E >>> -30); B += ((C << 5) | (C >>> -5)) + (D ^ E ^ A) + w[63] + 0xCA62C1D6; D = (D << 30) | (D >>> -30); A += ((B << 5) | (B >>> -5)) + (C ^ D ^ E) + w[64] + 0xCA62C1D6; C = (C << 30) | (C >>> -30); E += ((A << 5) | (A >>> -5)) + (B ^ C ^ D) + w[65] + 0xCA62C1D6; B = (B << 30) | (B >>> -30); D += ((E << 5) | (E >>> -5)) + (A ^ B ^ C) + w[66] + 0xCA62C1D6; A = (A << 30) | (A >>> -30); C += ((D << 5) | (D >>> -5)) + (E ^ A ^ B) + w[67] + 0xCA62C1D6; E = (E << 30) | (E >>> -30); B += ((C << 5) | (C >>> -5)) + (D ^ E ^ A) + w[68] + 0xCA62C1D6; D = (D << 30) | (D >>> -30); A += ((B << 5) | (B >>> -5)) + (C ^ D ^ E) + w[69] + 0xCA62C1D6; C = (C << 30) | (C >>> -30); E += ((A << 5) | (A >>> -5)) + (B ^ C ^ D) + w[70] + 0xCA62C1D6; B = (B << 30) | (B >>> -30); D += ((E << 5) | (E >>> -5)) + (A ^ B ^ C) + w[71] + 0xCA62C1D6; A = (A << 30) | (A >>> -30); C += ((D << 5) | (D >>> -5)) + (E ^ A ^ B) + w[72] + 0xCA62C1D6; E = (E << 30) | (E >>> -30); B += ((C << 5) | (C >>> -5)) + (D ^ E ^ A) + w[73] + 0xCA62C1D6; D = (D << 30) | (D >>> -30); A += ((B << 5) | (B >>> -5)) + (C ^ D ^ E) + w[74] + 0xCA62C1D6; C = (C << 30) | (C >>> -30); E += ((A << 5) | (A >>> -5)) + (B ^ C ^ D) + w[75] + 0xCA62C1D6; B = (B << 30) | (B >>> -30); D += ((E << 5) | (E >>> -5)) + (A ^ B ^ C) + w[76] + 0xCA62C1D6; A = (A << 30) | (A >>> -30); C += ((D << 5) | (D >>> -5)) + (E ^ A ^ B) + w[77] + 0xCA62C1D6; E = (E << 30) | (E >>> -30); B += ((C << 5) | (C >>> -5)) + (D ^ E ^ A) + w[78] + 0xCA62C1D6; D = (D << 30) | (D >>> -30); A += ((B << 5) | (B >>> -5)) + (C ^ D ^ E) + w[79] + 0xCA62C1D6; C = (C << 30) | (C >>> -30); // add this chunk's hash to result so far default.StaticHashValue.A += A; default.StaticHashValue.B += B; default.StaticHashValue.C += C; default.StaticHashValue.D += D; default.StaticHashValue.E += E; chunk++; } }
Implementation Notes
Only the 5 least significant bits of the second operand of UnrealScript's bitshift operators are used. That means, for example (C >>> -30)
is the same as (C >>> 2)
.
The implementation avoids unneccessary circular variable assignments the original SHA1 implementation in RFC 3174 and on Wikipedia suggest. While Wikipedia's very efficient notation of the main loop (see below) is very small, the high number of assignments make it quite slow in UnrealScript.
for i from 0 to 79 if 0 ≤ i ≤ 19 then f := (b and c) or ((not b) and d) k := 0x5A827999 else if 20 ≤ i ≤ 39 f := b xor c xor d k := 0x6ED9EBA1 else if 40 ≤ i ≤ 59 f := (b and c) or (b and d) or (c and d) k := 0x8F1BBCDC else if 60 ≤ i ≤ 79 f := b xor c xor d k := 0xCA62C1D6 temp := (a leftrotate 5) + f + e + k + w(i) e := d d := c c := b leftrotate 30 b := a a := temp
Another difference from the original implementation suggestions are the calculations on the first and third round. This algorithm implements the optimizations suggested by Wikipedia: d xor (b and (c xor d))
instead of (b and c) or ((not b) and d)
for the first round and (b and c) or (d and (b or c))
instead of (b and c) or (b and d) or (c and d)
for the third round. This results in a lower number of operations without changing the result.
Related Topics
- MD5Hash – a similar class for calculating MD5 hashes
- SHA1 – Wikipedia article about SHA hash functions
Discussion
Jimboh: I was just wondering why you are using 'default' static variables instead of a normal class variable? Is it faster to access default/static data or are you just trying to make it somewhat of a singleton class?
El Muerte: the functions are static thus you can only access default properties. In this case it's safe to use because there are no multiple processes that can access the same data at the same time. If the functions were not static you would need to create an instance first, which isn't the nicest way for utility functions.
Wormbo: I don't think unqualified default variables are accessed faster than unqualified instance variables or local variables. (With "unqualified" I mean "not prefixed with class'ThisClass'.
or Class.
for the default variable or Self.
for the instance variable.)
Switch`: Profiling shows there's a theoretical 10% difference in speed between slowest and fastest access method.
class Sandbox extends Commandlet; var vector vc, t; // call 1000000 times final function profilefunc() { t = ... } // results t = class.default.vc; // 428ms t = class'Sandbox'.default.vc; // 415ms t = default.vc; // 385ms t = vc; // 380ms t = self.vc; // randomly 406ms - 424ms...
Jimboh: Here is my instanced version of Wormbo's SHA1 implementation. It can be reused as long as Init() is called before each fresh use.
/******************************************************** * This SHA1 implementation is suitable for large files * * and can be read incrementally in aritrary chunks. * * Adapted by Jimboh from Wormbo's * * SHA1 Hash Implementation for UnrealScript. * ********************************************************/ class SHA1Hash extends Commandlet; var private int h0, h1, h2, h3, h4; var private int byteCount; var private int block; var private bool digested; var private int w[80]; final function Init() { h0 = 0x67452301; h1 = 0xEFCDAB89; h2 = 0x98BADCFE; h3 = 0x10325476; h4 = 0xC3D2E1F0; block = 0; byteCount = 0; digested = false; } final function Update(array<byte> data) { local int byteNum; local int len, rem; // Remaining bytes local int i, temp; local int A, B, C, D, E; len = data.Length; rem = len; while (rem > 0) { byteNum = byteCount % 4; if (rem >= 4 && byteNum == 0) { w[block] = (data[len - rem] << 24) | (data[len - rem + 1] << 16) | (data[len - rem + 2] << 8) | (data[len - rem + 3]); rem -= 4; byteCount += 4; block++; } else { if (byteNum == 0) w[block] = 0; w[block] = w[block] | (data[len - rem] << ((3 - byteNum) * 8)); rem--; byteCount++; if (byteNum == 3) block++; } if (block == 16) { // ****** START TRANSFORM ****** for (i = 16; i < 80; i++) { temp = w[i - 3] ^ w[i - 8] ^ w[i - 14] ^ w[i - 16]; w[i] = (temp << 1) | (temp >>> 31); } A = h0; B = h1; C = h2; D = h3; E = h4; // round 1 E += ((A << 5) | (A >>> -5)) + (D ^ (B & (C ^ D))) + w[ 0] + 0x5A827999; B = (B << 30) | (B >>> -30); D += ((E << 5) | (E >>> -5)) + (C ^ (A & (B ^ C))) + w[ 1] + 0x5A827999; A = (A << 30) | (A >>> -30); C += ((D << 5) | (D >>> -5)) + (B ^ (E & (A ^ B))) + w[ 2] + 0x5A827999; E = (E << 30) | (E >>> -30); B += ((C << 5) | (C >>> -5)) + (A ^ (D & (E ^ A))) + w[ 3] + 0x5A827999; D = (D << 30) | (D >>> -30); A += ((B << 5) | (B >>> -5)) + (E ^ (C & (D ^ E))) + w[ 4] + 0x5A827999; C = (C << 30) | (C >>> -30); E += ((A << 5) | (A >>> -5)) + (D ^ (B & (C ^ D))) + w[ 5] + 0x5A827999; B = (B << 30) | (B >>> -30); D += ((E << 5) | (E >>> -5)) + (C ^ (A & (B ^ C))) + w[ 6] + 0x5A827999; A = (A << 30) | (A >>> -30); C += ((D << 5) | (D >>> -5)) + (B ^ (E & (A ^ B))) + w[ 7] + 0x5A827999; E = (E << 30) | (E >>> -30); B += ((C << 5) | (C >>> -5)) + (A ^ (D & (E ^ A))) + w[ 8] + 0x5A827999; D = (D << 30) | (D >>> -30); A += ((B << 5) | (B >>> -5)) + (E ^ (C & (D ^ E))) + w[ 9] + 0x5A827999; C = (C << 30) | (C >>> -30); E += ((A << 5) | (A >>> -5)) + (D ^ (B & (C ^ D))) + w[10] + 0x5A827999; B = (B << 30) | (B >>> -30); D += ((E << 5) | (E >>> -5)) + (C ^ (A & (B ^ C))) + w[11] + 0x5A827999; A = (A << 30) | (A >>> -30); C += ((D << 5) | (D >>> -5)) + (B ^ (E & (A ^ B))) + w[12] + 0x5A827999; E = (E << 30) | (E >>> -30); B += ((C << 5) | (C >>> -5)) + (A ^ (D & (E ^ A))) + w[13] + 0x5A827999; D = (D << 30) | (D >>> -30); A += ((B << 5) | (B >>> -5)) + (E ^ (C & (D ^ E))) + w[14] + 0x5A827999; C = (C << 30) | (C >>> -30); E += ((A << 5) | (A >>> -5)) + (D ^ (B & (C ^ D))) + w[15] + 0x5A827999; B = (B << 30) | (B >>> -30); D += ((E << 5) | (E >>> -5)) + (C ^ (A & (B ^ C))) + w[16] + 0x5A827999; A = (A << 30) | (A >>> -30); C += ((D << 5) | (D >>> -5)) + (B ^ (E & (A ^ B))) + w[17] + 0x5A827999; E = (E << 30) | (E >>> -30); B += ((C << 5) | (C >>> -5)) + (A ^ (D & (E ^ A))) + w[18] + 0x5A827999; D = (D << 30) | (D >>> -30); A += ((B << 5) | (B >>> -5)) + (E ^ (C & (D ^ E))) + w[19] + 0x5A827999; C = (C << 30) | (C >>> -30); // round 2 E += ((A << 5) | (A >>> -5)) + (B ^ C ^ D) + w[20] + 0x6ED9EBA1; B = (B << 30) | (B >>> -30); D += ((E << 5) | (E >>> -5)) + (A ^ B ^ C) + w[21] + 0x6ED9EBA1; A = (A << 30) | (A >>> -30); C += ((D << 5) | (D >>> -5)) + (E ^ A ^ B) + w[22] + 0x6ED9EBA1; E = (E << 30) | (E >>> -30); B += ((C << 5) | (C >>> -5)) + (D ^ E ^ A) + w[23] + 0x6ED9EBA1; D = (D << 30) | (D >>> -30); A += ((B << 5) | (B >>> -5)) + (C ^ D ^ E) + w[24] + 0x6ED9EBA1; C = (C << 30) | (C >>> -30); E += ((A << 5) | (A >>> -5)) + (B ^ C ^ D) + w[25] + 0x6ED9EBA1; B = (B << 30) | (B >>> -30); D += ((E << 5) | (E >>> -5)) + (A ^ B ^ C) + w[26] + 0x6ED9EBA1; A = (A << 30) | (A >>> -30); C += ((D << 5) | (D >>> -5)) + (E ^ A ^ B) + w[27] + 0x6ED9EBA1; E = (E << 30) | (E >>> -30); B += ((C << 5) | (C >>> -5)) + (D ^ E ^ A) + w[28] + 0x6ED9EBA1; D = (D << 30) | (D >>> -30); A += ((B << 5) | (B >>> -5)) + (C ^ D ^ E) + w[29] + 0x6ED9EBA1; C = (C << 30) | (C >>> -30); E += ((A << 5) | (A >>> -5)) + (B ^ C ^ D) + w[30] + 0x6ED9EBA1; B = (B << 30) | (B >>> -30); D += ((E << 5) | (E >>> -5)) + (A ^ B ^ C) + w[31] + 0x6ED9EBA1; A = (A << 30) | (A >>> -30); C += ((D << 5) | (D >>> -5)) + (E ^ A ^ B) + w[32] + 0x6ED9EBA1; E = (E << 30) | (E >>> -30); B += ((C << 5) | (C >>> -5)) + (D ^ E ^ A) + w[33] + 0x6ED9EBA1; D = (D << 30) | (D >>> -30); A += ((B << 5) | (B >>> -5)) + (C ^ D ^ E) + w[34] + 0x6ED9EBA1; C = (C << 30) | (C >>> -30); E += ((A << 5) | (A >>> -5)) + (B ^ C ^ D) + w[35] + 0x6ED9EBA1; B = (B << 30) | (B >>> -30); D += ((E << 5) | (E >>> -5)) + (A ^ B ^ C) + w[36] + 0x6ED9EBA1; A = (A << 30) | (A >>> -30); C += ((D << 5) | (D >>> -5)) + (E ^ A ^ B) + w[37] + 0x6ED9EBA1; E = (E << 30) | (E >>> -30); B += ((C << 5) | (C >>> -5)) + (D ^ E ^ A) + w[38] + 0x6ED9EBA1; D = (D << 30) | (D >>> -30); A += ((B << 5) | (B >>> -5)) + (C ^ D ^ E) + w[39] + 0x6ED9EBA1; C = (C << 30) | (C >>> -30); // round 3 E += ((A << 5) | (A >>> -5)) + ((B & C) | (D & (B | C))) + w[40] + 0x8F1BBCDC; B = (B << 30) | (B >>> -30); D += ((E << 5) | (E >>> -5)) + ((A & B) | (C & (A | B))) + w[41] + 0x8F1BBCDC; A = (A << 30) | (A >>> -30); C += ((D << 5) | (D >>> -5)) + ((E & A) | (B & (E | A))) + w[42] + 0x8F1BBCDC; E = (E << 30) | (E >>> -30); B += ((C << 5) | (C >>> -5)) + ((D & E) | (A & (D | E))) + w[43] + 0x8F1BBCDC; D = (D << 30) | (D >>> -30); A += ((B << 5) | (B >>> -5)) + ((C & D) | (E & (C | D))) + w[44] + 0x8F1BBCDC; C = (C << 30) | (C >>> -30); E += ((A << 5) | (A >>> -5)) + ((B & C) | (D & (B | C))) + w[45] + 0x8F1BBCDC; B = (B << 30) | (B >>> -30); D += ((E << 5) | (E >>> -5)) + ((A & B) | (C & (A | B))) + w[46] + 0x8F1BBCDC; A = (A << 30) | (A >>> -30); C += ((D << 5) | (D >>> -5)) + ((E & A) | (B & (E | A))) + w[47] + 0x8F1BBCDC; E = (E << 30) | (E >>> -30); B += ((C << 5) | (C >>> -5)) + ((D & E) | (A & (D | E))) + w[48] + 0x8F1BBCDC; D = (D << 30) | (D >>> -30); A += ((B << 5) | (B >>> -5)) + ((C & D) | (E & (C | D))) + w[49] + 0x8F1BBCDC; C = (C << 30) | (C >>> -30); E += ((A << 5) | (A >>> -5)) + ((B & C) | (D & (B | C))) + w[50] + 0x8F1BBCDC; B = (B << 30) | (B >>> -30); D += ((E << 5) | (E >>> -5)) + ((A & B) | (C & (A | B))) + w[51] + 0x8F1BBCDC; A = (A << 30) | (A >>> -30); C += ((D << 5) | (D >>> -5)) + ((E & A) | (B & (E | A))) + w[52] + 0x8F1BBCDC; E = (E << 30) | (E >>> -30); B += ((C << 5) | (C >>> -5)) + ((D & E) | (A & (D | E))) + w[53] + 0x8F1BBCDC; D = (D << 30) | (D >>> -30); A += ((B << 5) | (B >>> -5)) + ((C & D) | (E & (C | D))) + w[54] + 0x8F1BBCDC; C = (C << 30) | (C >>> -30); E += ((A << 5) | (A >>> -5)) + ((B & C) | (D & (B | C))) + w[55] + 0x8F1BBCDC; B = (B << 30) | (B >>> -30); D += ((E << 5) | (E >>> -5)) + ((A & B) | (C & (A | B))) + w[56] + 0x8F1BBCDC; A = (A << 30) | (A >>> -30); C += ((D << 5) | (D >>> -5)) + ((E & A) | (B & (E | A))) + w[57] + 0x8F1BBCDC; E = (E << 30) | (E >>> -30); B += ((C << 5) | (C >>> -5)) + ((D & E) | (A & (D | E))) + w[58] + 0x8F1BBCDC; D = (D << 30) | (D >>> -30); A += ((B << 5) | (B >>> -5)) + ((C & D) | (E & (C | D))) + w[59] + 0x8F1BBCDC; C = (C << 30) | (C >>> -30); // round 4 E += ((A << 5) | (A >>> -5)) + (B ^ C ^ D) + w[60] + 0xCA62C1D6; B = (B << 30) | (B >>> -30); D += ((E << 5) | (E >>> -5)) + (A ^ B ^ C) + w[61] + 0xCA62C1D6; A = (A << 30) | (A >>> -30); C += ((D << 5) | (D >>> -5)) + (E ^ A ^ B) + w[62] + 0xCA62C1D6; E = (E << 30) | (E >>> -30); B += ((C << 5) | (C >>> -5)) + (D ^ E ^ A) + w[63] + 0xCA62C1D6; D = (D << 30) | (D >>> -30); A += ((B << 5) | (B >>> -5)) + (C ^ D ^ E) + w[64] + 0xCA62C1D6; C = (C << 30) | (C >>> -30); E += ((A << 5) | (A >>> -5)) + (B ^ C ^ D) + w[65] + 0xCA62C1D6; B = (B << 30) | (B >>> -30); D += ((E << 5) | (E >>> -5)) + (A ^ B ^ C) + w[66] + 0xCA62C1D6; A = (A << 30) | (A >>> -30); C += ((D << 5) | (D >>> -5)) + (E ^ A ^ B) + w[67] + 0xCA62C1D6; E = (E << 30) | (E >>> -30); B += ((C << 5) | (C >>> -5)) + (D ^ E ^ A) + w[68] + 0xCA62C1D6; D = (D << 30) | (D >>> -30); A += ((B << 5) | (B >>> -5)) + (C ^ D ^ E) + w[69] + 0xCA62C1D6; C = (C << 30) | (C >>> -30); E += ((A << 5) | (A >>> -5)) + (B ^ C ^ D) + w[70] + 0xCA62C1D6; B = (B << 30) | (B >>> -30); D += ((E << 5) | (E >>> -5)) + (A ^ B ^ C) + w[71] + 0xCA62C1D6; A = (A << 30) | (A >>> -30); C += ((D << 5) | (D >>> -5)) + (E ^ A ^ B) + w[72] + 0xCA62C1D6; E = (E << 30) | (E >>> -30); B += ((C << 5) | (C >>> -5)) + (D ^ E ^ A) + w[73] + 0xCA62C1D6; D = (D << 30) | (D >>> -30); A += ((B << 5) | (B >>> -5)) + (C ^ D ^ E) + w[74] + 0xCA62C1D6; C = (C << 30) | (C >>> -30); E += ((A << 5) | (A >>> -5)) + (B ^ C ^ D) + w[75] + 0xCA62C1D6; B = (B << 30) | (B >>> -30); D += ((E << 5) | (E >>> -5)) + (A ^ B ^ C) + w[76] + 0xCA62C1D6; A = (A << 30) | (A >>> -30); C += ((D << 5) | (D >>> -5)) + (E ^ A ^ B) + w[77] + 0xCA62C1D6; E = (E << 30) | (E >>> -30); B += ((C << 5) | (C >>> -5)) + (D ^ E ^ A) + w[78] + 0xCA62C1D6; D = (D << 30) | (D >>> -30); A += ((B << 5) | (B >>> -5)) + (C ^ D ^ E) + w[79] + 0xCA62C1D6; C = (C << 30) | (C >>> -30); h0 += A; h1 += B; h2 += C; h3 += D; h4 += E; // ******* END TRANSFORM ******* block = 0; } } } final function Finish() { local array<byte> pad; if (byteCount % 64 < 56) pad.Length = 64 - byteCount % 64; else pad.Length = 128 - byteCount % 64; pad[0] = 0x80; pad[pad.Length - 5] = (byteCount >>> 29); pad[pad.Length - 4] = (byteCount >>> 21); pad[pad.Length - 3] = (byteCount >>> 13); pad[pad.Length - 2] = (byteCount >>> 5); pad[pad.Length - 1] = (byteCount << 3); Update(pad); // Just a safeguard - the previous Update() should trigger a transform. if (block == 0) digested = true; } // Just an auxillary function to get the digested hash. // Any coder worth their salt should know how to merge with Final() to improve efficiency... final function String GetHash() { if (!digested) return ""; return IntToHex(h0)@IntToHex(h1)@IntToHex(h2)@IntToHex(h3)@IntToHex(h4); } static final function string IntToHex(int n) { const hex = "0123456789ABCDEF"; local string s; local int i; for (i = 0; i < 8; i++) { s = mid(hex, n & 0xf, 1) $ s; n = n >>> 4; } return s; } function int Main(string Params) { local int i; local array<byte> b; b.Length = 3; log("SHA-1 Test Program"); log("test: 'abc'"); b[0] = 0x61; b[1] = 0x62; b[2] = 0x63; Init(); Update(b); Finish(); log(""$GetHash()); b.Length = 1000000; log("SHA-1 Test Program"); log("test: 1 million rounds of 'a'"); for (i = 0; i < 1000000; i++) b[i] = 0x61; log ("done filling"); Init(); Update(b); Finish(); log(""$GetHash()); }
I made it into a commandlet so all you have to do is compile-n-go to test it out.
Unfourtunately, the real bottleneck is the transformation itself, so there aren't any real measurable performance gains from having a contextd version. If anyone finds anything that can be optimized, please point it out and we'll see if this code can go any faster. For now, if you're using this for anything larger than 1MB for real time applications I'd recommend you use a reduced rounds implementation (which would also reduce the secureness of the resulting hash).