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.

Solutions

  1. 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.
  2. Restore the datastore from a known-good etcd snapshot taken while the cluster was healthy.
  3. If no good snapshot exists, back up and delete the db directory and reinitialize the cluster (--cluster-init), then rejoin peers.
  4. 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

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


AI-assisted analysis of k3s-io/k3s@6ba341e396 (2026-08-15). Data as JSON: /api/errors/dbaa42222fda2bc1. Report an issue: GitHub.

Appendix: 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)