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
- Generate a traditional PEM RSA key: ssh-keygen -t rsa -m PEM -f key -N '' or openssl genrsa -out key 2048.
- Ensure the key has no passphrase (decrypt with openssl rsa -in enc -out plain).
- 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
- Generate keys specifically for rsadecrypt: ssh-keygen -t rsa -m PEM -N '' or openssl genrsa.
- Never use Ed25519/ECDSA/OpenSSH-format keys with rsadecrypt (RSA-only).
- Double-check you pass the private half, and keep it unencrypted.
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
- argument must be a string, a collection type, or a structura
- all arguments must have the same type
- no non-null, non-empty-string arguments
- argument must be a list or tuple
- cannot search an empty list
AI-assisted analysis of hashicorp/terraform@c9def3e214 (2026-08-07).
Data as JSON: /api/errors/57f667e896843616.
Report an issue: GitHub.