{"record":{"id":"d8a041d650132945","repo":"MuntashirAkon/AppManager","slug":"unauthorized-server-hmac-mismatch","errorCode":null,"errorMessage":"Unauthorized server: HMAC mismatch.","messagePattern":"Unauthorized server: HMAC mismatch\\.","errorType":"exception","errorClass":"IOException","httpStatus":null,"severity":"critical","filePath":"libserver/src/main/java/io/github/muntashirakon/AppManager/server/common/DataTransmission.java","lineNumber":184,"sourceCode":"     */\n    public void shakeHands(@NonNull String token, Role role) throws IOException {\n        Objects.requireNonNull(token);\n        if (role == Role.Client) {\n            FLog.log(\"DataTransmission#shakeHands: Client protocol: \" + PROTOCOL_VERSION);\n            // Send protocol version and client challenge (Nonce_C)\n            sendMessage(PROTOCOL_VERSION.getBytes(StandardCharsets.UTF_8));\n            byte[] nonceC = AuthUtils.generateNonce();\n            sendMessage(nonceC);\n\n            // Receive server's HMAC proof and server nonce (HMAC_S, None_S)\n            byte[] serverHmac = readMessage();\n            byte[] nonceS = readMessage();\n\n            // Validate server (HMAC_S == HMAC(token, Nonce_C)?)\n            byte[] expectedServerHmac = AuthUtils.calculateHmac(token, nonceC);\n            if (!MessageDigest.isEqual(serverHmac, expectedServerHmac)) {\n                FLog.log(\"DataTransmission#shakeHands: Rogue server detected! Connection dropped.\");\n                throw new IOException(\"Unauthorized server: HMAC mismatch.\");\n            }\n\n            // Prove legitimacy of client to the server (HMAC_C = HMAC(token, Nonce_S)\n            byte[] clientHmac = AuthUtils.calculateHmac(token, nonceS);\n            sendMessage(clientHmac);\n\n        } else if (role == Role.Server) {\n            FLog.log(\"DataTransmission#shakeHands: Server protocol: \" + PROTOCOL_VERSION);\n            // Receive protocol version and client nonce (Nonce_C)\n            String clientProtocol = new String(readMessage(), StandardCharsets.UTF_8);\n            if (!PROTOCOL_VERSION.equals(clientProtocol)) {\n                throw new ProtocolVersionException(\"Client protocol version: \" + clientProtocol + \", \" +\n                        \"Server protocol version: \" + PROTOCOL_VERSION);\n            }\n            byte[] nonceC = readMessage();\n\n            // Prove legitimacy of server to the client (HMAC_S = HMAC(token, Nonce_C))\n            byte[] serverHmac = AuthUtils.calculateHmac(token, nonceC);","sourceCodeStart":166,"sourceCodeEnd":202,"githubUrl":"https://github.com/MuntashirAkon/AppManager/blob/0152f468fc9463ee02dc2ca83f6fe4989a2c4ca5/libserver/src/main/java/io/github/muntashirakon/AppManager/server/common/DataTransmission.java#L166-L202","documentation":"During the challenge-response handshake the client verifies the server by checking that HMAC_S equals HMAC(token, Nonce_C). If MessageDigest.isEqual fails, the peer cannot prove possession of the shared token, so shakeHands() throws IOException(\"Unauthorized server: HMAC mismatch.\") and the connection is dropped as a rogue server.","triggerScenarios":"Connecting to a server that uses a different token/secret than the client, a man-in-the-middle without the token, a version mismatch in HMAC computation (different nonce encoding), or relaying through a modified root service.","commonSituations":"Stale or regenerated shared token between app and privileged service after reinstall/update; connecting to the wrong server socket; custom server implementations that compute the HMAC incorrectly.","solutions":["Ensure both client and server derive the token from the same source (same shared secret file/config)","Restart/rebind the privileged service so it picks up the current token","Verify both sides use the same HMAC algorithm and nonce byte encoding (AuthUtils.calculateHmac)","Re-run the full handshake after any token rotation"],"exampleFix":"// before\nDataTransmission t = new DataTransmission(in, out, oldToken);\nt.shakeHands(); // Unauthorized server: HMAC mismatch.\n// after\nbyte[] token = readCurrentSharedToken(); // same source as the server\nDataTransmission t = new DataTransmission(in, out, token);\nt.shakeHands();","handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"try {\n    transmission.shakeHands();\n} catch (IOException e) {\n    if (e.getMessage() != null && e.getMessage().contains(\"Unauthorized server\")) {\n        // drop connection; refresh token and reconnect\n        token = readCurrentSharedToken();\n        transmission = reconnectAndHandshake(token);\n    } else throw e;\n}","preventionTips":["Ensure client and server read the token from the same source","Rotate and redistribute tokens atomically on updates/reinstalls","Use identical HMAC computation and nonce encodings on both ends","Treat any HMAC failure as a security event: drop the connection immediately"],"tags":["security","hmac","authentication"],"backgroundTag":"checksum-mismatch","analyzedSha":"0152f468fc9463ee02dc2ca83f6fe4989a2c4ca5","analyzedAt":"2026-09-12T14:03:37.243Z","contentChangedAt":"2026-09-12T14:03:37.243Z","schemaVersion":2},"datasetVersion":"2026-09-14T16:17:12.679Z"}