getsops/sops · error
no key to unlock
Error message
no key to unlock
What it means
This error is returned by the pgp key-unlocking logic when it is asked to unlock a private key but no decryption mechanism applies: the key is neither encrypted with a passphrase it can prompt for nor otherwise decryptable. The library throws it because it cannot obtain the key material needed to use the key.
Source
Thrown at pgp/keysource.go:589
log.Errorf("failed to close connection with gpg-agent: %s", err)
}
}(conn)
for _, k := range keys {
req := gpgagent.PassphraseRequest{
CacheKey: k.PublicKey.KeyIdShortString(),
Prompt: "Passphrase",
Desc: fmt.Sprintf("Unlock key %s to decrypt sops's key", k.PublicKey.KeyIdShortString()),
}
pass, err := conn.GetPassphrase(&req)
if err != nil {
return nil, fmt.Errorf("gpg-agent passphrase request errored: %s", err)
}
k.PrivateKey.Decrypt([]byte(pass))
return []byte(pass), nil
}
return nil, fmt.Errorf("no key to unlock")
}
}
// loadRing attempts to load the keyring from the provided path.
// Unsupported keys are ignored as long as at least a single valid key is
// found.
func loadRing(path string) (openpgp.EntityList, error) {
f, err := os.Open(path)
if err != nil {
return nil, err
}
defer f.Close()
keyring, err := openpgp.ReadKeyRing(f)
if err != nil {
return nil, err
}
return keyring, nil
}View on GitHub (pinned to 13442bb981)
Solutions
- Verify the key being unlocked actually contains a private key entity (not just a public key).
- Re-export the key from GPG including the private portion (gpg --export-secret-keys).
- Ensure the key uses an algorithm/encryption format supported by this library (e.g. RSA, not a newer AEAD-only format).
- If using passphrase-protected keys, make sure the passphrase branch is reachable (key is actually encrypted).
Example fix
// before
key, _ := openpgp.ReadArmoredKeyRing(pubOnlyArmoredKey)
unlocked, err := unlock(key)
// after
if key.PrivateKey == nil {
return nil, fmt.Errorf("key has no private key material; export the secret key")
}
unlocked, err := unlock(key) Defensive patterns
Strategy: try-catch
Validate before calling
if key == nil || key.PrivateKey == nil {
return fmt.Errorf("cannot unlock: key %s has no private key", keyId)
} Type guard
func hasPrivateKey(e *openpgp.Entity) bool { return e != nil && e.PrivateKey != nil } Try / catch
unlocked, err := unlockKey(key)
if err != nil {
if err.Error() == "no key to unlock" {
return fmt.Errorf("key %s lacks unlockable private material: %w", keyId, err)
}
return err
} Prevention
- Export secret keys, not just public keys, when keys must be decrypted
- Confirm key algorithm support before adding to the keyring
- Test unlocking keys at setup time, not at first use
When it happens
Trigger: Calling the key unlock function with a key whose PrivateKey is nil or of an unsupported type that matches none of the unlock branches (passphrase-encrypted, gpg-agent, etc.).
Common situations: Loading a PGP keyring file containing public-only or unsupported key types, passing the wrong key to a decrypt workflow, or keys created by newer OpenPGP implementations with algorithms this library does not handle.
Related errors
- Failed to read %q: %w
- no encrypted data
- encrypted ciphertext cannot be cast to string
- no decrypted data
- decrypted plaintext data cannot be cast to string
AI-assisted analysis of getsops/sops@13442bb981 (2026-09-01).
Data as JSON: /api/errors/2790c973e45b6e73.
Report an issue: GitHub.