From d352624629871b9ddc1f808033e957d7ff8a4622 Mon Sep 17 00:00:00 2001 From: Andreas Shimokawa Date: Tue, 24 Aug 2021 12:02:04 +0200 Subject: [PATCH] Mi Band 6 auth: first full but nonworking version --- app/src/main/c/ecdh_jni.c | 20 ++++- .../huami/operations/InitOperation.java | 2 +- .../huami/operations/InitOperation2021.java | 87 ++++++++++++++----- 3 files changed, 85 insertions(+), 24 deletions(-) diff --git a/app/src/main/c/ecdh_jni.c b/app/src/main/c/ecdh_jni.c index 27e3ee1ca..08a51228c 100644 --- a/app/src/main/c/ecdh_jni.c +++ b/app/src/main/c/ecdh_jni.c @@ -5,10 +5,26 @@ JNIEXPORT jbyteArray JNICALL Java_nodomain_freeyourgadget_gadgetbridge_service_devices_huami_operations_InitOperation2021_ecdh_1generate_1public( JNIEnv *env, jobject thiz, jbyteArray private_ec) { jboolean isCopy; - jbyte *a = (*env)->GetByteArrayElements(env, private_ec, &isCopy); + jbyte *privec = (*env)->GetByteArrayElements(env, private_ec, &isCopy); jbyte public_ec[48]; - ecdh_generate_keys((unsigned char *) public_ec, (unsigned char *) a); + ecdh_generate_keys((unsigned char *) public_ec, (unsigned char *) privec); jbyteArray return_array = (*env)->NewByteArray(env, 48); (*env)->SetByteArrayRegion(env, return_array, 0, 48, public_ec); return return_array; +} + +JNIEXPORT jbyteArray JNICALL +Java_nodomain_freeyourgadget_gadgetbridge_service_devices_huami_operations_InitOperation2021_ecdh_1generate_1shared( + JNIEnv *env, jobject thiz, jbyteArray private_ec, jbyteArray remote_public_ec) { + + jboolean isCopy; + jbyte *privec = (*env)->GetByteArrayElements(env, private_ec, &isCopy); + jbyte *rempubec = (*env)->GetByteArrayElements(env, remote_public_ec, &isCopy); + jbyte shared_ec[48]; + + ecdh_shared_secret((unsigned char *) privec, (unsigned char *) rempubec, + (unsigned char *) shared_ec); + jbyteArray return_array = (*env)->NewByteArray(env, 48); + (*env)->SetByteArrayRegion(env, return_array, 0, 48, shared_ec); + return return_array; } \ No newline at end of file diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/operations/InitOperation.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/operations/InitOperation.java index a4a24850e..99984049b 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/operations/InitOperation.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/operations/InitOperation.java @@ -87,7 +87,7 @@ public class InitOperation extends AbstractBTLEOperation { } } - private byte[] getSecretKey() { + protected byte[] getSecretKey() { byte[] authKeyBytes = new byte[]{0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x40, 0x41, 0x42, 0x43, 0x44, 0x45}; SharedPreferences sharedPrefs = GBApplication.getDeviceSpecificSharedPrefs(getDevice().getAddress()); diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/operations/InitOperation2021.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/operations/InitOperation2021.java index ab3410ae3..09df7459b 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/operations/InitOperation2021.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/operations/InitOperation2021.java @@ -45,10 +45,17 @@ import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.HuamiSupport; import nodomain.freeyourgadget.gadgetbridge.util.GB; public class InitOperation2021 extends InitOperation { - - private byte[] privateEC = new byte[24]; + private final byte[] privateEC = new byte[24]; private byte[] publicEC; + private byte[] remotePublicEC = new byte[48]; + private byte[] remoteRandom = new byte[16]; private byte[] sharedEC; + private byte[] finalSharedSessionAES = new byte[16]; + + private final byte[] reassembleBuffer = new byte[512]; + private int lastSequenceNumber = 0; + private int reassembleBuffer_pointer = 0; + private int reassembleBuffer_expectedBytes = 0; static { System.loadLibrary("tiny-edhc"); @@ -78,29 +85,15 @@ public class InitOperation2021 extends InitOperation { private native byte[] ecdh_generate_public(byte[] privateEC); + private native byte[] ecdh_generate_shared(byte[] privateEC, byte[] remotePublicEC); + + private void generateKeyPair() { Random r = new Random(); r.nextBytes(privateEC); publicEC = ecdh_generate_public(privateEC); } - private byte[] getSecretKey() { - byte[] authKeyBytes = new byte[]{0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x40, 0x41, 0x42, 0x43, 0x44, 0x45}; - - SharedPreferences sharedPrefs = GBApplication.getDeviceSpecificSharedPrefs(getDevice().getAddress()); - - String authKey = sharedPrefs.getString("authkey", null); - if (authKey != null && !authKey.isEmpty()) { - byte[] srcBytes = authKey.trim().getBytes(); - 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; - } - @Override public TransactionBuilder performInitialized(String taskName) { throw new UnsupportedOperationException("This IS the initialization class, you cannot call this method"); @@ -112,6 +105,58 @@ public class InitOperation2021 extends InitOperation { UUID characteristicUUID = characteristic.getUuid(); if (HuamiService.UUID_CHARACTERISTIC_CHUNKEDTRANSFER_2021_READ.equals(characteristicUUID)) { byte[] value = characteristic.getValue(); +// 0x03 0x01 0x00 0x64 0x00 0x43 0x00 0x00 0x00 0x82 0x00 0x10 0x04 0x01 0x36 0x41 0xf2 0x5a 0x8f 0xb3 + if (value.length > 1 && value[0] == 0x03) { + int sequenceNumber = value[4]; + int headerSize; + if (sequenceNumber == 0 && value[9] == (byte) 0x82 && value[10] == 0x00 && value[11] == 0x10 && value[12] == 0x04 && value[13] == 0x01) { + reassembleBuffer_pointer = 0; + headerSize = 14; + reassembleBuffer_expectedBytes = value[5] - 3; + + } else if (sequenceNumber > 0) { + if (sequenceNumber != lastSequenceNumber + 1) { + LOG.warn("unexpected sequence number"); + return false; + } + headerSize = 5; + } else { + LOG.info("Unhandled characteristic changed: " + characteristicUUID); + return super.onCharacteristicChanged(gatt, characteristic); + } + + int bytesToCopy = value.length - headerSize; + System.arraycopy(value, headerSize, reassembleBuffer, reassembleBuffer_pointer, bytesToCopy); + reassembleBuffer_pointer += bytesToCopy; + + lastSequenceNumber = sequenceNumber; + if (reassembleBuffer_pointer == reassembleBuffer_expectedBytes) { + System.arraycopy(reassembleBuffer, 0, remoteRandom, 0, 16); + System.arraycopy(reassembleBuffer, 16, remotePublicEC, 0, 48); + sharedEC = ecdh_generate_shared(privateEC, remotePublicEC); + byte[] secretKey = getSecretKey(); + for (int i = 0; i < 16; i++) { + finalSharedSessionAES[i] = (byte) (sharedEC[i + 8] ^ secretKey[i]); + } + try { + byte[] encryptedRandom1 = encryptAES(remoteRandom, secretKey); + byte[] encryptedRandom2 = encryptAES(remoteRandom, finalSharedSessionAES); + if (encryptedRandom1.length == 16 && encryptedRandom2.length == 16) { + byte[] command = new byte[33]; + command[0] = 0x05; + System.arraycopy(encryptedRandom1, 0, command, 1, 16); + System.arraycopy(encryptedRandom2, 0, command, 17, 16); + TransactionBuilder builder = createTransactionBuilder("Sending double encryted random to device"); + huamiSupport.writeToChunked2021(builder, (short) 0x82, (byte) 0x67, command); + huamiSupport.performImmediately(builder); + } + } catch (Exception e) { + LOG.error("AES encryption failed", e); + } + } + return true; + } + huamiSupport.logMessageContent(value); return super.onCharacteristicChanged(gatt, characteristic); } else { @@ -121,8 +166,8 @@ public class InitOperation2021 extends InitOperation { } - private byte[] handleAESAuth(byte[] value, byte[] secretKey) throws InvalidKeyException, NoSuchPaddingException, NoSuchAlgorithmException, BadPaddingException, IllegalBlockSizeException { - byte[] mValue = Arrays.copyOfRange(value, 3, 19); + private byte[] encryptAES(byte[] value, byte[] secretKey) throws InvalidKeyException, NoSuchPaddingException, NoSuchAlgorithmException, BadPaddingException, IllegalBlockSizeException { + byte[] mValue = Arrays.copyOfRange(value, 0, 16); @SuppressLint("GetInstance") Cipher ecipher = Cipher.getInstance("AES/ECB/NoPadding"); SecretKeySpec newKey = new SecretKeySpec(secretKey, "AES"); ecipher.init(Cipher.ENCRYPT_MODE, newKey);