mirror of
https://codeberg.org/Freeyourgadget/Gadgetbridge
synced 2024-12-18 14:47:46 +01:00
Mi Band 4: Bring your own key support (blindly done, I dont have my key)
THIS STILL REQUIRES MI FIT AND YOUR EXTRACTED KEY HOWTO: 1) press + button in Gadgerbridge 2) LONG PRESS Mi Band 4 3) Tap "Auth Key" 4) Enter your key prefixed with 0x (eg. 0x112233445566778899aabbccddeeff00) 5) Go back 6) Tap Mi Band 4 Success? You tell me.
This commit is contained in:
parent
be28da9430
commit
a60268c05c
@ -590,7 +590,7 @@ public class DiscoveryActivity extends AbstractGBActivity implements AdapterView
|
|||||||
|
|
||||||
DeviceCoordinator coordinator = DeviceHelper.getInstance().getCoordinator(deviceCandidate);
|
DeviceCoordinator coordinator = DeviceHelper.getInstance().getCoordinator(deviceCandidate);
|
||||||
GBDevice device = DeviceHelper.getInstance().toSupportedDevice(deviceCandidate);
|
GBDevice device = DeviceHelper.getInstance().toSupportedDevice(deviceCandidate);
|
||||||
if (coordinator.getSupportedDeviceSpecificSettings(device) != null) {
|
if (coordinator.getSupportedDeviceSpecificSettings(device) == null) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -209,15 +209,16 @@ public class HuamiSupport extends AbstractBTLEDeviceSupport {
|
|||||||
@Override
|
@Override
|
||||||
protected TransactionBuilder initializeDevice(TransactionBuilder builder) {
|
protected TransactionBuilder initializeDevice(TransactionBuilder builder) {
|
||||||
try {
|
try {
|
||||||
heartRateNotifyEnabled = false;
|
|
||||||
boolean authenticate = needsAuth;
|
|
||||||
needsAuth = false;
|
|
||||||
byte authFlags = getAuthFlags();
|
byte authFlags = getAuthFlags();
|
||||||
new InitOperation(authenticate, authFlags, this, builder).perform();
|
byte cryptFlags = getCryptFlags();
|
||||||
|
heartRateNotifyEnabled = false;
|
||||||
|
boolean authenticate = needsAuth && (cryptFlags == 0x00);
|
||||||
|
needsAuth = false;
|
||||||
|
new InitOperation(authenticate, authFlags, cryptFlags, this, builder).perform();
|
||||||
characteristicHRControlPoint = getCharacteristic(GattCharacteristic.UUID_CHARACTERISTIC_HEART_RATE_CONTROL_POINT);
|
characteristicHRControlPoint = getCharacteristic(GattCharacteristic.UUID_CHARACTERISTIC_HEART_RATE_CONTROL_POINT);
|
||||||
characteristicChunked = getCharacteristic(HuamiService.UUID_CHARACTERISTIC_CHUNKEDTRANSFER);
|
characteristicChunked = getCharacteristic(HuamiService.UUID_CHARACTERISTIC_CHUNKEDTRANSFER);
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
GB.toast(getContext(), "Initializing Mi Band 2 failed", Toast.LENGTH_SHORT, GB.ERROR, e);
|
GB.toast(getContext(), "Initializing Huami device failed", Toast.LENGTH_SHORT, GB.ERROR, e);
|
||||||
}
|
}
|
||||||
return builder;
|
return builder;
|
||||||
}
|
}
|
||||||
@ -226,6 +227,10 @@ public class HuamiSupport extends AbstractBTLEDeviceSupport {
|
|||||||
return HuamiService.AUTH_BYTE;
|
return HuamiService.AUTH_BYTE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public byte getCryptFlags() {
|
||||||
|
return 0x00;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the given date/time (calendar) as a byte sequence, suitable for sending to the
|
* Returns the given date/time (calendar) as a byte sequence, suitable for sending to the
|
||||||
* Mi Band 2 (or derivative). The band appears to not handle DST offsets, so we simply add this
|
* Mi Band 2 (or derivative). The band appears to not handle DST offsets, so we simply add this
|
||||||
|
@ -26,6 +26,11 @@ import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.miband3.MiBand
|
|||||||
|
|
||||||
public class MiBand4Support extends MiBand3Support {
|
public class MiBand4Support extends MiBand3Support {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public byte getCryptFlags() {
|
||||||
|
return (byte) 0x80;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public HuamiFWHelper createFWHelper(Uri uri, Context context) throws IOException {
|
public HuamiFWHelper createFWHelper(Uri uri, Context context) throws IOException {
|
||||||
return null;
|
return null;
|
||||||
|
@ -16,6 +16,7 @@
|
|||||||
along with this program. If not, see <http://www.gnu.org/licenses/>. */
|
along with this program. If not, see <http://www.gnu.org/licenses/>. */
|
||||||
package nodomain.freeyourgadget.gadgetbridge.service.devices.huami.operations;
|
package nodomain.freeyourgadget.gadgetbridge.service.devices.huami.operations;
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint;
|
||||||
import android.bluetooth.BluetoothGatt;
|
import android.bluetooth.BluetoothGatt;
|
||||||
import android.bluetooth.BluetoothGattCharacteristic;
|
import android.bluetooth.BluetoothGattCharacteristic;
|
||||||
import android.content.SharedPreferences;
|
import android.content.SharedPreferences;
|
||||||
@ -24,9 +25,7 @@ import android.widget.Toast;
|
|||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.security.InvalidKeyException;
|
import java.security.InvalidKeyException;
|
||||||
import java.security.MessageDigest;
|
|
||||||
import java.security.NoSuchAlgorithmException;
|
import java.security.NoSuchAlgorithmException;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
@ -52,21 +51,25 @@ public class InitOperation extends AbstractBTLEOperation<HuamiSupport> {
|
|||||||
private final TransactionBuilder builder;
|
private final TransactionBuilder builder;
|
||||||
private final boolean needsAuth;
|
private final boolean needsAuth;
|
||||||
private final byte authFlags;
|
private final byte authFlags;
|
||||||
|
private final byte cryptFlags;
|
||||||
|
private final HuamiSupport huamiSupport;
|
||||||
|
|
||||||
public InitOperation(boolean needsAuth, byte authFlags, HuamiSupport support, TransactionBuilder builder) {
|
public InitOperation(boolean needsAuth, byte authFlags, byte cryptFlags, HuamiSupport support, TransactionBuilder builder) {
|
||||||
super(support);
|
super(support);
|
||||||
|
this.huamiSupport = support;
|
||||||
this.needsAuth = needsAuth;
|
this.needsAuth = needsAuth;
|
||||||
this.authFlags = authFlags;
|
this.authFlags = authFlags;
|
||||||
|
this.cryptFlags = cryptFlags;
|
||||||
this.builder = builder;
|
this.builder = builder;
|
||||||
builder.setGattCallback(this);
|
builder.setGattCallback(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void doPerform() throws IOException {
|
protected void doPerform() {
|
||||||
getSupport().enableNotifications(builder, true);
|
huamiSupport.enableNotifications(builder, true);
|
||||||
if (needsAuth) {
|
if (needsAuth) {
|
||||||
builder.add(new SetDeviceStateAction(getDevice(), GBDevice.State.AUTHENTICATING, getContext()));
|
builder.add(new SetDeviceStateAction(getDevice(), GBDevice.State.AUTHENTICATING, getContext()));
|
||||||
// write key to miband2
|
// write key to device
|
||||||
byte[] sendKey = org.apache.commons.lang3.ArrayUtils.addAll(new byte[]{HuamiService.AUTH_SEND_KEY, authFlags}, getSecretKey());
|
byte[] sendKey = org.apache.commons.lang3.ArrayUtils.addAll(new byte[]{HuamiService.AUTH_SEND_KEY, authFlags}, getSecretKey());
|
||||||
builder.write(getCharacteristic(HuamiService.UUID_CHARACTERISTIC_AUTH), sendKey);
|
builder.write(getCharacteristic(HuamiService.UUID_CHARACTERISTIC_AUTH), sendKey);
|
||||||
} else {
|
} else {
|
||||||
@ -77,7 +80,11 @@ public class InitOperation extends AbstractBTLEOperation<HuamiSupport> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private byte[] requestAuthNumber() {
|
private byte[] requestAuthNumber() {
|
||||||
return new byte[]{HuamiService.AUTH_REQUEST_RANDOM_AUTH_NUMBER, authFlags};
|
if (cryptFlags == 0x00) {
|
||||||
|
return new byte[]{HuamiService.AUTH_REQUEST_RANDOM_AUTH_NUMBER, authFlags};
|
||||||
|
} else {
|
||||||
|
return new byte[]{(byte) (cryptFlags | HuamiService.AUTH_REQUEST_RANDOM_AUTH_NUMBER), authFlags, 0x02};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private byte[] getSecretKey() {
|
private byte[] getSecretKey() {
|
||||||
@ -88,13 +95,17 @@ public class InitOperation extends AbstractBTLEOperation<HuamiSupport> {
|
|||||||
String authKey = sharedPrefs.getString("authkey", null);
|
String authKey = sharedPrefs.getString("authkey", null);
|
||||||
if (authKey != null && !authKey.isEmpty()) {
|
if (authKey != null && !authKey.isEmpty()) {
|
||||||
byte[] srcBytes = authKey.getBytes();
|
byte[] srcBytes = authKey.getBytes();
|
||||||
System.arraycopy(srcBytes, 0, authKeyBytes, 0, Math.min(srcBytes.length,16));
|
if (authKey.length() == 34 && authKey.substring(0, 2).equals("0x")) {
|
||||||
|
srcBytes = GB.hexStringToByteArray(authKey.substring(2));
|
||||||
|
}
|
||||||
|
System.arraycopy(srcBytes, 0, authKeyBytes, 0, Math.min(srcBytes.length, 16));
|
||||||
}
|
}
|
||||||
|
|
||||||
return authKeyBytes;
|
return authKeyBytes;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public TransactionBuilder performInitialized(String taskName) throws IOException {
|
public TransactionBuilder performInitialized(String taskName) {
|
||||||
throw new UnsupportedOperationException("This IS the initialization class, you cannot call this method");
|
throw new UnsupportedOperationException("This IS the initialization class, you cannot call this method");
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -105,41 +116,40 @@ public class InitOperation extends AbstractBTLEOperation<HuamiSupport> {
|
|||||||
if (HuamiService.UUID_CHARACTERISTIC_AUTH.equals(characteristicUUID)) {
|
if (HuamiService.UUID_CHARACTERISTIC_AUTH.equals(characteristicUUID)) {
|
||||||
try {
|
try {
|
||||||
byte[] value = characteristic.getValue();
|
byte[] value = characteristic.getValue();
|
||||||
getSupport().logMessageContent(value);
|
huamiSupport.logMessageContent(value);
|
||||||
if (value[0] == HuamiService.AUTH_RESPONSE &&
|
if (value[0] == HuamiService.AUTH_RESPONSE &&
|
||||||
value[1] == HuamiService.AUTH_SEND_KEY &&
|
value[1] == HuamiService.AUTH_SEND_KEY &&
|
||||||
value[2] == HuamiService.AUTH_SUCCESS) {
|
value[2] == HuamiService.AUTH_SUCCESS) {
|
||||||
TransactionBuilder builder = createTransactionBuilder("Sending the secret key to the band");
|
TransactionBuilder builder = createTransactionBuilder("Sending the secret key to the device");
|
||||||
builder.write(characteristic, requestAuthNumber());
|
builder.write(characteristic, requestAuthNumber());
|
||||||
getSupport().performImmediately(builder);
|
huamiSupport.performImmediately(builder);
|
||||||
} else if (value[0] == HuamiService.AUTH_RESPONSE &&
|
} else if (value[0] == HuamiService.AUTH_RESPONSE &&
|
||||||
value[1] == HuamiService.AUTH_REQUEST_RANDOM_AUTH_NUMBER &&
|
(value[1] & 0x0f) == HuamiService.AUTH_REQUEST_RANDOM_AUTH_NUMBER &&
|
||||||
value[2] == HuamiService.AUTH_SUCCESS) {
|
value[2] == HuamiService.AUTH_SUCCESS) {
|
||||||
// md5??
|
|
||||||
byte[] eValue = handleAESAuth(value, getSecretKey());
|
byte[] eValue = handleAESAuth(value, getSecretKey());
|
||||||
byte[] responseValue = org.apache.commons.lang3.ArrayUtils.addAll(
|
byte[] responseValue = org.apache.commons.lang3.ArrayUtils.addAll(
|
||||||
new byte[]{HuamiService.AUTH_SEND_ENCRYPTED_AUTH_NUMBER, authFlags}, eValue);
|
new byte[]{(byte) (HuamiService.AUTH_SEND_ENCRYPTED_AUTH_NUMBER | cryptFlags), authFlags}, eValue);
|
||||||
|
|
||||||
TransactionBuilder builder = createTransactionBuilder("Sending the encrypted random key to the band");
|
TransactionBuilder builder = createTransactionBuilder("Sending the encrypted random key to the device");
|
||||||
builder.write(characteristic, responseValue);
|
builder.write(characteristic, responseValue);
|
||||||
getSupport().setCurrentTimeWithService(builder);
|
huamiSupport.setCurrentTimeWithService(builder);
|
||||||
getSupport().performImmediately(builder);
|
huamiSupport.performImmediately(builder);
|
||||||
} else if (value[0] == HuamiService.AUTH_RESPONSE &&
|
} else if (value[0] == HuamiService.AUTH_RESPONSE &&
|
||||||
value[1] == HuamiService.AUTH_SEND_ENCRYPTED_AUTH_NUMBER &&
|
(value[1] & 0x0f) == HuamiService.AUTH_SEND_ENCRYPTED_AUTH_NUMBER &&
|
||||||
value[2] == HuamiService.AUTH_SUCCESS) {
|
value[2] == HuamiService.AUTH_SUCCESS) {
|
||||||
TransactionBuilder builder = createTransactionBuilder("Authenticated, now initialize phase 2");
|
TransactionBuilder builder = createTransactionBuilder("Authenticated, now initialize phase 2");
|
||||||
builder.add(new SetDeviceStateAction(getDevice(), GBDevice.State.INITIALIZING, getContext()));
|
builder.add(new SetDeviceStateAction(getDevice(), GBDevice.State.INITIALIZING, getContext()));
|
||||||
getSupport().requestDeviceInfo(builder);
|
huamiSupport.requestDeviceInfo(builder);
|
||||||
getSupport().enableFurtherNotifications(builder, true);
|
huamiSupport.enableFurtherNotifications(builder, true);
|
||||||
getSupport().phase2Initialize(builder);
|
huamiSupport.phase2Initialize(builder);
|
||||||
getSupport().phase3Initialize(builder);
|
huamiSupport.phase3Initialize(builder);
|
||||||
getSupport().setInitialized(builder);
|
huamiSupport.setInitialized(builder);
|
||||||
getSupport().performImmediately(builder);
|
huamiSupport.performImmediately(builder);
|
||||||
} else {
|
} else {
|
||||||
return super.onCharacteristicChanged(gatt, characteristic);
|
return super.onCharacteristicChanged(gatt, characteristic);
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
GB.toast(getContext(), "Error authenticating Mi Band 2", Toast.LENGTH_LONG, GB.ERROR, e);
|
GB.toast(getContext(), "Error authenticating Huami device", Toast.LENGTH_LONG, GB.ERROR, e);
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
} else {
|
} else {
|
||||||
@ -148,17 +158,11 @@ public class InitOperation extends AbstractBTLEOperation<HuamiSupport> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private byte[] getMD5(byte[] message) throws NoSuchAlgorithmException {
|
|
||||||
MessageDigest md5 = MessageDigest.getInstance("MD5");
|
|
||||||
return md5.digest(message);
|
|
||||||
}
|
|
||||||
|
|
||||||
private byte[] handleAESAuth(byte[] value, byte[] secretKey) throws InvalidKeyException, NoSuchPaddingException, NoSuchAlgorithmException, BadPaddingException, IllegalBlockSizeException {
|
private byte[] handleAESAuth(byte[] value, byte[] secretKey) throws InvalidKeyException, NoSuchPaddingException, NoSuchAlgorithmException, BadPaddingException, IllegalBlockSizeException {
|
||||||
byte[] mValue = Arrays.copyOfRange(value, 3, 19);
|
byte[] mValue = Arrays.copyOfRange(value, 3, 19);
|
||||||
Cipher ecipher = Cipher.getInstance("AES/ECB/NoPadding");
|
@SuppressLint("GetInstance") Cipher ecipher = Cipher.getInstance("AES/ECB/NoPadding");
|
||||||
SecretKeySpec newKey = new SecretKeySpec(secretKey, "AES");
|
SecretKeySpec newKey = new SecretKeySpec(secretKey, "AES");
|
||||||
ecipher.init(Cipher.ENCRYPT_MODE, newKey);
|
ecipher.init(Cipher.ENCRYPT_MODE, newKey);
|
||||||
byte[] enc = ecipher.doFinal(mValue);
|
return ecipher.doFinal(mValue);
|
||||||
return enc;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -194,6 +194,16 @@ public class GB {
|
|||||||
return new String(hexChars);
|
return new String(hexChars);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static byte[] hexStringToByteArray(String s) {
|
||||||
|
int len = s.length();
|
||||||
|
byte[] data = new byte[len / 2];
|
||||||
|
for (int i = 0; i < len; i += 2) {
|
||||||
|
data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4)
|
||||||
|
+ Character.digit(s.charAt(i+1), 16));
|
||||||
|
}
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
public static String formatRssi(short rssi) {
|
public static String formatRssi(short rssi) {
|
||||||
return String.valueOf(rssi);
|
return String.valueOf(rssi);
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user