{"record":{"id":"0f5de0799104d174","repo":"OpenNHP/opennhp","slug":"ciphertext-length-invalid-must-be-iv-multiple-o","errorCode":null,"errorMessage":"cipherText length invalid: must be IV + multiple of block size","messagePattern":"cipherText length invalid: must be IV \\+ multiple of block size","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"nhp/core/crypto.go","lineNumber":375,"sourceCode":"\n// pad adds PKCS#7 padding to data. Uses shared implementation from utils.\nfunc pad(data []byte, blockSize int) []byte {\n\treturn utils.PKCS7Pad(data, blockSize)\n}\n\nfunc AESDecrypt(cipherText []byte, key []byte) ([]byte, error) {\n\tblock, err := aes.NewCipher(key)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\t// Validate ciphertext length:\n\t// - Must have at least IV (16 bytes) + one encrypted block (16 bytes)\n\t// - After IV extraction, remaining must be a multiple of block size\n\tif len(cipherText) < aes.BlockSize*2 {\n\t\treturn nil, fmt.Errorf(\"cipherText too short: need at least %d bytes, got %d\", aes.BlockSize*2, len(cipherText))\n\t}\n\tif (len(cipherText)-aes.BlockSize)%aes.BlockSize != 0 {\n\t\treturn nil, fmt.Errorf(\"cipherText length invalid: must be IV + multiple of block size\")\n\t}\n\tiv := cipherText[:aes.BlockSize]\n\tcipherText = cipherText[aes.BlockSize:]\n\n\t// Decrypt\n\tmode := cipher.NewCBCDecrypter(block, iv)\n\tdecrypted := make([]byte, len(cipherText))\n\tmode.CryptBlocks(decrypted, cipherText)\n\n\t// Remove padding\n\tdecrypted = unpad(decrypted, aes.BlockSize)\n\n\treturn decrypted, nil\n}\n\n// unpad removes PKCS#7 padding from data. Uses shared implementation from utils.\nfunc unpad(padded []byte, blockSize int) []byte {\n\treturn utils.PKCS7Unpad(padded, blockSize)","sourceCodeStart":357,"sourceCodeEnd":393,"githubUrl":"https://github.com/OpenNHP/opennhp/blob/6e04ca5ff03222a699c24205cd4bf8fee9af7ffe/nhp/core/crypto.go#L357-L393","documentation":"After reserving the 16-byte IV, the remaining ciphertext in AESDecrypt must be an exact multiple of the 16-byte AES block size, since CBC decryption cannot process partial blocks (Go's CBC decrypter would panic). This guard rejects misaligned input with a clean error.","triggerScenarios":"Calling AESDecrypt where len(cipherText)-16 is not a multiple of 16 — one or more bytes were added or lost after the IV, or the input isn't AESEncrypt output at all (e.g. raw CBC ciphertext without an IV, or base64 text passed as bytes).","commonSituations":"Storing ciphertext as text and losing/altering bytes in encoding round-trips; concatenating multiple encrypted blobs; passing GCM or other-mode output to this CBC helper; manual slicing that keeps the wrong offset.","solutions":["Validate (len(data)-16)%16 == 0 before calling; trace which serialization step changed the byte count.","Store/transfer ciphertext as raw bytes or length-safe encodings (base64) and decode before decrypting.","Only feed AESDecrypt data produced by AESEncrypt (IV + CBC blocks); use matching APIs for other modes (e.g. AeadFromKey for GCM).","Verify no header/footer bytes are included in the slice passed in."],"exampleFix":"// before\nraw := []byte(b64Stored) // base64 text decrypted directly\nplain, err := core.AESDecrypt(raw, key)\n// after\ndata, err := base64.StdEncoding.DecodeString(b64Stored)\nif (len(data)-16)%16 != 0 {\n    return nil, fmt.Errorf(\"corrupt blob: %d bytes\", len(data))\n}\nplain, err := core.AESDecrypt(data, key)","handlingStrategy":"validation","validationCode":"if (len(blob)-16)%16 != 0 {\n    return fmt.Errorf(\"blob not IV+block-aligned: %d bytes\", len(blob))\n}","typeGuard":null,"tryCatchPattern":"plain, err := core.AESDecrypt(blob, key)\nif err != nil {\n    return fmt.Errorf(\"AES decrypt framing invalid (len=%d): %w\", len(blob), err)\n}","preventionTips":["Decode base64/hex before decrypting; never pass encoded text as ciphertext bytes.","Encrypt/decrypt with matching API pairs (AESEncrypt <-> AESDecrypt).","Persist ciphertext as bytes or length-safe encodings to prevent byte loss."],"tags":["go","crypto","aes","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"}