u/EryopsUruguay

▲ 68 r/Burises+1 crossposts

Alguno de acá es dev en el BROU? Bug en la app con llave digital y me están tomando el pelo

Llevo más de un mes perdiendo la llave digital de eBROU cada 24 horas. Fui a sucursal del BROU varias veces, atención al cliente me ignora cerrándome los tickets o me mandan a crearla de nuevo... hasta me dicen que es problema de mi teléfono. Un amigo mío tiene exactamente el mismo problema y tampoco logra que le den bola. Ambos somos usuarios de OnePlus 12.

Como soy tremendo nerd, descompilé la app oficial (sin root, sin frida, todo legal) y encontré el bug exacto: el plugin de almacenamiento seguro genera una clave del Android Keystore con validez codificada a 86400 segundos (exactamente 24 horas), no la renueva, y cuando expira borra silenciosamente todo el storage cifrado. La llave muere sola al día siguiente. El backend del BROU no se entera y sigue mandando push de firma al teléfono. Reproducible en cualquier OnePlus / Xiaomi / OPPO moderno con SoC Qualcomm.

Síntomas:

Activás la llave digital y funciona. Al otro día, la app dice "Llave no configurada". Pero acá viene la prueba determinante de que NO es problema tuyo ni de tu teléfono.

Si en ese momento iniciás una transacción desde la web de BROU (banking online), llega el push de firma al celular. Lo apretás, se abre la app, y te manda a "configurar Llave desde cero".

O sea: el servidor del BROU cree que la Llave está activa (porque te manda el push), pero la app dice que no lo está. Esa contradicción es imposible si la culpa fuera tuya o del fabricante del teléfono.

Lo que descarté antes de mirar el código

Con `adb shell dumpsys` (no necesitás root para esto):

- App instalada desde Play Store, signature original.

- Dispositivo de fábrica (Verified Boot verde, bootloader bloqueado).

- Sin root, sin ROM custom, sin Magisk.

- Standby bucket = 5 (el sistema no la restringe).

- `uy.brou` está en `mDeviceIdleAllowlist` (OS la exime del doze).

Permiso POST_NOTIFICATIONS otorgado, canal `PushPluginChannel` activo.

Sin VPN, sin Private DNS, sin nada raro en la red.

Reproducible en 2 OnePlus 12 idénticos (descarta defecto único de hardware).

Nada. La app simplemente se rompe sola.

La causa, citada del código

eBROU es una app Cordova/PhoneGap híbrida (no nativa Android — bah, una app del 2010 disfrazada de moderna). Usa un plugin llamado `cordova-plugin-secure-storage-echo-brou`, un fork del proyecto open source `crypho-plugins/cordova-plugin-secure-storage-echo` con modificaciones del BROU. La clase Java nativa es `com.crypho.plugins.SecureStorage`.

Hice `adb pull` del APK y lo descompilé con `apktool` (herramienta open source, todo legal). Hay 7 bugs encadenados:

Bug #1 — Default de validez = 24 horas exactas

`com/crypho/plugins/SecureStorage.smali`, línea 174:

```

const v0, 0x15180

```

`0x15180` = **86400 segundos = 24 horas exactas**. Ese es el valor por defecto que el plugin pasa a `setUserAuthenticationValidityDurationSeconds()` al crear la clave RSA que cifra el estado de la llave.

Bug #2 — El JS del BROU nunca cambia ese default

Hice `grep -c "userAuthenticationValidityDuration"` sobre los 10 MB de JavaScript bundle de la app. 0 ocurrencias. La app nunca sobrescribe el default. Cada llave generada por eBROU queda con validez de exactamente 24 horas**, sin que el usuario haga nada.

Bug #3 — Usa una API que Google deprecó en Android 11

`setUserAuthenticationValidityDurationSeconds(int)` Está deprecado desde 2020!!!! según la documentación oficial de Android. El reemplazo (`setUserAuthenticationParameters(int, int)`) existe desde Android 11. La app declara `targetSdk = 35` (Android 15). Usan una API deprecada hace 5 generaciones de Android en una app bancaria al día...

Bug #4 — No protege contra re-enrolment biométrico

Default de Android: `setInvalidatedByBiometricEnrollment(true)`. Significa que si el OS reentrana el modelo facial (cosa que OxygenOS / ColorOS hace solo periódicamente), la clave muere para siempre.

Verificación: `grep -r "setInvalidatedByBiometricEnrollment" smali_classes*/com/crypho/` 0 hits. El plugin nunca llama explícitamente a esa función para protegerse. Deja el default destructivo.

Bug #5 — Atrapa todo como `Exception` genérico

`grep -n ".catch Ljava/lang/Exception" com/crypho/plugins/SecureStorage.smali` → 6 hits, los 6 son `Exception` genérico.

Lo correcto sería atrapar `UserNotAuthenticatedException` y `KeyPermanentlyInvalidatedException` específicamente y manejar cada caso con su flujo (re-prompt de biometría, regeneración con re-cifrado). El plugin no hace nada de eso. Trata todo como un fallo terminal.

Bug #6 — Cuando falla, **borra tu storage**

`com/crypho/plugins/SecureStorage$6.smali`. Pseudo-Java reconstruido del bytecode:

```java

SharedPreferencesHandler sp = secureStorage.getStorage(INIT_SERVICE);

sp.clear(); // ← BORRA todo el storage cifrado de la Llave

rsa.createKeyPair(ctx, alias, validityDuration); // regenera clave vacía

ErrorResponse err = new ErrorResponse(

ErrorCode.KEY_INVALIDATED,

"Key Invalidated, was re-generated successfully" // ← el mensaje literal en el código

);

```

El plugin literalmente borra tu estado de llave cuando detecta cualquier excepción del Keystore. Y el mensaje que reporta (string literal en el bytecode) es "Key Invalidated, was re-generated successfully"... felicitándose por haber destruido tus datos.

Lo podés verificar vos:

```

strings classes2.dex | grep "was re-generated"

```

Bug #7 — La capa JS hace un segundo CLEAN_UP

Por las dudas que el nativo no haya borrado suficiente, el saga Redux del JavaScript de la app dispara una acción `CLEAN_UP` que borra dos entradas más del storage y resetea el state del flujo de Llave. Después la app muestra "no configurada".

Además el JS no avisa al backend que el estado se perdió. Por eso el backend sigue mandando push como si la llave estuviera activa, obligándote a ir presencial a una sucursal.

La cronología completa del bug

  1. Activás la Llave en sucursal. El plugin genera una clave RSA 2048 en el Android Keystore con validez de 24 horas. Backend te registra y manda push token de FCM al servidor.

  2. T < 24h: todo funciona.

  3. T ≈ 24h: la TEE (entorno seguro del chip) lanza `UserNotAuthenticatedException` porque expiró la ventana de validez.

  4. El plugin atrapa la excepción como `Exception` genérica, **borra tu storage** y regenera una clave vacía.

  5. El JS recibe el error, hace su propio CLEAN_UP y resetea el state.

  6. El backend no se entera. Sigue mandando push a un teléfono que no puede firmar nada.

  7. Vos abrís la app: "Llave no configurada". Tenés que ir a sucursal otra vez.

Cualquier cambio biométrico antes de 24h (re-enrolment automático del modelo facial, parche del sensor de huella) acelera el ciclo.

Por qué te afecta especialmente a vos si tenés OnePlus / Xiaomi / OPPO

Los chips Qualcomm modernos (Snapdragon 8 Gen 2 y posteriores) implementan KeyMint 3 / StrongBox que hace cumplir esta validez de manera más estricta que las TEE viejas. Las ROMs basadas en ColorOS (OxygenOS, Realme UI, ColorOS) reentrenan biometría automáticamente.

Hay un montón de uruguayos con estos teléfonos. Cualquier cliente de BROU con OnePlus, Xiaomi, OPPO, Realme, ASUS Zenfone o Sony moderno está sufriendo esto en silencio.

Lo que BROU debería arreglar

  1. Cambiar el default de 24h a algo razonable (o eliminar la API deprecada y usar `setUserAuthenticationParameters`).

  2. Llamar `setInvalidatedByBiometricEnrollment(false)` (1 línea de código).

  3. Atrapar `UserNotAuthenticatedException` específicamente y reprompt en lugar de borrar.

  4. Atrapar `KeyPermanentlyInvalidatedException` y regenerar la clave **sin destruir el ciphertext**.

  5. Sacar el `CLEAN_UP` automático del lado JS.

  6. Notificar al backend cuando el estado local se pierde para que no mande push fantasma.

Perdón por tanto texto y por tirar esto acá!

Pero estoy re podrido, me tratan como un viejo gagá que no sabe usar un teléfono, a mi amigo que le pasa lo mismo también... Está todo mal en la atención del BROU.

Si por acá no logro que llegue a alguien, voy a ir al BCU a ver si ahí los pueden apretar.

reddit.com
u/EryopsUruguay — 3 days ago
▲ 26 r/Burises+1 crossposts

Integración Home Assistant con UTE (funcionando en 2026)

Buenas!

Les comparto la integración que armé con el amigo Claude para integrar Home Assistant con UTE.

Se accede con el nombre de usuario y contraseña y trae información que ya tendrías en la app de Android.

Cualquier mejora o sugerencia, es bienvenida!

https://github.com/juanmreyesa/ute-ha-uruguay

u/EryopsUruguay — 10 days ago