{"record":{"id":"ffe63890535bc6b4","repo":"MuntashirAkon/AppManager","slug":"unauthorized-client-hmac-mismatch","errorCode":null,"errorMessage":"Unauthorized client: HMAC mismatch.","messagePattern":"Unauthorized client: HMAC mismatch\\.","errorType":"exception","errorClass":"IOException","httpStatus":null,"severity":"error","filePath":"libserver/src/main/java/io/github/muntashirakon/AppManager/server/common/DataTransmission.java","lineNumber":218,"sourceCode":"\n            // Prove legitimacy of server to the client (HMAC_S = HMAC(token, Nonce_C))\n            byte[] serverHmac = AuthUtils.calculateHmac(token, nonceC);\n            sendMessage(serverHmac);\n\n            // Send challenge to client (Nonce_S)\n            byte[] nonceS = AuthUtils.generateNonce();\n            sendMessage(nonceS);\n\n            // Receive client's HMAC (HMAC_C)\n            byte[] clientHmac = readMessage();\n\n            // Validate client (HMAC_C == HMAC(token, Nonce_S)?)\n            byte[] expectedClientHmac = AuthUtils.calculateHmac(token, nonceS);\n            if (MessageDigest.isEqual(clientHmac, expectedClientHmac)) {\n                FLog.log(\"DataTransmission#shakeHands: Authentication successful.\");\n            } else {\n                FLog.log(\"DataTransmission#shakeHands: Authentication failed.\");\n                throw new IOException(\"Unauthorized client: HMAC mismatch.\");\n            }\n        }\n    }\n\n    /**\n     * Handle for messages received. For asynchronous operations or when the socket is not active,\n     * nothing is done. But when server is running {@link #onReceiveMessage(byte[])} is called.\n     *\n     * @throws IOException When it fails to read the message received\n     */\n    public void handleReceive() throws IOException {\n        if (!mAsync) return;\n        while (mRunning) {\n            onReceiveMessage(readMessage());\n        }\n    }\n\n    /**","sourceCodeStart":200,"sourceCodeEnd":236,"githubUrl":"https://github.com/MuntashirAkon/AppManager/blob/0152f468fc9463ee02dc2ca83f6fe4989a2c4ca5/libserver/src/main/java/io/github/muntashirakon/AppManager/server/common/DataTransmission.java#L200-L236","documentation":"During the server side of the mutual HMAC handshake, the client must prove it holds the shared token by sending HMAC(token, Nonce_S). The server recomputes the expected HMAC and compares it in constant time (MessageDigest.isEqual); a mismatch means the client is not authorized, so an IOException('Unauthorized client: HMAC mismatch.') is thrown and the connection is refused.","triggerScenarios":"shakeHands(role=Server) receives a clientHmac byte array that does not equal AuthUtils.calculateHmac(token, nonceS): the client was started with a different/missing token, the client's token file is stale after server restart, or a rogue/foreign client connects to the port.","commonSituations":"AppManager server regenerated its one-time token but an old client session still has the previous token; manually launching the server with a custom token while the app uses the default; security tooling or an attacker probing the root server port; clipboard/config corruption of the token.","solutions":["Restart the AppManager server so the client re-reads the freshly generated token (the token is per-session in this design).","Verify the client passes the exact same token string to DataTransmission that the server was started with (check ServerHandler/mConfigParams token path).","Confirm no encoding mismatch: token must be the identical string on both sides (no trailing whitespace/newline).","If you did not initiate this connection, treat it as an unauthorized probe and consider firewalling the port to local access only."],"exampleFix":"// before: client caches token from previous server session\nString token = readOldTokenFile();\n// after: fetch the token the running server was started with\nString token = serverConfig.getToken();\nif (token == null) throw new IOException(\"Server token missing; restart server.\");\ntransmission.shakeHands(token, Role.Client);\n","handlingStrategy":"try-catch","validationCode":"if (token == null || token.isEmpty()) {\n    throw new IOException(\"No server token available; (re)start the server first.\");\n}\n// confirm the token equals the one the running server was started with\n","typeGuard":null,"tryCatchPattern":"try {\n    transmission.shakeHands(token, Role.Client);\n} catch (IOException e) {\n    if (e.getMessage() != null && e.getMessage().contains(\"HMAC mismatch\")) {\n        // refetch token from the running server and retry once, else abort\n    }\n}\n","preventionTips":["Never cache the token across server restarts; read it fresh from the server's config each session.","Compare token strings for exact equality (trim nothing silently) on both ends.","Restrict the server port to localhost so only your app can even attempt the HMAC handshake.","Treat repeated mismatches as a probe and log the peer for auditing."],"tags":["authentication","hmac","handshake","security"],"backgroundTag":"authentication-failed","analyzedSha":"0152f468fc9463ee02dc2ca83f6fe4989a2c4ca5","analyzedAt":"2026-09-12T14:03:37.243Z","contentChangedAt":"2026-09-12T14:03:37.243Z","schemaVersion":2},"datasetVersion":"2026-09-14T11:17:12.474Z"}