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

  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.