getsops/sops · error

failed to decrypt identity file

Error message

failed to decrypt identity file: %v

What it means

While decrypting an encrypted age identity file, any read/decryption error other than the recognized incorrect-passphrase case is wrapped as "failed to decrypt identity file". This covers I/O errors and malformed ciphertext encountered before identities can be parsed. It signals the identity file could not be turned into plaintext at all.

Solutions

  1. Verify the identity file is intact and is a valid age-encrypted (possibly armored) file.
  2. Re-copy or restore the key file from a backup.
  3. Update age/SOPS if the file was produced by a newer format version.

Example fix

// before
$ head -c 100 key.txt > truncated-key.txt  # corrupted key file
// after
$ age -d -o key.txt key.txt.age            # restore full, valid identity file
Defensive patterns

Strategy: validation

Validate before calling

data, err := os.ReadFile(keyPath)
if err != nil {
    return err
}
if len(data) == 0 || !bytes.HasPrefix(bytes.TrimSpace(data), []byte("-----BEGIN")) && !isAgeFile(data) {
    return fmt.Errorf("%s does not look like an age identity file", keyPath)
}

Try / catch

fileKey, err := identity.Unwrap(stanzas)
if err != nil && strings.Contains(err.Error(), "failed to decrypt identity file") {
    // restore the key file from backup before retrying
}

Prevention

When it happens

Trigger: Calling Unwrap on a LazyScryptIdentity when reading/decrypting the encrypted identity file fails with an unexpected error — corrupted file, truncated download, unsupported armor, or I/O failure.

Common situations: Partially copied or corrupted key files; files encrypted with an incompatible age version; disk/permission errors mid-read.

Related errors


AI-assisted analysis of getsops/sops@13442bb981 (2026-09-01). Data as JSON: /api/errors/e6a47c973858e9cc. Report an issue: GitHub.

Appendix: source

Thrown at age/encrypted_keys.go:73

		return fileKey, nil
	}
	i.NoMatchWarning()
	return nil, age.ErrIncorrectIdentity
}

func (i *EncryptedIdentity) decrypt() error {
	d, err := age.Decrypt(bytes.NewReader(i.Contents), &LazyScryptIdentity{i.Passphrase})
	if e := new(age.NoIdentityMatchError); errors.As(err, &e) {
		// ScryptIdentity returns ErrIncorrectIdentity for an incorrect
		// passphrase, which would lead Decrypt to returning "no identity
		// matched any recipient". That makes sense in the API, where there
		// might be multiple configured ScryptIdentity. Since in cmd/age there
		// can be only one, return a better error message.
		i.IncorrectPassphrase()
		return fmt.Errorf("incorrect passphrase")
	}
	if err != nil {
		return fmt.Errorf("failed to decrypt identity file: %v", err)
	}
	i.identities, err = age.ParseIdentities(d)
	return err
}

// LazyScryptIdentity is an age.Identity that requests a passphrase only if it
// encounters an scrypt stanza. After obtaining a passphrase, it delegates to
// ScryptIdentity.
type LazyScryptIdentity struct {
	Passphrase func() (string, error)
}

var _ age.Identity = &LazyScryptIdentity{}

func (i *LazyScryptIdentity) Unwrap(stanzas []*age.Stanza) (fileKey []byte, err error) {
	for _, s := range stanzas {
		if s.Type == "scrypt" && len(stanzas) != 1 {
			return nil, errors.New("an scrypt recipient must be the only one")

View on GitHub (pinned to 13442bb981)