{"record":{"id":"505b887b6b63a563","repo":"gchq/CyberChef","slug":"incorrect-hash-length","errorCode":null,"errorMessage":"Incorrect hash length","messagePattern":"Incorrect hash length","errorType":"exception","errorClass":"OperationError","httpStatus":null,"severity":"error","filePath":"src/core/operations/CitrixCTX1Decode.mjs","lineNumber":39,"sourceCode":"\n        this.name = \"Citrix CTX1 Decode\";\n        this.module = \"Encodings\";\n        this.description = \"Decodes strings in a Citrix CTX1 password format to plaintext.\";\n        this.infoURL = \"https://www.reddit.com/r/AskNetsec/comments/1s3r6y/citrix_ctx1_hash_decoding/\";\n        this.inputType = \"ArrayBuffer\";\n        this.outputType = \"string\";\n        this.args = [];\n    }\n\n    /**\n     * @param {ArrayBuffer} input\n     * @param {Object[]} args\n     * @returns {string}\n     */\n    run(input, args) {\n        input = new Uint8Array(input);\n        if (input.length % 4 !== 0) {\n            throw new OperationError(\"Incorrect hash length\");\n        }\n        const revinput = input.reverse();\n        const result = [];\n        let temp = 0;\n        for (let i = 0; i < revinput.length; i += 2) {\n            if (i + 2 >= revinput.length) {\n                temp = 0;\n            } else {\n                temp = ((revinput[i + 2] - 0x41) & 0xf) ^ (((revinput[i + 3]- 0x41) << 4) & 0xf0);\n            }\n            temp = (((revinput[i] - 0x41) & 0xf) ^ (((revinput[i + 1] - 0x41) << 4) & 0xf0)) ^ 0xa5 ^ temp;\n            result.push(temp);\n        }\n        // Decodes a utf-16le string\n        return cptable.utils.decode(1200, result.reverse());\n    }\n\n}","sourceCodeStart":21,"sourceCodeEnd":57,"githubUrl":"https://github.com/gchq/CyberChef/blob/4290ea753912378913b1f3f54e0fc5720afeda5d/src/core/operations/CitrixCTX1Decode.mjs#L21-L57","documentation":"Thrown by CitrixCTX1Decode when the input byte length is not a multiple of 4. Citrix CTX1 hashes encode in 4-byte groups (two payload bytes per group after decoding), so a length that isn't divisible by 4 cannot be a well-formed CTX1 hash.","triggerScenarios":"CitrixCTX1Decode.run wraps input in Uint8Array and tests input.length % 4 !== 0. Any input whose byte length mod 4 is nonzero throws immediately — truncated hashes, extra whitespace/newline bytes, or wrong input type (string vs bytes) causing length mismatch.","commonSituations":"User pastes a CTX1 hash with a trailing newline or space (adds 1 byte → not divisible by 4), copies a partial hash, or feeds a plaintext password expecting CTX1 format.","solutions":["Trim trailing whitespace/newlines from the hash before decoding.","Verify the input is actually a CTX1 hash (ASCII letters in the A–P range, length multiple of 4).","Re-copy the full hash without truncation.","Confirm the input type fed to the operation matches bytes vs string expectations."],"exampleFix":"// before — hash with trailing newline → length % 4 != 0\n// input (bytes): \"MMEGGGHD\\n\"\n// after — trimmed, length divisible by 4\n// input (bytes): \"MMEGGGHD\"","handlingStrategy":"validation","validationCode":"const bytes = new Uint8Array(input);\nif (bytes.length === 0 || bytes.length % 4 !== 0) { /* reject — not a valid CTX1 length */ }","typeGuard":"function isCtx1Length(bytes) { return bytes.length > 0 && bytes.length % 4 === 0; }","tryCatchPattern":"null","preventionTips":["Trim trailing newlines/spaces from the hash before decoding.","Confirm every byte is in the CTX1 ASCII range (A–P).","Verify the input is actually CTX1, not a plaintext password."],"tags":["citrix","hash","decode","input-validation"],"backgroundTag":null,"analyzedSha":"4290ea753912378913b1f3f54e0fc5720afeda5d","analyzedAt":"2026-08-13T06:05:50.210Z","schemaVersion":2},"datasetVersion":"2026-08-13T09:17:06.757Z"}