getsops/sops · error

could not create encrypted SSH identity: %w

Error message

could not create encrypted SSH identity: %w

What it means

sops wraps agessh.NewEncryptedSSHIdentity to turn an age-encrypted SSH private key file into an age identity. This error means the key file was read and the public key parsed, but the encrypted-SSH-identity object could not be constructed, almost always because the public key blob is not one of the supported SSH key types (ssh-ed25519 or RSA) or the blob is corrupt.

Source

Thrown at age/ssh_parse.go:76

	id, err := agessh.ParseIdentity(contents)
	if sshErr, ok := err.(*ssh.PassphraseMissingError); ok {
		pubKey := sshErr.PublicKey
		if pubKey == nil {
			pubKey, err = readPublicKeyFile(keyPath)
			if err != nil {
				return nil, err
			}
		}
		passphrasePrompt := func() ([]byte, error) {
			pass, err := pluginTerminalUI.RequestValue("", fmt.Sprintf("Enter passphrase for %q:", keyPath), true)
			if err != nil {
				return nil, fmt.Errorf("could not read passphrase for %q: %v", keyPath, err)
			}
			return []byte(pass), nil
		}
		i, err := agessh.NewEncryptedSSHIdentity(pubKey, contents, passphrasePrompt)
		if err != nil {
			return nil, fmt.Errorf("could not create encrypted SSH identity: %w", err)
		}
		return i, nil
	}
	if err != nil {
		return nil, fmt.Errorf("malformed SSH identity in %q: %w", keyPath, err)
	}
	return id, nil
}

View on GitHub (pinned to 13442bb981)

Solutions

  1. Replace the SSH key with an ed25519 (or RSA) key: ssh-keygen -t ed25519 and use the new key file
  2. Verify the key file is a complete, valid OpenSSH private key: ssh-keygen -y -f <keyfile> prints the public key without error
  3. Check that the age binary and sops versions support the key type; upgrade sops if using a newer key format
  4. Convert the workflow to a native age key (age-keygen) instead of SSH keys

Example fix

// before
ssh-keygen -t ecdsa -f ~/.ssh/id_ecdsa  # unsupported by age
// after
ssh-keygen -t ed25519 -f ~/.ssh/id_ed25519 # supported by agessh
Defensive patterns

Strategy: validation

Validate before calling

pub, err := os.ReadFile(keyPath)
if err != nil { return err }
out, err := exec.Command("ssh-keygen", "-y", "-f", keyPath).Output()
if err != nil { return fmt.Errorf("not a usable SSH private key: %w", err) }
kt := strings.Fields(string(out)) // must be ssh-ed25519 or ssh-rsa
if len(kt) < 2 || (kt[len(kt)-1] != "ssh-ed25519" && kt[len(kt)-1] != "ssh-rsa") {
    return fmt.Errorf("unsupported key type %q for age SSH identity", kt[len(kt)-1])
}

Type guard

func isAgeSupportedSSHKey(pubKeyLine string) bool {
    f := strings.Fields(pubKeyLine)
    return len(f) >= 2 && (f[len(f)-1] == "ssh-ed25519" || f[len(f)-1] == "ssh-rsa")
}

Try / catch

i, err := loadAgeSSHIdentities(paths)
if err != nil {
    var uerr *UnsupportedSSHKeyError
    if errors.As(err, &uerr) { /* regenerate as ed25519 */ }
    return err
}

Prevention

When it happens

Trigger: Calling parseSSHIdentityFromPrivateKeyFile (via loadAgeSSHIdentities) on an SSH private key whose parsed public key is unsupported (e.g. ECDSA or ed25519-sk), or a pubKey/contents pair that fails agessh.NewEncryptedSSHIdentity validation.

Common situations: Pointing SOPS_AGE_SSH_PRIVATE_KEY_FILE or an ssh recipient/identity at an ECDSA SSH key; corrupted or truncated key file; a key generated with a newer/algorithms the age ssh library doesn't accept; using an OpenSSH key with unsupported cipher features.

Related errors


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