Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save silbinarywolf/8816b87a72531e1c755d60f179e89bde to your computer and use it in GitHub Desktop.
Save silbinarywolf/8816b87a72531e1c755d60f179e89bde to your computer and use it in GitHub Desktop.
hmac_md5
/// @function hmac_md5
/// @param {buffer} buffer The buffer to get a hash of
/// @param {number} offset
/// @param {number} size
/// @param {string} key Shared secret key used for generating the HMAC variant of the message digest.
/// @description Create a hash-based message authentication code using a provided key
/// @source https://en.wikipedia.org/wiki/HMAC
var buffer = argument0
var buffer_offset = argument1
var buffer_size = argument2
var key = argument3
var key_size = string_byte_length(key)
var hash_block_size = 64 // MD5 and SHA1 are 512 bits, ie. 64 bytes
if key_size > hash_block_size {
// Other language equivalents:
// - PHP - md5('Test_Japanese_昨夜のコ') == ff772e31410ae7f31b93c2109f496006
// - GML - md5_string_utf8("Test_Japanese_昨夜のコ") == ff772e31410ae7f31b93c2109f496006
key = md5_string_utf8(key)
key_size = string_byte_length(key)
}
// NOTE(Jake): 2018-03-17
//
// Implementation on wiki notes you need to pad the end of the buffer with zero bytes:
// https://en.wikipedia.org/wiki/HMAC
//
// This is done implicitly by creating a buffer of hash_block_size as the final bytes
// that aren't written over will remain at zero byte values.
//
var o_key_pad_buffer = buffer_create(hash_block_size, buffer_fast, 1)
var i_key_pad_buffer = buffer_create(hash_block_size, buffer_fast, 1)
for (var i = 0; i < key_size; i++) {
var byte = string_byte_at(key, i)
// i_key_pad = key xor [0x36 * blockSize]
buffer_write(i_key_pad_buffer, buffer_u8, byte ^ 0x36)
// o_key_pad = key xor [0x5c * blockSize]
buffer_write(o_key_pad_buffer, buffer_u8, byte ^ 0x5c)
}
// Pad if the key is smaller than hash block size
for (var i = key_size; i < hash_block_size; i++) {
buffer_write(i_key_pad_buffer, buffer_u8, 0 ^ 0x36)
buffer_write(o_key_pad_buffer, buffer_u8, 0 ^ 0x5c)
}
var output_cap_size = hash_block_size+buffer_size
var output = buffer_create(output_cap_size, buffer_fixed, 1)
// Concat ipad + buffer
// - hash(opad_buffer + hash(ipad_buffer + buffer))
// ^^^^^^^^^^^^^^^^^^^^^^^^^
buffer_copy(i_key_pad_buffer, 0, buffer_tell(i_key_pad_buffer), output, buffer_tell(output))
buffer_seek(output, buffer_seek_relative, buffer_tell(i_key_pad_buffer))
buffer_copy(buffer, buffer_offset, buffer_size, output, buffer_tell(output))
buffer_seek(output, buffer_seek_relative, buffer_size)
var ipad_hash = buffer_md5(output, 0, buffer_tell(output))
var ipad_hash_size = string_byte_length(ipad_hash)
show_debug_message("Ipad Hash: " + ipad_hash)
// NOTE(Jake): 2018-03-17
//
// In GM Runtime 2.1.3.189, there seems to be a bug where buffer_write()
// doesn't move the "seek" pointer for buffer_string or buffer_text
//
// Using buffer_seek manually afterwards to work around this
// ----------------------------------------------------------------------
//
// Concat opad + hash(ipad + message)
// - hash(opad_buffer + ihash)
// ^^^^^^^^^^^^^^^^^^^^^^^^^
buffer_seek(output, buffer_seek_start, 0) // reset / reuse buffer to get final hash
buffer_copy(o_key_pad_buffer, 0, buffer_tell(o_key_pad_buffer), output, buffer_tell(output))
buffer_seek(output, buffer_seek_relative, buffer_tell(o_key_pad_buffer))
buffer_write(output, buffer_text, ipad_hash)
buffer_seek(output, buffer_seek_relative, ipad_hash_size)
var result_hash = buffer_md5(output, 0, buffer_tell(output))
// Clear buffers used to calculate hashes
buffer_delete(i_key_pad_buffer)
buffer_delete(o_key_pad_buffer)
buffer_delete(output)
return result_hash;
/*Function hmac
Inputs:
key: Bytes array of bytes
message: Bytes array of bytes to be hashed
hash: Function the hash function to use (e.g. SHA-1)
blockSize: Integer the block size of the underlying hash function (e.g. 64 bytes for SHA-1)
outputSize: Integer the output size of the underlying hash function (e.g. 20 bytes for SHA-1)
Keys longer than blockSize are shortened by hashing them
if (length(key) > blockSize) then
key ← hash(key) //Key becomes outputSize bytes long
Keys shorter than blockSize are padded to blockSize by padding with zeros on the right
if (length(key) < blockSize) then
key ← Pad(key, blockSize) //pad key with zeros to make it blockSize bytes long
o_key_pad = key xor [0x5c * blockSize] //Outer padded key
i_key_pad = key xor [0x36 * blockSize] //Inner padded key
return hash(o_key_pad ∥ hash(i_key_pad ∥ message)) //Where ∥ is concatenation
*/
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment