Partial and full hash of the MAC addresses detected by Accuware WiFi Location Monitor

In order to comply with the EU General Data Protection Regulation (GDPR) all the MAC addresses detected by Accuware WiFi Location Monitor (made available through the real-time API and the CSV service) are hashed one-way which means that:

  1. starting from the hashed MAC address it is IMPOSSIBLE to compute the original MAC address.
  2. starting from the original MAC address it is possible to compute the hashed MAC address (with the algorithm described below).

Specifically there are 2 types of hash available:

  • Partial Hash (applied by default for every new site created) – only the last part (last 3 octets) of the MAC address is hashed allowing thus to identify the vendor of the Wi-Fi chip using an official repository like this one or using an API like this one. The partially hashed MAC addresses have the first 3 octets identical to the first 3 octets of the original MAC address, then the “H” letter and then the hash of the remaining part of the original MAC address:
    • EXAMPLE: original MAC address: 14:8F:C6:11:22:22 → hashed MAC address: 14:8F:C6:H5:21:YD (14:8F:C6:XX:XX:XX -> Apple Inc.)
  • Fully Hash – the MAC address is completely hashed. The fully hashed MAC addresses begins with the “H” letter:
    • EXAMPLE: original MAC address: 14:8F:C6:11:22:22 → hashed MAC address: HK:ZB:KZ:R5:21:YD

The location of a station is represented in JSON format as in the following example:

{
    "mac": "148FC6H521YD",
    "name": "Tablet",
    "desc": "iPad 3",
    "loc": {
        "lat": 32.797585,
        "lng": -117.249064,
        "levelId": 0,
        "prec": 5 //do not consider this field
    },
    "lrrt": 1,
     "rss": {
         "AC8674106778": -81,
         "AC8674102580": -63,
         "AC8674106780": -80
      }
}

How to convert a MAC address into a hashed MAC address

Below you can find 2 examples of algorithms (written in Ruby and Java) that allows to compute the hashed MAC address (partial or full hashed) starting from a MAC address.

NOTE: the variable site key passed to the hashing function MUST be hard coded to a string provided by Accuware (which is NOT to the siteID!)

Ruby

Code

require 'digest/md5'

def hash_mac(mac_str, site_key, keep_vendor)
    hash = Digest::MD5.hexdigest(mac_str.upcase + site_key).to_i(16).to_s(36)[-11..-1]
    keep_vendor ? (mac_str[0..5] + 'h' + hash[6..10]).upcase : ('h' + hash).upcase
end

Example

hash_mac("000011112222", "abcd", false)
=> "HKZBKZR521YD"
hash_mac("000011112222", "abcd", true)
=> "000011H521YD"

C#

Code

using System;
using System.Numerics;
using System.Security.Cryptography;
using System.Text;
using System.Text.RegularExpressions;

namespace AccuwareMacHasher

{
 class Program
 {
	public static byte[] addByteToArray(byte[] bArray, byte newByte) {
			byte[] newArray = new byte[bArray.Length + 1];
			bArray.CopyTo(newArray, 1);
			newArray[0] = newByte;
			return newArray;
	}

  static int Main(string[] args){
   if (args.Length < 2 || args.Length > 3)

   {
    Console.WriteLine("Please enter valid arguments");
    Console.WriteLine("Usage: AccuwareMacHasher [-partial] MAC_ADDRESS SITE_KEY");
    Console.WriteLine("Example: AccuwareMacHasher 01-23-45-67-89-0A ABCD");
    Console.WriteLine("Example: AccuwareMacHasher -partial 01-23-45-67-89-0A ABCD");
    return 1;
   }

   bool partial = false;
   string mac = "";
   string site = "";

   if (args.Length == 3 && args[0].Equals("-partial", StringComparison.CurrentCultureIgnoreCase)){
    partial = true;
    mac = args[1];
    site = args[2];
   } else{
    mac = args[0];
    site = args[1];
   }

   bool isValid = Regex.IsMatch(mac, @ "(?:[0-9a-fA-F]{2}:){5}[0-9a-fA-F]{2}|(?:[0-9a-fA-F]{2}-){5}[0-9a-fA-F]{2}|(?:[0-9a-fA-F]{2}){5}[0-9a-fA-F]{2}");

   if (!isValid)
   {
    Console.WriteLine("Please provide a valid Mac Address.");
    return 1;
   }

   mac = mac.Replace(" ", "").Replace(":", "").Replace("-", "");
   byte[] inputBytes = Encoding.ASCII.GetBytes(mac.ToUpper() + site);
   Console.WriteLine(mac + site);
   MD5 md5 = new MD5CryptoServiceProvider();
   byte[] result = md5.ComputeHash(inputBytes);
   byte[] resultb = addByteToArray(result, 0);
   string hash = ToBase36String(resultb);
   Console.WriteLine(hash);
   hash = hash.Substring(hash.Length - 11);

   if (partial){
    Console.WriteLine(mac.Substring(0, 6).ToUpper() + "H" + hash.Substring(6));
   } else{
    Console.WriteLine("H" + hash);
   }

   Console.WriteLine("Press any key to close");
   Console.ReadKey();
   return 0;
  }

  public static string ToBase36String(byte[] toConvert)
  {
   const string alphabet = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
   if (BitConverter.IsLittleEndian)
   {
    Array.Reverse(toConvert);
   }
   BigInteger dividend = new BigInteger(toConvert);
   var builder = new StringBuilder();
   while (dividend != 0)
   {
    BigInteger remainder;
    dividend = BigInteger.DivRem(dividend, 36, out remainder);
    builder.Insert(0, alphabet[Math.Abs(((int) remainder))]);
   }
   return builder.ToString();
  }
 }
}

JAVA

Code

public static String HASHED_MAC_PREFIX = "H";

/**
 * Hash a MAC address
 */
static public String hashMac(String macString, String siteKey, boolean keepVendor) {
    byte[] macBytes = (macString + siteKey).getBytes();
    MessageDigest md = null;
    try {
        md = MessageDigest.getInstance("MD5");
    } catch (NoSuchAlgorithmException e) {
        return null;
    }
    byte[] digest = md.digest(macBytes);
    BigInteger bi = new BigInteger(1, digest);
    String biString = bi.toString(36);
    String hash = biString.substring(biString.length() - 11, biString.length());
    if (keepVendor) {
        return mac.toString().substring(0, 6) + HASHED_MAC_PREFIX + hash.substring(6, 11);
    } else {
        return HASHED_MAC_PREFIX + hash;
    }
}