hashicorp/terraform · error

invalid private key: %s

Error message

invalid private key: %s

What it means

Thrown by RsaDecryptFunc.Impl (the HCL rsadecrypt() builtin) when ssh.ParseRawPrivateKey fails with an error that is neither an asn1.SyntaxError nor asn1.StructuralError. The default branch formats the underlying Go error as 'invalid private key: <err>'. Note rsadecrypt only ever accepts an unencrypted RSA private key.

Source

Thrown at internal/lang/funcs/crypto.go:187

		key := args[1].AsString()

		b, err := base64.StdEncoding.DecodeString(s)
		if err != nil {
			return cty.UnknownVal(cty.String), function.NewArgErrorf(0, "failed to decode input %q: cipher text must be base64-encoded", s)
		}

		rawKey, err := ssh.ParseRawPrivateKey([]byte(key))
		if err != nil {
			var errStr string
			switch e := err.(type) {
			case asn1.SyntaxError:
				errStr = strings.ReplaceAll(e.Error(), "asn1: syntax error", "invalid ASN1 data in the given private key")
			case asn1.StructuralError:
				errStr = strings.ReplaceAll(e.Error(), "asn1: struture error", "invalid ASN1 data in the given private key")
			default:
				errStr = fmt.Sprintf("invalid private key: %s", e)
			}
			return cty.UnknownVal(cty.String), function.NewArgError(1, errors.New(errStr))
		}
		privateKey, ok := rawKey.(*rsa.PrivateKey)
		if !ok {
			return cty.UnknownVal(cty.String), function.NewArgErrorf(1, "invalid private key type %t", rawKey)
		}

		out, err := rsa.DecryptPKCS1v15(nil, privateKey, b)
		if err != nil {
			return cty.UnknownVal(cty.String), fmt.Errorf("failed to decrypt: %s", err)
		}

		return cty.StringVal(string(out)), nil
	},
})

// Sha1Func contructs a function that computes the SHA1 hash of a given string
// and encodes it with hexadecimal digits.
var Sha1Func = makeStringHashFunction(sha1.New, hex.EncodeToString)

View on GitHub (pinned to c9def3e214)

Solutions

  1. Generate a traditional PEM RSA key: ssh-keygen -t rsa -m PEM -f key -N '' or openssl genrsa -out key 2048.
  2. Ensure the key has no passphrase (decrypt with openssl rsa -in enc -out plain).
  3. Confirm you pass the private half, not the public key, and that it is RSA (not Ed25519/ECDSA).

Example fix

# before
output "pw" { value = rsadecrypt(aws_kms_secrets.s.blob, file("~/.ssh/id_ed25519")) }
# after: generate an RSA PEM key without passphrase
#   ssh-keygen -t rsa -m PEM -f rsa_key -N ''
output "pw" { value = rsadecrypt(aws_kms_secrets.s.blob, file("rsa_key")) }
Defensive patterns

Strategy: validation

Validate before calling

# validate the key is an unencrypted RSA PEM before rsadecrypt
locals {
  key      = file("rsa_key")
  is_rsa   = strcontains(local.key, "-----BEGIN RSA PRIVATE KEY-----") || strcontains(local.key, "BEGIN PRIVATE KEY")
  enc      = strcontains(local.key, "ENCRYPTED")
}
check "rsa_key" {
  assert {
    condition     = local.is_rsa && !local.enc
    error_message = "rsadecrypt needs an unencrypted PEM RSA private key"
  }
}

Type guard

locals {
  ok   = can(regex("-----BEGIN (RSA )?PRIVATE KEY-----", local.key))
  blob = local.ok ? rsadecrypt(var.cipher, local.key) : null
}

Try / catch

locals { pw = try(rsadecrypt(var.cipher, file("rsa_key")), null) }

Prevention

When it happens

Trigger: rsadecrypt(ciphertext, key) where key is an OpenSSH-format key, an Ed25519/ECDSA key, a passphrase-protected key, a public key, or any non-RSA-PEM string.

Common situations: Modern ssh-keygen writes the OpenSSH private-key format which ParseRawPrivateKey rejects; using an Ed25519 key (not RSA) for rsadecrypt; passing the .pub half by mistake; key generated with a passphrase.

Related errors


AI-assisted analysis of hashicorp/terraform@c9def3e214 (2026-08-07). Data as JSON: /api/errors/57f667e896843616. Report an issue: GitHub.