{"record":{"id":"9fdf51526f064955","repo":"sipeed/picoclaw","slug":"credential-enc-decryption-failed-wrong-passph","errorCode":null,"errorMessage":"credential: enc:// decryption failed (wrong passphrase or SSH key?)","messagePattern":"credential: enc:// decryption failed \\(wrong passphrase or SSH key\\?\\)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"pkg/credential/credential.go","lineNumber":67,"sourceCode":"// process environment. Replace it at startup to use a different source, such as\n// an in-memory SecureStore, so that all LoadConfig() calls everywhere share the\n// same passphrase source without needing os.Environ.\n//\n// Example (launcher main.go):\n//\n//\tcredential.PassphraseProvider = apiHandler.passphraseStore.Get\nvar PassphraseProvider func() string = func() string {\n\treturn os.Getenv(PassphraseEnvVar)\n}\n\n// ErrPassphraseRequired is returned when an enc:// credential is encountered but\n// no passphrase is available from PassphraseProvider. Callers can detect this\n// with errors.Is to distinguish a missing-passphrase condition from other errors.\nvar ErrPassphraseRequired = errors.New(\"credential: enc:// passphrase required\")\n\n// ErrDecryptionFailed is returned when an enc:// credential cannot be decrypted,\n// indicating a wrong passphrase or SSH key. Callers can detect this with errors.Is.\nvar ErrDecryptionFailed = errors.New(\"credential: enc:// decryption failed (wrong passphrase or SSH key?)\")\n\n// SSHKeyPathEnvVar is the environment variable that specifies the path to the\n// SSH private key used for enc:// credential encryption and decryption.\nconst SSHKeyPathEnvVar = \"PICOCLAW_SSH_KEY_PATH\"\n\n// picoclawHome is a package-local copy of config.EnvHome. It is kept here to\n// avoid a circular import between pkg/credential and pkg/config.\nconst picoclawHome = \"PICOCLAW_HOME\"\n\nconst (\n\tFileScheme = \"file://\"\n\tEncScheme  = \"enc://\"\n\n\thkdfInfo = \"picoclaw-credential-v1\"\n\tsaltLen  = 16\n\tnonceLen = 12\n\tkeyLen   = 32\n)","sourceCodeStart":49,"sourceCodeEnd":85,"githubUrl":"https://github.com/sipeed/picoclaw/blob/49183d7e8daed0dba89ddbb6fcb60089401d9680/pkg/credential/credential.go#L49-L85","documentation":"Returned when AES-256-GCM authentication fails while decrypting an enc:// credential (gcm.Open error, wrapped as ErrDecryptionFailed). The key is derived via HKDF-SHA256 from ikm=HMAC-SHA256(SHA256(sshKeyBytes), passphrase), so a wrong passphrase OR a different SSH key file both produce a different key and the same failure. Corrupted/truncated ciphertext fails the same way.","triggerScenarios":"resolveEncrypted on a valid enc:// payload with: (a) PICOCLAW_KEY_PASSPHRASE differing from the encrypt-time passphrase, (b) the SSH key resolved via PICOCLAW_SSH_KEY_PATH or ~/.ssh/picoclaw_ed25519.key differing (by bytes) from the key used by Encrypt, or (c) a base64 blob mangled in transit (quotes/reflow in YAML, truncated line).","commonSituations":"Rotated or regenerated ~/.ssh/picoclaw_ed25519.key after encrypting keys; typos in the passphrase; copying config between hosts with different default keys; the key file in PICOCLAW_SSH_KEY_PATH pointing at a different machine's key.","solutions":["Re-enter the exact passphrase used at encryption time into PICOCLAW_KEY_PASSPHRASE","Restore or point to the original SSH key: export PICOCLAW_SSH_KEY_PATH=/path/to/key_used_when_encrypting (key bytes are part of key derivation)","If the original key material is gone, re-encrypt the credential with credential.Encrypt(currentPassphrase, currentKeyPath, plaintext) and update the config","Verify the enc:// blob is intact: single line, valid base64, no truncation from YAML quoting or editor reflow"],"exampleFix":"// before: key regenerated, old enc:// value no longer decrypts\n// after: re-encrypt with current key material\nenc, err := credential.Encrypt(passphrase, \"/home/user/.ssh/picoclaw_ed25519.key\", \"sk-abc123\")\n// write enc into model_list api_key","handlingStrategy":"try-catch","validationCode":null,"typeGuard":"func isDecryptionFailed(err error) bool {\n    return errors.Is(err, credential.ErrDecryptionFailed)\n}","tryCatchPattern":"val, err := resolver.Resolve(raw)\nif err != nil {\n    if errors.Is(err, credential.ErrDecryptionFailed) {\n        // wrong passphrase OR wrong SSH key: surface a 're-enter passphrase / restore key' prompt;\n        // do NOT brute-force retry in a loop\n    }\n    return err\n}","preventionTips":["Back up the exact SSH key used for encryption (PICOCLAW_SSH_KEY_PATH or ~/.ssh/picoclaw_ed25519.key) together with the passphrase","After rotating the SSH key, re-encrypt all enc:// credentials before deleting the old key","Keep enc:// blobs on a single line and quote them in YAML to prevent truncation/reflow"],"tags":["credentials","crypto","configuration","security"],"backgroundTag":null,"analyzedSha":"49183d7e8daed0dba89ddbb6fcb60089401d9680","analyzedAt":"2026-08-15T21:55:41.315Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}