Unique computer identifier for license locking

Hi all,

I am almost ready to start selling my first Xojo desktop application for Mac and Windows - this is a project that I moved over from Filemaker, since they have deprecated runtimes, which is what I am currently selling.

In the Filemaker version of my app, I use the PersistentID function to prevent people from copying an unlocked copy of the app to a new computer. On start up of the app, I check that the PersistentID matches the PersistentID that was stored in the database when the license key was first validated. Whilst this mostly works, the PersistentID is not always persistent! I have had a couple of users who’s computers change PersistentID, seemingly at random. From what I’ve read, this may possibly be related to changing hardware components or changing MAC address.

In any case, as Xojo doesn’t have an equivalent built in function, I have tried to design my own. This is based mainly on code I found in the Xojo Forum by Dave S. From my limited testing so far, it seems to serve its purpose. I was hoping to get some feedback as to whether there are any potential pitfalls that I have missed, or if there is a more elegant way to do this.

I have tried to build in “soft” matching, so that if the determined serial no changes for some unknown reason, but the MAC address still matches, then it still counts as a match. I am a self-taught hack, so my apologies if you look at this code and think “WTF is this guy doing…” :stuck_out_tongue:

[code]Public Function MachineID(type As MachineEnum) as String
Dim retval As String

Select Case type
Case MachineEnum.SerialNo, MachineEnum.SerialHash
Dim sh As New Shell
Dim s As String
Dim x As Integer
Dim bytes As String
Dim hex As String

#If TargetMacOS
  sh.execute "/usr/sbin/ioreg -l | /usr/bin/grep IOPlatformSerialNumber"
  If x>0 Then s=ReplaceAll(Trim(Mid(s,x+1)),ChrB(34),"")
#ElseIf TargetWindows
  sh.Execute "wmic bios get serialnumber" 'works for Vista and newer
  s=ReplaceAll(s, "SerialNumber", "")
  '// use IPCONFIG /all and parse the "Physical Address from "Local Area Connection" Block
  '// will be something like C4-2C-03-02-31-26  [C42c03023126 take first 11 char = C42C0302312]
  'sh.Execute("ipconfig /all")
  'If sh.ErrorCode = 0 Then
  's = sh.Result
  's = Mid(s,InStr(s,"Local Area Connection")+21)
  's = Mid(s,InStr(s,"Physical Address")+16)
  's = Left(s,InStr(s,EndOfLine))
  's= ReplaceAll(Trim(Right(s,19)),"-","")
  'End If

If type = MachineEnum.SerialHash Then
  bytes = MD5(s)
  // To display the hash, convert it to hexadecimal
  hex = EncodeHex(bytes)
End If

retval = s

Case MachineEnum.MACAddress
If System.NetworkInterfaceCount > 0 Then
Dim iface As NetworkInterface = System.GetNetworkInterface(0)
retval = iface.MACAddress
retval = “NoNetworkInterface”
End If

End Select

Return retval
End Function

[code]Public Function MatchMachineIdentity() as Boolean
Dim machId As String
Dim serialNo As String
Dim macAddress As String

machId = MachineID(MachineEnum.SerialHash)
serialNo = MachineID(MachineEnum.SerialNo)
macAddress = MachineID(MachineEnum.MACAddress)

'connect to UserAccountDatabase
'(done in MainWindow.open event)

'get data
Dim sql As String
Dim rs As RecordSet

sql = "SELECT Account_ID, Machine_ID, ComputerSerialNo, ComputerMACAddress "
sql = sql + "FROM UserAccount "
sql = sql + "WHERE TRUE "
sql = sql + “ORDER BY Account_ID”

rs = UserAccountDatabase.SQLSelect(sql)

Dim id As Integer
Dim machIdOnfIle As String
Dim serialNoOnfIle As String
Dim macAddressOnfIle As String

If rs <> Nil Then
id = rs.Field(“Account_ID”).IntegerValue
machIdOnfIle = rs.Field(“Machine_ID”).StringValue
serialNoOnfIle = rs.Field(“ComputerSerialNo”).StringValue
macAddressOnfIle = rs.Field(“ComputerMACAddress”).StringValue

If machId = machIdOnfIle Then
  'machineIDs match
  Return True
  For i as Integer = 0 to System.NetworkInterfaceCount-1
    Dim n as NetworkInterface
    n = System.GetNetworkInterface( i )
    If macAddressOnfIle = n.MACAddress Then 
      'macAddress still matches
      sql = "UPDATE UserAccount SET Machine_ID = '" + machId + "', ComputerSerialNo = '" + serialNo + "'"
      If UserAccountDatabase.Error Then
        MsgBox(UserAccountDatabase.ErrorMessage + EndOfLine + CurrentMethodName)
      End If
      Return True
    End If
  Return False
End If

If UserAccountDatabase.Error Then
MsgBox(sql + EndOfLine + UserAccountDatabase.ErrorMessage + EndOfLine + CurrentMethodName)
End If

End If
End Function

Hi Frank,

I have a library ready to sell in some days (macOS, Windows) that can help with this. It is a kind of extension to my current GuancheMOS solution for serial number generation/validation. Matching against the data you’re using is not the best way to go IMHO.

Contact me if you are interested in it.


Also check this example on the blog post:

Machine ID

We use a database and 10 different identifiers to get an unique record. And even handle the case that the record changes.

You can also write a random number to a hidden preferences file and check this, but this won’t survive a reformat.

Personally, for Desktop I use MBS UUID and for Web I use a Cookie with random text.

Thanks for the replies, I appreciate that you guys take the time to help out a novice. Thanks Christian for the link to your Machine ID example - great to see how a more robust system can be implemented!