{"record":{"id":"01bc59470dd3357e","repo":"laurent22/joplin","slug":"authentication-failed-error","errorCode":null,"errorMessage":"Authentication failed! ${error}","messagePattern":"Authentication failed! (.+?)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/app-mobile/services/e2ee/crypto.ts","lineNumber":55,"sourceCode":"\tconst authTag = cipher.getAuthTag();\n\n\treturn Buffer.concat([encryptedData[0], encryptedData[1], authTag]);\n};\n\nconst decryptRaw = (data: ArrayBuffer, algorithm: CipherAlgorithm, key: CryptoBuffer, iv: ArrayBuffer, authTagLength: number, associatedData: CryptoBuffer) => {\n\n\tconst decipher = QuickCrypto.createDecipheriv(algorithm, key, iv, { authTagLength: authTagLength } as CipherGCMOptions) as unknown as DecipherGCM;\n\n\tconst plaintextLength = data.byteLength - authTagLength;\n\tconst authTag = new Uint8Array(data, plaintextLength, authTagLength);\n\tconst encryptedData = new Uint8Array(data, 0, plaintextLength);\n\tdecipher.setAuthTag(authTag);\n\tdecipher.setAAD(associatedData, { plaintextLength: plaintextLength });\n\n\ttry {\n\t\treturn Buffer.concat([decipher.update(encryptedData), decipher.final()]);\n\t} catch (error) {\n\t\tthrow new Error(`Authentication failed! ${error}`);\n\t}\n};\n\nconst crypto: Crypto = {\n\n\trandomBytes: async (size: number) => {\n\t\treturn new Promise((resolve, reject) => {\n\t\t\tQuickCrypto.randomBytes(size, (error, result) => {\n\t\t\t\tif (error) {\n\t\t\t\t\treject(error);\n\t\t\t\t} else {\n\t\t\t\t\tresolve(result);\n\t\t\t\t}\n\t\t\t});\n\t\t});\n\t},\n\n\tdigest: async (algorithm: Digest, data: Uint8Array) => {","sourceCodeStart":37,"sourceCodeEnd":73,"githubUrl":"https://github.com/laurent22/joplin/blob/2654b33620775080d1d59c552259d41e33dad3d2/packages/app-mobile/services/e2ee/crypto.ts#L37-L73","documentation":"Thrown by the mobile E2EE crypto module when GCM authentication fails during decryption — i.e. `decipher.update`/`decipher.final` raises because the auth tag does not match the ciphertext. This means the data was tampered with, the wrong key was used, or the AAD/plaintextLength are inconsistent. The original error is interpolated into the message.","triggerScenarios":"Decrypting a master-key-encrypted note/resource with the wrong key, a corrupted blob, an auth tag sliced from the wrong offset, or mismatched `associatedData`/`plaintextLength`. Reached via the `decrypt` helper in `packages/app-mobile/services/e2ee/crypto.ts` when `data.byteLength <= authTagLength` is also a risk (negative plaintextLength).","commonSituations":"User changed/recovered the master password on a different device so the derived key differs; sync payload truncated or partially written; an older client wrote the blob without AAD and a newer client expects it; concurrency during partial download of an encrypted resource.","solutions":["Confirm the master key / password matches the one used to encrypt (re-derive and compare).","Verify `data.byteLength > authTagLength` before slicing the auth tag.","Ensure the same `associatedData` and `authTagLength` are passed that were used at encryption time.","If the blob is from a partial download, re-fetch the resource before decrypting."],"exampleFix":"// before\nreturn Buffer.concat([decipher.update(encryptedData), decipher.final()]);\n\n// after\nif (data.byteLength <= authTagLength) {\n  throw new Error('Ciphertext too short to contain an auth tag');\n}\ntry {\n  return Buffer.concat([decipher.update(encryptedData), decipher.final()]);\n} catch (error) {\n  throw new Error(`Authentication failed! ${error}`);\n}","handlingStrategy":"validation","validationCode":"if (data.byteLength <= authTagLength) {\n  throw new Error('Ciphertext too short to contain an auth tag');\n}\n// Verify the derived key matches the encryption key before attempting decrypt.","typeGuard":"null","tryCatchPattern":"try {\n  return await decrypt({ data, key, iv, associatedData, authTagLength });\n} catch (error) {\n  if (/Authentication failed/i.test(error.message)) {\n    logger.warn('GCM auth tag mismatch — wrong key or corrupted blob');\n    return null;\n  }\n  throw error;\n}","preventionTips":["Derive the key from the same master password used to encrypt.","Validate ciphertext length is greater than authTagLength before slicing.","Pass identical associatedData and authTagLength to encrypt and decrypt.","Re-fetch partially-downloaded resources before retrying decryption."],"tags":["e2ee","crypto","security","mobile"],"backgroundTag":null,"analyzedSha":"2654b33620775080d1d59c552259d41e33dad3d2","analyzedAt":"2026-08-12T14:26:46.263Z","schemaVersion":2},"datasetVersion":"2026-08-12T18:17:37.767Z"}