getsops/sops · critical

could not decrypt data key with PGP key: %w

Error message

could not decrypt data key with PGP key: %w

What it means

This is the aggregate error returned by DecryptContext after both the go-crypto OpenPGP attempt and the gpg-binary fallback failed to decrypt the data key. It wraps the collected list of both errors. Seeing it means none of the available PGP mechanisms could recover the sops data key for this master key, so the file cannot be decrypted with this recipient.

Source

Thrown at pgp/keysource.go:415

	if !key.disableOpenPGP {
		dataKey, openpgpErr := key.decryptWithOpenPGP()
		if openpgpErr == nil {
			log.WithField("fingerprint", key.Fingerprint).Info("Decryption succeeded")
			return dataKey, nil
		}
		errs = append(errs, fmt.Errorf("github.com/ProtonMail/go-crypto/openpgp error: %w", openpgpErr))
	}

	dataKey, binaryErr := key.decryptWithGnuPG(ctx)
	if binaryErr == nil {
		log.WithField("fingerprint", key.Fingerprint).Info("Decryption succeeded")
		return dataKey, nil
	}
	errs = append(errs, fmt.Errorf("GnuPG binary error: %w", binaryErr))

	log.WithField("fingerprint", key.Fingerprint).Info("Decryption failed")
	return nil, fmt.Errorf("could not decrypt data key with PGP key: %w", errs)
}

// decryptWithOpenPGP attempts to obtain the data key from the EncryptedKey
// using OpenPGP and returns the result.
//
// Note: the current development of OpenPGP vs GnuPG has moved in separate
// directions. This means that e.g. GnuPG >=2.1 works with a .kbx format which
// can not be read by OpenPGP. Given the further assumptions around the
// placement of the files, and the generic fallback Decrypt uses, this raises
// the question of how widely utilized this method still is.
func (key *MasterKey) decryptWithOpenPGP() ([]byte, error) {
	ring, err := key.getSecRing()
	if err != nil {
		return nil, fmt.Errorf("could not load secring: %s", err)
	}
	block, err := armor.Decode(strings.NewReader(key.EncryptedKey))
	if err != nil {
		return nil, fmt.Errorf("armor decoding failed: %s", err)

View on GitHub (pinned to 13442bb981)

Solutions

  1. Read both wrapped causes; typically import the private key for the fingerprint
  2. Verify gpg --list-secret-keys shows the fingerprint; import with gpg --import if not
  3. Ensure the environment can supply the passphrase (GPG_TTY, gpg-agent, loopback pinentry in CI)
  4. If the file was encrypted to a fingerprint you cannot access, obtain the key from the owner or restore it from backup, then re-encrypt: sops updatekeys file.yaml

Example fix

// before
sops -d file.yaml  # could not decrypt data key with PGP key
// after
gpg --import backup-secret.asc && gpg --list-secret-keys <fingerprint> && sops -d file.yaml
Defensive patterns

Strategy: try-catch

Validate before calling

out, err := exec.Command("gpg", "--list-secret-keys", fingerprint).CombinedOutput()
if err != nil || !strings.Contains(string(out), fingerprint) {
    return fmt.Errorf("cannot decrypt: no secret key for %s; import it first", fingerprint)
}

Try / catch

dk, err := key.DecryptContext(ctx)
if err != nil {
    var errs []error
    if errors.As(err, &errs) {
        for _, e := range errs { log.Errorf("decrypt: %v", e) }
    }
    return fmt.Errorf("PGP master key %s cannot decrypt file (missing/unlockable private key): %w", key.Fingerprint, err)
}

Prevention

When it happens

Trigger: Calling DecryptContext (or sops -d) when the private key for the fingerprint is unavailable/unlockable in both mechanisms: not imported, wrong GNUPGHOME, passphrase unavailable, or the EncryptedKey blob is corrupt/rotated to a key you lack.

Common situations: New laptop or CI runner without the secret key; team member removed from key access; file encrypted to an old/rotated fingerprint; gpg-agent passphrase prompts impossible in non-interactive shells.

Related errors


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