Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Bug]: Newer versions of Bluetti firmware use BLE encryption #120

Open
jsaiko opened this issue Jul 8, 2024 · 23 comments
Open

[Bug]: Newer versions of Bluetti firmware use BLE encryption #120

jsaiko opened this issue Jul 8, 2024 · 23 comments
Labels
bug Something isn't working verified bug Bug verified by Repository owner

Comments

@jsaiko
Copy link

jsaiko commented Jul 8, 2024

What happened?

I have a new AC180 which does not work with any known currently available open source solution. After some digging, it appears the device is using BLE encryption. Based on my troubleshooting, the "2A 2A" is a signature to start the encryption process.

Connected: True
Started notifications on 0000ff01-0000-1000-8000-00805f9b34fb
Waiting for notifications...
Notification from 0000ff01-0000-1000-8000-00805f9b34fb (Handle: 30): Vendor specific:
Offset  Hexadecimal                                       ASCII
------  -----------------------------------------------   ----------------
000000  2A 2A 01 04 84 69 0F 5A 01 5B                    **...i.Z.[
Notification from 0000ff01-0000-1000-8000-00805f9b34fb (Handle: 30): Vendor specific:
Offset  Hexadecimal                                       ASCII
------  -----------------------------------------------   ----------------
000000  2A 2A 01 04 84 69 0F 5A 01 5B                    **...i.Z.[
Notification from 0000ff01-0000-1000-8000-00805f9b34fb (Handle: 30): Vendor specific:
Offset  Hexadecimal                                       ASCII
------  -----------------------------------------------   ----------------
000000  2A 2A 01 04 84 69 0F 5A 01 5B                    **...i.Z.[
Notification from 0000ff01-0000-1000-8000-00805f9b34fb (Handle: 30): Vendor specific:
Offset  Hexadecimal                                       ASCII
------  -----------------------------------------------   ----------------
000000  2A 2A 01 04 84 69 0F 5A 01 5B                    **...i.Z.[
Notification from 0000ff01-0000-1000-8000-00805f9b34fb (Handle: 30): Vendor specific:
Offset  Hexadecimal                                       ASCII
------  -----------------------------------------------   ----------------
000000  2A 2A 01 04 84 69 0F 5A 01 5B                    **...i.Z.[
Notification from 0000ff01-0000-1000-8000-00805f9b34fb (Handle: 30): Vendor specific:
Offset  Hexadecimal                                       ASCII
------  -----------------------------------------------   ----------------
000000  2A 2A 01 04 84 69 0F 5A 01 5B                    **...i.Z.[
Notification from 0000ff01-0000-1000-8000-00805f9b34fb (Handle: 30): Vendor specific:
Offset  Hexadecimal                                       ASCII
------  -----------------------------------------------   ----------------
000000  2A 2A 01 04 84 69 0F 5A 01 5B                    **...i.Z.[

These notifications appear then a few seconds later the device disconnects if no action is taken.

What version of our software are you running?

0.1.2

What device are you seeing the problem on?

AC180

IOT v9046.01
ARM v2107.02
DSP v2102.02

What bluetooth connection are you using?

Internal bluetooth adapter

Have you changed the integration settings?

false

Integration settings (if you changed them)

No response

Relevant log output

No response

@jsaiko jsaiko added the bug Something isn't working label Jul 8, 2024
@jsaiko
Copy link
Author

jsaiko commented Jul 8, 2024

Screenshot_20240708-114805

Further investigation is showing that devices with manufacturer data containing this value are encrypted: 424c5545545446

@misterdalto
Copy link

also experiencing the same issue, though good to know others were able to sus it out

@jsaiko
Copy link
Author

jsaiko commented Jul 9, 2024

also experiencing the same issue, though good to know others were able to sus it out

I have made tons of progress reverse engineering the protocol. It appears that once you get past the encryption that the underlying data exchange is the same. As for merging this with the integration, not sure I will be much use there.

@Patrick762 Patrick762 added the verified bug Bug verified by Repository owner label Jul 14, 2024
@Patrick762
Copy link
Owner

Seems like my new AC70 also uses encryption ...
Such a waste of potential for bluetti, but ecoflow is not better ...
I really hope there will be a law for open interfaces in the future

@jhagenk
Copy link

jhagenk commented Jul 20, 2024

From what their marketing people told me, they added a "password" function to the Bluetooth control to prevent people walking around RV parks from turning off other people's power.

I don't have details on how they added it.

@jsaiko
Copy link
Author

jsaiko commented Jul 25, 2024

@Patrick762 I have the encryption process mostly worked out in a test script that I wrote in python. I haven't had much time to complete the final steps of the handshake but I at least know what is happening. Hit me up on Discord (I'm in your channel) and I can share it with you if you want. I would like to see if it is universal across the different models.

@10der
Copy link

10der commented Aug 8, 2024

is any news about AC180(T) connection?
image

please...

@Fridgeir-9
Copy link

Fridgeir-9 commented Aug 13, 2024

the same here, also a AC180

UPDATE: todays update to hassio 13.0 finished, after that, state of charge is displayed

@v-maslovskyi
Copy link

Same here with an AC 180
Any updates on this or assistance is needed?

@smai86
Copy link

smai86 commented Sep 24, 2024

Any News on this? Just updated my ep600 to the newest version... now nothing works ... :-(

@jsaiko
Copy link
Author

jsaiko commented Sep 25, 2024

Any News on this? Just updated my ep600 to the newest version... now nothing works ... :-(

Yeah definitely don't update if your stuff is working. I haven't had a bunch of time to finish dissecting the encryption due to work stress. It's definitely not standard and I'm not sure it will work with the HA integration. Might be portable to the MQTT project that exists however.

@smai86
Copy link

smai86 commented Sep 27, 2024

Any News on this? Just updated my ep600 to the newest version... now nothing works ... :-(

Yeah definitely don't update if your stuff is working. I haven't had a bunch of time to finish dissecting the encryption due to work stress. It's definitely not standard and I'm not sure it will work with the HA integration. Might be portable to the MQTT project that exists however.

Yeah, but had to Update my EP600. Because I get a second one and the parallel grid connection didint work until I updated both EP600 to the newest available FW.. :-(
It would be enough for me to get the SOC into homeassistant because all the other values I´ll get from some shelly 3pm...

UPDATE:
Newest Update is working, sorry guys.... The rpoblem was a broken bt proxy

@LeoAlioth
Copy link

guys, i just got the AC180, and while the device connects, only 2 entities a created and nothing shows up.

@russellproud
Copy link

@jsaiko are you able to share the current code, irrelevant of the state? I'd love to pick it up as I have some time to put into some projects and this one is a bug bear of mine that I'd like to sort out.

@jsaiko
Copy link
Author

jsaiko commented Oct 18, 2024

@jsaiko are you able to share the current code, irrelevant of the state? I'd love to pick it up as I have some time to put into some projects and this one is a bug bear of mine that I'd like to sort out.

I unfortunately just did a system format and dont have the most recent version of the code 😞 I did find an older copy though, not sure how broken it is or isn't. Maybe I can find time to touch it up soon.

https://github.com/jsaiko/bluetti-enc-test

@russellproud
Copy link

Thanks @jsaiko. I'll spend some time during the week having a look through and seeing what I can add. Will share back here.

@russellproud
Copy link

@jsaiko (let me know if you don't want me tagging you).

I've gone a slightly different route; I've decompiled the Android APK for Bluetti. I've attached the connection manager classes and associated services to this.

ConnectManager.txt

Item to note is one of the consts defined is ;

public static final String LOCAL_AES_KEY = "459FC535808941F17091E0993EE3E93D";

My java and cryptography skills are a bit rusty, at best. I'll spend some time over next few days trying to compare this to what you've implemented (or atlernatively, just reimplement the below in Python once I've got my head around it).

If something below is an aha moment for you and it's quick for you to implement this to finalise, call it out and I'll hold.

The method bleEncryptedHandler is below for reference.

    public final void bleEncryptedHandle(String str, BleTaskItem bleTaskItem) {
        String str2 = str;
        Intrinsics.checkNotNullParameter(str2, "data");
        String str3 = "";
        if (StringsKt.startsWith(str2, "2A 2A", true)) {
            List split$default = StringsKt.split$default((CharSequence) str2, new String[]{" "}, false, 0, 6, (Object) null);
            int parseInt = Integer.parseInt((String) split$default.get(2), CharsKt.checkRadix(16));
            if (parseInt == 1) {
                byte[] hexStringToBytes = HexUtil.hexStringToBytes(CollectionsKt.joinToString$default(split$default.subList(4, 8), str3, (CharSequence) null, (CharSequence) null, 0, (CharSequence) null, ConnectManager$bleEncryptedHandle$1$random$1.INSTANCE, 30, (Object) null));
                Intrinsics.checkNotNullExpressionValue(hexStringToBytes, "hexStringToBytes(random)");
                String md5Encode = MD5.md5Encode(ArraysKt.reversedArray(hexStringToBytes));
                Intrinsics.checkNotNullExpressionValue(md5Encode, "md5Encode(HexUtil.hexStr…(random).reversedArray())");
                this.randomMd5 = md5Encode;
                String substring = md5Encode.substring(16, 24);
                Intrinsics.checkNotNullExpressionValue(substring, "this as java.lang.String…ing(startIndex, endIndex)");
                getTaskQueue().add(new BleTaskItem(-1, "2A2A0204" + substring + HexUtilKt.hexStrSum$default(HexUtilKt.INSTANCE, "0204" + substring, 0, 2, (Object) null), 0, (StringBuilder) null, false, 0, false, (String) null, 248, (DefaultConstructorMarker) null));
                executeTask$default(this, (BleTaskItem) null, 1, (Object) null);
                String hexStrXorOther = HexUtilKt.INSTANCE.hexStrXorOther(this.randomMd5, ConnConstantsV2.LOCAL_AES_KEY);
                if (hexStrXorOther != null) {
                    str3 = hexStrXorOther;
                }
                this.bleConnAESKey = str3;
            } else if (parseInt == 3) {
                getTaskQueue().poll();
            }
        } else {
            CharSequence charSequence = this.bleConnShareKey;
            if (charSequence == null || charSequence.length() == 0) {
                List<String> hexStr2StringList = HexUtilKt.INSTANCE.hexStr2StringList(ProtocolParse.parseAESCBCData$default(ProtocolParse.INSTANCE, str, this.bleConnAESKey, HexUtil.hexStringToBytes(this.randomMd5), false, 8, (Object) null));
                if (StringsKt.equals(hexStr2StringList.get(0) + hexStr2StringList.get(1), "2A2A", true)) {
                    int parseInt2 = Integer.parseInt(hexStr2StringList.get(2), CharsKt.checkRadix(16));
                    if (parseInt2 == 4) {
                        String joinToString$default = CollectionsKt.joinToString$default(hexStr2StringList.subList(4, 68), str3, (CharSequence) null, (CharSequence) null, 0, (CharSequence) null, ConnectManager$bleEncryptedHandle$2$lotPkHexStr$1.INSTANCE, 30, (Object) null);
                        String joinToString$default2 = CollectionsKt.joinToString$default(hexStr2StringList.subList(68, hexStr2StringList.size() - 2), str3, (CharSequence) null, (CharSequence) null, 0, (CharSequence) null, ConnectManager$bleEncryptedHandle$2$signature$1.INSTANCE, 30, (Object) null);
                        SignatureCrypt signatureCrypt = SignatureCrypt.INSTANCE;
                        byte[] hexStringToBytes2 = HexUtil.hexStringToBytes(joinToString$default + this.randomMd5);
                        Intrinsics.checkNotNullExpressionValue(hexStringToBytes2, "hexStringToBytes(lotPkHexStr + randomMd5)");
                        SignatureCrypt signatureCrypt2 = SignatureCrypt.INSTANCE;
                        byte[] hexStringToBytes3 = HexUtil.hexStringToBytes(joinToString$default2);
                        Intrinsics.checkNotNullExpressionValue(hexStringToBytes3, "hexStringToBytes(signature)");
                        byte[] edcsaToDERSignature = signatureCrypt2.edcsaToDERSignature(hexStringToBytes3);
                        byte[] hexStringToBytes4 = HexUtil.hexStringToBytes(SignatureCrypt.PUBLIC_KEY_K2);
                        Intrinsics.checkNotNullExpressionValue(hexStringToBytes4, "hexStringToBytes(SignatureCrypt.PUBLIC_KEY_K2)");
                        if (signatureCrypt.verifyForECDSA256(hexStringToBytes2, edcsaToDERSignature, hexStringToBytes4)) {
                            ECDHUtils eCDHUtils = ECDHUtils.INSTANCE;
                            byte[] hexStringToBytes5 = HexUtil.hexStringToBytes(ECDHUtils.SECP_256R1_PUBLIC_PREFIX + joinToString$default);
                            Intrinsics.checkNotNullExpressionValue(hexStringToBytes5, "hexStringToBytes(\"${ECDH…LIC_PREFIX}$lotPkHexStr\")");
                            this.iotPublicKey = ECDHUtils.getPublicKey$default(eCDHUtils, hexStringToBytes5, (String) null, 2, (Object) null);
                            KeyPair generateKeyPair = ECDHUtils.INSTANCE.generateKeyPair();
                            this.ecdhKeyPair = generateKeyPair;
                            if (generateKeyPair != null) {
                                ECDHUtils eCDHUtils2 = ECDHUtils.INSTANCE;
                                KeyPair keyPair = this.ecdhKeyPair;
                                if (keyPair != null) {
                                    String publicKey = eCDHUtils2.getPublicKey(keyPair);
                                    SignatureCrypt signatureCrypt3 = SignatureCrypt.INSTANCE;
                                    byte[] hexStringToBytes6 = HexUtil.hexStringToBytes(publicKey + this.randomMd5);
                                    Intrinsics.checkNotNullExpressionValue(hexStringToBytes6, "hexStringToBytes(pkHexStr + randomMd5)");
                                    ECDHUtils eCDHUtils3 = ECDHUtils.INSTANCE;
                                    byte[] hexStringToBytes7 = HexUtil.hexStringToBytes(SignatureCrypt.PRIVATE_KEY_L1);
                                    Intrinsics.checkNotNullExpressionValue(hexStringToBytes7, "hexStringToBytes(SignatureCrypt.PRIVATE_KEY_L1)");
                                    String edcsaDERSignatureParse = SignatureCrypt.INSTANCE.edcsaDERSignatureParse(TransformExtKt.toHexString(signatureCrypt3.sign(hexStringToBytes6, eCDHUtils3.getPrivateKeyForSecp256r1(hexStringToBytes7), "SHA256withECDSA")));
                                    addTaskItem$default(this, new BleTaskItem(-1, "2A2A0580" + publicKey + edcsaDERSignatureParse + HexUtilKt.hexStrSum$default(HexUtilKt.INSTANCE, "0580" + publicKey + edcsaDERSignatureParse, 0, 2, (Object) null), 0, (StringBuilder) null, false, 0, false, (String) null, 248, (DefaultConstructorMarker) null), true, false, 0, false, 28, (Object) null);
                                }
                            }
                        }
                    } else if (parseInt2 == 6 && Integer.parseInt(hexStr2StringList.get(4), CharsKt.checkRadix(16)) == 0) {
                        ECDHUtils eCDHUtils4 = ECDHUtils.INSTANCE;
                        KeyPair keyPair2 = this.ecdhKeyPair;
                        PrivateKey privateKey = keyPair2 != null ? keyPair2.getPrivate() : null;
                        if (privateKey != null) {
                            Intrinsics.checkNotNullExpressionValue(privateKey, "ecdhKeyPair?.private ?: return");
                            PublicKey publicKey2 = this.iotPublicKey;
                            if (publicKey2 != null) {
                                this.bleConnShareKey = TransformExtKt.toHexString(eCDHUtils4.getSharedSecret(privateKey, publicKey2));
                                this.iotPublicKey = null;
                                this.bleConnAESKey = str3;
                                readProtocolVer();
                            }
                        }
                    }
                }
            } else if (bleTaskItem != null) {
                HexUtilKt hexUtilKt = HexUtilKt.INSTANCE;
                ProtocolParse protocolParse = ProtocolParse.INSTANCE;
                String str4 = this.bleConnShareKey;
                if (str4 != null) {
                    modbusDataHandle$default(this, bleTaskItem, hexUtilKt.hexStr2StringList(ProtocolParse.parseAESCBCData$default(protocolParse, str, str4, (byte[]) null, false, 12, (Object) null)), (String) null, 4, (Object) null);
                    executeTask$default(this, (BleTaskItem) null, 1, (Object) null);
                }
            }
        }
    }

@jsaiko
Copy link
Author

jsaiko commented Oct 21, 2024

@russellproud I was using the APK as a reference when creating the script too ;)

@russellproud
Copy link

Haha yeah, I came to that conclusion after posting this :)

@blackys7312
Copy link

Hi, I don't suppose anyone has found a way of getting at least the percentage available info into homeassistant on an EB3A? appreciate there is the encryption issue but didn't know if anyone had found a workaround to get the raw data into HA somehow.

@jsaiko
Copy link
Author

jsaiko commented Dec 13, 2024

If your device uses encryption, even reading the values is not possible without decryption.

@blackys7312
Copy link

it's odd as they have enabled encryption (from what people say) to prevent other people accessing your device however the app has local option which just finds and connects to devices close by without having to authenticate. that would therefore not stop people accessing your device whether it's using encryption or no encryption, you just need the app.

@jhagenk
Copy link

jhagenk commented Dec 14, 2024 via email

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working verified bug Bug verified by Repository owner
Projects
None yet
Development

No branches or pull requests