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.

Solutions

  1. Verify the key being unlocked actually contains a private key entity (not just a public key).
  2. Re-export the key from GPG including the private portion (gpg --export-secret-keys).
  3. Ensure the key uses an algorithm/encryption format supported by this library (e.g. RSA, not a newer AEAD-only format).
  4. 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

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


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

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