{"record":{"id":"f7b7faa1a7724cff","repo":"OpenNHP/opennhp","slug":"ciphertext-too-short-need-at-least-d-bytes","errorCode":null,"errorMessage":"ciphertext too short: need at least %d bytes","messagePattern":"ciphertext too short: need at least (.+?) bytes","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"nhp/core/crypto.go","lineNumber":264,"sourceCode":"\t\tiv = key[8:24]\n\n\tcase GCM_SM4:\n\t\tblock, err = sm4.NewCipher(key[:16])\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"failed to create SM4 cipher for CBC decryption: %w\", err)\n\t\t}\n\t\tiv = key[16:]\n\n\tcase GCM_CHACHA20POLY1305:\n\t\treturn nil, ErrNotApplicable\n\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported cipher type for CBC decryption: %d\", t)\n\t}\n\n\t// Validate ciphertext: must be at least one block and a multiple of block size\n\tif len(ciphertext) < block.BlockSize() {\n\t\treturn nil, fmt.Errorf(\"ciphertext too short: need at least %d bytes\", block.BlockSize())\n\t}\n\tif len(ciphertext)%block.BlockSize() != 0 {\n\t\treturn nil, fmt.Errorf(\"ciphertext length %d is not a multiple of block size %d\", len(ciphertext), block.BlockSize())\n\t}\n\n\tvar plaintext []byte\n\tif inPlace {\n\t\tplaintext = ciphertext\n\t} else {\n\t\tplaintext = make([]byte, len(ciphertext))\n\t}\n\n\tmode := cipher.NewCBCDecrypter(block, iv)\n\t// CryptBlocks can work in-place if the two arguments are the same.\n\tmode.CryptBlocks(plaintext, ciphertext)\n\n\tif len(plaintext)%block.BlockSize() == 0 {\n\t\t// skip unpadding","sourceCodeStart":246,"sourceCodeEnd":282,"githubUrl":"https://github.com/OpenNHP/opennhp/blob/6e04ca5ff03222a699c24205cd4bf8fee9af7ffe/nhp/core/crypto.go#L246-L282","documentation":"After the block cipher is constructed, CBCDecryption requires the ciphertext to contain at least one full block (16 bytes for AES, 16 for SM4). Empty or truncated ciphertext cannot yield any plaintext block, so it is rejected before CBC decryption. This is a data-integrity guard, not a key problem.","triggerScenarios":"Calling CBCDecryption with an empty slice, a nil slice, or ciphertext truncated to fewer than 16 bytes — e.g. a packet body sliced wrongly or a failed read stored as empty bytes.","commonSituations":"Decoding a zero-length payload from a malformed packet; slicing off the wrong prefix and leaving <16 bytes; a DB/network layer returning empty bytes treated as ciphertext.","solutions":["Check len(ciphertext) >= 16 before calling CBCDecryption and return a caller-level error if empty.","Verify the buffer slice passed excludes headers/IV correctly so the ciphertext region is intact.","Log the ciphertext length at the call site to find where it becomes empty or truncated.","Handle sender-side failure: the encrypting peer may have produced no output — validate before sending."],"exampleFix":"// before\nplain, err := core.CBCDecryption(core.GCM_AES256, key, payload, false)\n// after\nif len(payload) < 16 {\n    return fmt.Errorf(\"payload too short to decrypt: %d bytes\", len(payload))\n}\nplain, err := core.CBCDecryption(core.GCM_AES256, key, payload, false)","handlingStrategy":"validation","validationCode":"if len(ct) < 16 {\n    return fmt.Errorf(\"ciphertext empty or truncated: %d bytes\", len(ct))\n}","typeGuard":null,"tryCatchPattern":"plain, err := core.CBCDecryption(t, key, ct, false)\nif err != nil && strings.Contains(err.Error(), \"ciphertext too short\") {\n    return ErrCorruptPayload\n}","preventionTips":["Length-check payloads before any decrypt call.","Verify slicing logic strips only headers/IVs, leaving full ciphertext blocks.","Treat empty reads from storage/network as errors, not decryptable input."],"tags":["go","crypto","cbc","input-validation"],"backgroundTag":"invalid-argument-format","analyzedSha":"6e04ca5ff03222a699c24205cd4bf8fee9af7ffe","analyzedAt":"2026-09-07T15:44:59.941Z","contentChangedAt":"2026-09-07T15:44:59.941Z","schemaVersion":2},"datasetVersion":"2026-09-16T04:17:20.429Z"}