langchain-ai/langgraph · error · ValueError
LANGGRAPH_AES_KEY must be 16, 24, or 32 bytes long.
Error message
LANGGRAPH_AES_KEY must be 16, 24, or 32 bytes long.
What it means
Key-length validation in EncryptedSerializer.from_pycryptodome_aes: a key was resolved (from the 'key' kwarg or LANGGRAPH_AES_KEY) but its byte length is not one of the AES-legal sizes 16, 24, or 32 bytes (AES-128/192/256). The value at fault is the decoded key bytes; strings are encoded before this check, so length is measured post-encoding.
Solutions
- Use exactly 32 bytes for AES-256 (recommended), e.g. a urlsafe base64-encoded 32-byte secret
- Pad/derive a proper key with a KDF (e.g. hashlib.sha256(secret).digest()) instead of truncating or extending manually
- If storing the key in LANGGRAPH_AES_KEY, ensure the variable holds precisely 16, 24, or 32 bytes
Defensive patterns
Strategy: validation
When it happens
Trigger: Thrown at libs/checkpoint/langgraph/checkpoint/serde/encrypted.py:59 when the library encounters an invalid state.
Common situations: See trigger scenarios.
AI-assisted analysis of langchain-ai/langgraph@38031739e5 (2026-08-26).
Data as JSON: /api/errors/5461bbed6bfef047.
Report an issue: GitHub.
Appendix: source
Thrown at libs/checkpoint/langgraph/checkpoint/serde/encrypted.py:59
) -> "EncryptedSerializer":
"""Create an `EncryptedSerializer` using AES encryption."""
try:
from Crypto.Cipher import AES
except ImportError:
raise ImportError(
"Pycryptodome is not installed. Please install it with `pip install pycryptodome`."
) from None
# check if AES key is provided
if "key" in kwargs:
key: bytes = kwargs.pop("key")
else:
key_str = os.getenv("LANGGRAPH_AES_KEY")
if key_str is None:
raise ValueError("LANGGRAPH_AES_KEY environment variable is not set.")
key = key_str.encode()
if len(key) not in (16, 24, 32):
raise ValueError("LANGGRAPH_AES_KEY must be 16, 24, or 32 bytes long.")
# set default mode to EAX if not provided
if kwargs.get("mode") is None:
kwargs["mode"] = AES.MODE_EAX
class PycryptodomeAesCipher(CipherProtocol):
def encrypt(self, plaintext: bytes) -> tuple[str, bytes]:
cipher = AES.new(key, **kwargs)
ciphertext, tag = cipher.encrypt_and_digest(plaintext)
return "aes", cipher.nonce + tag + ciphertext
def decrypt(self, ciphername: str, ciphertext: bytes) -> bytes:
assert ciphername == "aes", f"Unsupported cipher: {ciphername}"
nonce = ciphertext[:16]
tag = ciphertext[16:32]
actual_ciphertext = ciphertext[32:]
cipher = AES.new(key, **kwargs, nonce=nonce)View on GitHub (pinned to 38031739e5)