eLinkSmart Fingerprint Padlock Cracked

5 minute read

Introduction

The eLinkSmart Fingerprint Padlock is an inexpensive BLE padlock with fingerprint reader. The lock is marketed as good for a backpack, luggage, or a gym locker; the lock can be unlocked with a fingerprint or with the eLinkSmart app via Bluetooth Low Energy (BLE).

As far as I know there are no public attacks against this lock. In this post I’ll detail my attempt to open the lock over BLE.

eLinkSmart fingerprint reader via elinksmart.com

Attack

This lock encrypts app communication to make eavesdropping and impersonation harder. The lock is also not vulerable to simple replay attacks.

Similar locks have been physically attacked by Bosnian Bill with a screwdriver, the LockpickingLawyer with a screwdriver, or compromised via poor API access control and replay attacks .

Unfortunately recent versions of this lock use a hard-coded encryption key. If you have that encryption key you can easily read app communications and impersonate the app.

After recovering the encryption key I will compromise the lock by decrypting password material from a sniffed session and forge my own unlock packets.

Prerequisites

The unlock attack requires two pieces of information

Encryption key
The AES encryption key used to secure BLE communication
Admin Password
The short ascii admin password, which is dynamic and assigned when a lock is bound to the app.

The security of the lock relies entierly on these data remaining secret, but both are trivial to discover.

Encryption key

I found the AES encryption key by decompiling the Android client application. Investigating the app was made more diffcult since the code was obfuscated, but certain telltale strings (SecretKeySepc, AES) were easy enough to find.

A few minutes of exploring uncovered up the following functions:

  public static byte[] c(SecretKeySpec paramSecretKeySpec, byte[] paramArrayOfbyte) throws GeneralSecurityException {
    // Cipher mode!
    Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
    cipher.init(1, paramSecretKeySpec);
    return cipher.doFinal(paramArrayOfbyte);
  }
  // [snip]
  private static SecretKeySpec e() throws UnsupportedEncodingException {
    // uhoh -- key text removed for blog post
    return new SecretKeySpec("...".getBytes("UTF-8"), "AES");
  }

Function c does the encryption with the hardcoded key material in function e. Now I’ve got the encryption key as well as the cipher mode (AES/ECB/PKCS5Padding) required to decrypt traffic.

Admin password capture

The admin password is required to send an unlock command. The user does not choose their own password – it is dynamically set by the client app when first paired with a lock.

I captured a few unlock actions with my bluetooth sniffing rig.

Sniffing rig

I use 3 BBC micro:bits running the absolutely fantastic btlejack to pull BLE data into wireshark.

Admin password decode

After decrypting the captured data I isolated an 18 byte packet that is sent immediately before the lock opens.

0000: 1000 1200 cd3d 88a9 ba69 0562 3238 3434
0010: 3537


I was able to work backwards by searching the app code for the blue and red byte groups as 16-bit little endian integers. Those values are 16 and 18; I’m assuming they’re packet identifiers.

  public static byte[] w(int paramInt, String paramString) {
    byte[] arrayOfByte2 = new byte[18];
    // BLUE
    System.arraycopy(Packet.shortToByteArray_Little((short)16), 0, arrayOfByte2, 0, 2);
    // RED
    System.arraycopy(Packet.shortToByteArray_Little((short)18), 0, arrayOfByte2, 2, 2);
    // ORANGE
    byte[] arrayOfByte1 = paramString.getBytes();
    System.arraycopy(arrayOfByte1, 0, arrayOfByte2, 12, arrayOfByte1.length);
    // this is a very helpful log message
    stringBuilder.append("--packageUnlockCloudPwd-- bUlkCloudPwd:");
    return h(arrayOfByte2);
  }

I ended up on the annoyingly named w function that constructs a login packet (above: edited of brevity and comments mine). Using the helpful bUlkCloudPwd string I was able to search application logs and deteremine the last part of the package (orange) is the ASCII admin password I need to create my own unlock packet.

With that password and the encryption key I can forge my own packets.

Unattented Unlock

To perform an unattended unlock I needed to write some code to speak the unlock protocol. The unlock protocol looks more or less like this:

(login-request) App or Attacker: Hey friend, I’d like to talk with you. Here’s some magic numbers and a timestamp.

(login-response) Lock: Hi there! Here’s my battery level, lock name, firmware version, and a session key. Hold on to that last bit as I’ll ask you for it later!

…later…

(unlock-request) App or Attacker: me again! Here’s an unlock packet with my timestamp, that session key from earlier, and an admin password I got from secure memory (if I’m an app) or a LEGO guy (if I’m an attacker).

(unlock-response) Lock: This seems legit <Whirr Brrr Click> I’m unlocked!

To perform the unlock attack I recreated parts of the protocol in Javascript. The packet coders and decoders, and the functions to send GATT data are less than ~100 lines of Javascript.

TLDR:

Success! The lock has been unlocked. Best of all: the attempt doesn’t show up on the internal lock access logs.

Future work

I only worked on unlock - there are many more interesting pieces of this protocol to explore such as:

  • Factory reset
  • Enrolling fingerprints
  • Temporary and shared passwords

I will return to at least the fingerprint enroll at some point in the future.

Vendor contact

Repeated attempts to notify the vendor over email have gone unanswered, with first contact in Jan 2022 and latest in May 2022.

Mitigation and conclusion

This lock can be easily opened mechanically and digitally and should be avoided for anything other than research purposes in my opinion.

The manufacturer did a few things right like obfuscating the application code and bothering to encrypt the communications. But they undermined their own security by hardcoding the encryption key and emitting copious log messages with easily greppable strings. The log messages were very helpful when trying to figure out what the obsfucated functions like public void M() actually does (sendUnlockByPwd).

The vendor hasn’t returned my emails, but if they did I’d suggest that they:

  • Switch to a more secure version of BLE pairing
  • Stop using static encryption keys
  • Really stop using AES in ECB mode (SO discussion)
  • Remove debug logging from production app builds

I would not recommend this lock to secure your valuables. I would recommend this lock if you want to experiment with BLE lock cracking because it is inexpensive and has a huge and interesting attack surface. If you find anything interesting please reach out to me on Twitter or email. Happy hacking.

Updated: