RMRK is retiring.
Registration is disabled. The site will remain online, but eventually become a read-only archive. More information.

RMRK.net has nothing to do with Blockchains, Cryptocurrency or NFTs. We have been around since the early 2000s, but there is a new group using the RMRK name that deals with those things. We have nothing to do with them.
NFTs are a scam, and if somebody is trying to persuade you to buy or invest in crypto/blockchain/NFT content, please turn them down and save your money. See this video for more information.
HMAC-SHA1 Digest/Hashing Module

0 Members and 1 Guest are viewing this topic.

**
Rep:
Level 33
Waifuist Anon
Reposted in the RMXVA section and removed the old posts (I had posted it wrong section ><).

This module provides HMAC-SHA1 hashing through WinAPIs with no custom dlls/libraries.
 
Why did I make this? What's the point of having HMAC-SHA1 hashing in RPGMaker?
I'm a programming enthusiast, and I usually have no use for pre-made engines like RPGMaker because for me most of the fun is making the engine from scratch in low-ish level languages such as C and C++.
About two days ago I had a gnar-gnar idea which proved to be a ton of fun: messing around with RPGMaker's scripting system to see if I could make it do things it wasn't exactly designed for in pure RGSS3 (without using custom dlls) and made a script that successfully gets an OAuth token from twitter (video: http://hnng.moe/f/hM).
OAuth requires HMAC-SHA1 hashing, so this Digest module is part of my OAuth code.
I will also release the OAuth module once I finish it, but not just yet.
 
Script name: Digest
Author: Franc[e]sco aka lolisamurai
Download: http://hnng.moe/f/hO
Source code (also available on pastie):
Spoiler for:
Code: [Select]
=begin
  Digest r1
  by Franc[e]sco
 
  01/07/2015
  r0
    [+] Initial revision
   
  01/08/2015
  r1
    [*] Better commenting
=end
 
# Win32API
GetLastError = Win32API.new("Kernel32", "GetLastError", "", "I")
 
CryptAcquireContext = Win32API.new("Advapi32",
  "CryptAcquireContext", "PPPII", "I")
 
CryptCreateHash = Win32API.new("Advapi32", "CryptCreateHash", "PIPIP", "I")
CryptHashData = Win32API.new("Advapi32", "CryptHashData", "PPII", "I")
CryptImportKey = Win32API.new("Advapi32", "CryptImportKey", "PPIPIP", "I")
CryptSetHashParam = Win32API.new("Advapi32", "CryptSetHashParam", "PIPI", "I")
CryptGetHashParam = Win32API.new("Advapi32", "CryptGetHashParam", "PIPPI", "I")
CryptDestroyHash = Win32API.new("Advapi32", "CryptDestroyHash", "P", "I")
CryptDestroyKey = Win32API.new("Advapi32", "CryptDestroyKey", "P", "I")
CryptReleaseContext = Win32API.new("Advapi32", "CryptReleaseContext", "P", "I")
 
# various utilities for hashing
module Digest
 
  # generates a HMAC-SHA1
  #
  # keybytes: the key (string)
  # data: the data that will be digested
  #
  # returns the array [hmac, last_error]
  # if successful, hmac contains the HMAC-SHA1 and last_error is zero
  # if unsuccessful, hmac contains the error message and last_error
  #   contains the value of GetLastError after the error happened.
  def self.hmacsha1(keybytes, data)
    provider = 0 # handle to the crypto service provider
    hash = 0 # handle to a hash object
    key = 0 # handle to a symmetric key
    hmachash = 0 # handle to a HMAC hash
    hashbytes = "" # the hash as a bynary string
    datalen = 0 # hash length in bytes
    errortext = nil # will be used to store errors
    gle = 0 # will be use to store the value of GetLastError
   
=begin
  RPGMaker's Ruby is extremely minimal, so the only way to call
  WinAPIs that work on Win32 structs is to manually create a byte array
  with the same size of the struct and manually pack the values to
  recreate the memory region that would hold the struct.
 
  We will basically create an array that contains the values of each
  member of the struct in order and then use .pack and specify the
  size of each element so that it will properly get packed.
 
  Here's some of .pack's size specifiers:
  C = uint8_t / BYTE
  S = uint16_t / WORD
  L = uint32_t / DWORD
 
  c = int8_t
  s = int16_t
  l = int32_t
 
  so for example this is how you pack a Win32 POINT struct with X=13,Y=37:
  - Google "msdn POINT" to find the struct definition:
    typedef struct tagPOINT {
      LONG x;
      LONG y;
    } POINT, *PPOINT;
  - x and y and LONGs
  - LONG is a signed DWORD (int32_t), so the size specifier will be l
  - Initialize an array with each member's value in order
    mypoint = [13, 37]
  - Before you pass it to a WinAPI, pack it and then unpack it after the call
    to retrieve the new values:
    packedpoint = mypoint.pack("ll") # LONG, LONG
    some_winapi.call(packedpoint) # some_winapi expects a pointer to POINT
    mypoint = packedpoint.unpack("ll") # retrieve the values of mypoint after
                                       # some_winapi modified it
 
  These are the sizes of the HMAC_INFO struct that I will be using
  typedef struct _HMAC_Info {
    ALG_ID HashAlgid; // L
    BYTE   *pbInnerString; // C
    DWORD  cbInnerString; // L
    BYTE   *pbOuterString; // C
    DWORD  cbOuterString; // L
  } HMAC_INFO, *PHMAC_INFO;
=end
 
    hmacinfo = [0, 0, 0, 0, 0] # zeroed HMAC_INFO struct
    hmacinfo[0] = 0x00008004 # hmacinfo.HashAlgid = CALG_SHA1
   
    # this is a fake loop that breaks after 1 iteration
    # I break out of it on errors to jump straight to the cleanup code
    loop do
      # acquire a handle to the default RSA crypto service provider
      #
      # PROV_RSA_FULL = 1
      # CRYPT_VERIFYCONTEXT = 0xF0000000
      if not CryptAcquireContext.call(o=[provider].pack('i!'),
        0, 0, 1, 0xF0000000)
        gle = GetLastError.call
        errortext = sprintf("CryptAcquireContext failed")
        break
      end
      provider = o.unpack('i!')[0]
 
=begin
  by the way, packing pointers to integers for winapi's works similarly
  to structs, as you see in the call above
 
  my_value = 0
  packed_value = [my_value].pack('i!')
  some_winapi(packed_value)
  my_value = packed_value.unpack('i!')[0]
=end
     
      # acquire a handle to a new hash object
      #
      # CALG_SHA1 = 0x00008004
      if not CryptCreateHash.call(provider, 0x00008004, 0, 0,
        o=[hash].pack('i!'))
        gle = GetLastError.call
        errortext = "CryptCreateHash CALG_SHA1 failed"
        break
      end
      hash = o.unpack('i!')[0]
     
      # hash the key
      if not CryptHashData.call(hash, keybytes, keybytes.length, 0)
        gle = GetLastError.call
        errortext = "CryptHashData CALG_SHA1 failed"
        break
      end
   
=begin
  typedef struct _PUBLICKEYSTRUC {
    BYTE   bType; // C
    BYTE   bVersion; // C
    WORD   reserved; // S
    ALG_ID aiKeyAlg; // L
  } BLOBHEADER, PUBLICKEYSTRUC;
 
  struct {
    BLOBHEADER hdr;
    DWORD len; // L
    BYTE key[1024]; // C * 1024
  } key_blob;
 
  embedded structs are the same as replacing that embedded struct
  with the struct's members.
 
  therefore, the packing for key_blob is: "CCSLL" + "C" * 1024
  - CCSL is the embedded BLOBHEADER
  - L is len
  - and then we have 1024 C's which are the bytes of key
=end
 
      # This is what you'd normally do in C
      # key_blob.hdr.bType = PLAINTEXTKEYBLOB;
      # key_blob.hdr.bVersion = CUR_BLOB_VERSION;
      # key_blob.hdr.reserved = 0;
      # key_blob.hdr.aiKeyAlg = CALG_RC2;
      # key_blob.len = keyBytes.size();
      #
      # This is how we do it with packed structs in Ruby
      key_blob = [
        8,
        2,
        0,
        0x00006602,
        keybytes.length
      ]
      # PLAINTEXTKEYBLOB = 8
      # CUR_BLOB_VERSION = 2
      # CALG_RC2 = 0x00006602
     
      # initialize zeroed memory for "key"
      for i in 1..1024
        key_blob << 0
      end
     
      blobpacking = "CCSLL" + "C" * 1024
     
      # copy keybytes to key_blob.key
      i = 0
      keybytes.each_byte do |kb|
        key_blob[5 + i] = kb
        i += 1
      end
     
      # derive the final key object from the key_blob struct
      #
      # CRYPT_IPSEC_HMAC_KEY = 0x00000100
      if not CryptImportKey.call(provider,
        packedblob=key_blob.pack(blobpacking),
        key_blob.length, 0, 0x00000100, o=[key].pack('i!'))
        gle = GetLastError.call
        errortext = "CryptImportKey failed"
        break
      end
      key = o.unpack('i!')[0]
      key_blob = packedblob.unpack(blobpacking)
     
      # acquire a handle to a new hash object
      #
      # CALG_HMAC = 0x00008009
      if not CryptCreateHash.call(provider, 0x00008009, key,
        0, o=[hmachash].pack('i!'))
        gle = GetLastError.call
        errortext = "CryptCreateHash CALG_HMAC failed"
        break
      end
      hmachash = o.unpack('i!')[0]
   
      # set the HMAC_INFO struct instance for the hash
      #
      # HP_HMAC_INFO = 0x0005
      if not CryptSetHashParam.call(hmachash, 0x0005, o=hmacinfo.pack('LCLCL'), 0)
        gle = GetLastError.call
        errortext = "CryptSetHashParam failed"
        break
      end
      hmacinfo = o.unpack('LCLCL')
     
      # hash the message/data
      if not CryptHashData.call(hmachash, data, data.length, 0)
        gle = GetLastError.call
        errortext = "CryptHashData CALG_HMAC failed"
        break
      end
     
      # get hash length in bytes for the HMAC
      #
      # HP_HASHVAL = 0x0002
      if not CryptGetHashParam.call(hmachash,
        0x0002, 0, o=[datalen].pack('i!'), 0)
        gle = GetLastError.call
        errortext = "CryptGetHashParam 1 failed"
        break
      end
      datalen = o.unpack('i!')[0]
     
      # allocate an array that will hold the final HMAC
      hashbytes = "\0" * datalen
     
      # store the HMAC into hashbytes
      #
      # HP_HASHVAL = 0x0002
      if not CryptGetHashParam.call(hmachash, 0x0002, hashbytes,
        o=[datalen].pack('i!'), 0)
        gle = GetLastError.call
        errortext = "CryptGetHashParam 2 failed"
        break
      end
     
      break
    end
   
    # cleanup
    CryptDestroyHash.call(hmachash) if hmachash
    CryptDestroyKey.call(key) if key
    CryptDestroyHash.call(hash) if hash
    CryptReleaseContext.call(provider) if provider
   
    # return errortext instead of the hash if an error has occurred
    if gle != 0
      return [errortext, gle]
    end
   
    return [hashbytes, gle]
  end
 
end

Example usage:
 
Code: [Select]
hash, gle = Digest::hmacsha1("my key", "my message")
if gle != 0
  # something went wrong
  msgbox_p(sprintf("Error: %s, GLE=%.8X (%d)", hash, gle, gle))
else
  msgbox_p(sprintf("The hash is: %s", hash))
end