k3s-io/k3s · error
invalid cipher text, not : delimited
Error message
invalid cipher text, not : delimited
What it means
decrypt() requires ciphertext in the exact form produced by encrypt(): '<salt>:<base64 AES-GCM sealed data>'. SplitN on ':' yields fewer than 2 parts when the value contains no colon, so the stored bytes are not ciphertext from this scheme and cannot be decrypted with any passphrase.
Source
Thrown at pkg/cluster/encrypt.go:57
return nil, err
}
nonce := make([]byte, gcm.NonceSize())
_, err = io.ReadFull(rand.Reader, nonce)
if err != nil {
return nil, err
}
sealed := gcm.Seal(nonce, nonce, plaintext, nil)
return []byte(salt + ":" + base64.StdEncoding.EncodeToString(sealed)), nil
}
// decrypt attempts to decrypt the byte slice using the supplied passphrase.
// The input byte slice should be the ciphertext output from the encrypt function.
func decrypt(passphrase string, ciphertext []byte) ([]byte, error) {
parts := strings.SplitN(string(ciphertext), ":", 2)
if len(parts) != 2 {
return nil, errors.New("invalid cipher text, not : delimited")
}
clearKey := pbkdf2.Key([]byte(passphrase), []byte(parts[0]), 4096, 32, sha1.New)
key, err := aes.NewCipher(clearKey)
if err != nil {
return nil, err
}
gcm, err := cipher.NewGCM(key)
if err != nil {
return nil, err
}
data, err := base64.StdEncoding.DecodeString(parts[1])
if err != nil {
return nil, err
}
View on GitHub (pinned to 6ba341e396)
Solutions
- Confirm the value stored under the token-derived bootstrap key still contains a colon: etcdctl get <bootstrap-key> and inspect; if not, it is corrupt.
- Restore the datastore from a known-good etcd snapshot taken while the cluster was healthy.
- If no good snapshot exists, back up and delete the db directory and reinitialize the cluster (--cluster-init), then rejoin peers.
- Verify you are decrypting with the matching token only after the format is confirmed; a wrong token fails later in gcm.Open, not here.
Defensive patterns
Strategy: validation
Validate before calling
// Mirror the format check decrypt() performs, before calling it:
func isEncryptedBootstrap(v []byte) bool {
parts := strings.SplitN(string(v), ":", 2)
return len(parts) == 2 && parts[0] != "" && parts[1] != ""
}
if !isEncryptedBootstrap(kv.Value) { /* corrupt: restore from snapshot */ } Try / catch
if _, err := decrypt(token, kv.Value); err != nil {
if strings.Contains(err.Error(), "not : delimited") {
// value corrupt/foreign: restore datastore from snapshot, do not retry with other tokens
} else {
// wrong token (gcm.Open auth failure) or transient error
}
} Prevention
- Never hand-edit /bootstrap keys; the value format is salt:base64(gcm) and irreversible if damaged.
- Take etcd snapshots before any datastore maintenance so a format-corrupt value is recoverable.
- Distinguish the two failure modes: no colon = corrupt data; cipher auth failure = wrong token.
When it happens
Trigger: The '/bootstrap' key value was corrupted, truncated, base64-decoded, or overwritten with plaintext/manual bytes; a datastore restore wrote the wrong bytes; code passed an arbitrary buffer (e.g., an unencrypted value from a foreign tool) into decrypt.
Common situations: Datastore restored from a partially corrupt backup; someone hand-edited the etcd key; version mix where an older format wrote values without the salt prefix.
Related errors
- bootstrap data already found and encrypted with different to
- no bootstrap data found in datastore - check server token va
- no bootstrap data is available to reconcile against
- no bootstrap data found
- found multiple bootstrap keys in storage
AI-assisted analysis of k3s-io/k3s@6ba341e396 (2026-08-15).
Data as JSON: /api/errors/dbaa42222fda2bc1.
Report an issue: GitHub.