{"record":{"id":"8eb30c39754ce7d1","repo":"FiloSottile/age","slug":"mismatched-private-and-public-ssh-key","errorCode":null,"errorMessage":"mismatched private and public SSH key","messagePattern":"mismatched private and public SSH key","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"agessh/encrypted_keys.go","lineNumber":132,"sourceCode":"\tcase *ed25519.PrivateKey:\n\t\tdecrypted, err = NewEd25519Identity(*k)\n\t\tpubKey = k.Public().(ed25519.PublicKey)\n\t// ParseRawPrivateKey returns inconsistent types. See Issue 429.\n\tcase ed25519.PrivateKey:\n\t\tdecrypted, err = NewEd25519Identity(k)\n\t\tpubKey = k.Public().(ed25519.PublicKey)\n\tcase *rsa.PrivateKey:\n\t\tdecrypted, err = NewRSAIdentity(k)\n\t\tpubKey = &k.PublicKey\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unexpected SSH key type: %T\", k)\n\t}\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"invalid SSH key: %v\", err)\n\t}\n\n\tif exp := i.pubKey.(ssh.CryptoPublicKey).CryptoPublicKey(); !pubKey.Equal(exp) {\n\t\treturn nil, fmt.Errorf(\"mismatched private and public SSH key\")\n\t}\n\n\ti.decrypted = decrypted\n\treturn i.decrypted.Unwrap(stanzas)\n}\n","sourceCodeStart":114,"sourceCodeEnd":138,"githubUrl":"https://github.com/FiloSottile/age/blob/b74dce4cdbe35b5e5f66c06d9612b72f89028758/agessh/encrypted_keys.go#L114-L138","documentation":"This error is returned by age's SSH identity Unwrap when decrypting an age-encrypted file with an SSH private key. After successfully decrypting the file key, the library compares the public key derived from the provided private key against the public key recorded in the age header stanza. A mismatch means the private key supplied does not correspond to the SSH public key the file was encrypted to, so the decrypted key cannot be trusted and is discarded.","triggerScenarios":"Calling age.Decrypt with an ssh.ParseRawPrivateKey-parsed private key whose derived public key (via CryptoPublicKey) does not equal the pubKey stored in the encrypted-SSH stanza; typically the wrong identity file was passed to the identity list.","commonSituations":"A user has multiple SSH keys and passes the wrong one (e.g. id_ed25519 instead of the id_rsa that was used to encrypt); key files were rotated or regenerated on the same filename; a deploy pipeline points at a default ~/.ssh/id_* key that differs from the intended one; the age file was encrypted to a teammate's public key.","solutions":["Locate the SSH public key recorded in the age file header stanza and supply the matching private key file to age.Decrypt","Verify the pairing locally: run ssh-keygen -y -f <private-key-file> and compare output against the public key used for encryption","List all candidate private keys (ssh.ParseRawPrivateKey on each) and pass them all in the []age.Identity slice so the matching one is found","If the key was rotated, restore the original private key from backup or have the sender re-encrypt the file to the current public key"],"exampleFix":"// before\nidentity, _ := ssh.ParseRawPrivateKey(pemBytes)\nrecipient, _ := agessh.NewRSARecipient(identity.(*rsa.PublicKey))\n// decrypting a file encrypted to a different key fails:\n// \"mismatched private and public SSH key\"\n\n// after\n// pass the identity matching the public key in the file header\nidentity, err := ssh.ParseRawPrivateKey(correctPemBytes)\nif err != nil { log.Fatal(err) }\ni, _ := agessh.NewRSAIdentity(identity.(*rsa.PrivateKey))\nout, err := age.Decrypt(ciphertext, []age.Identity{i})","handlingStrategy":"validation","validationCode":"func validateSSHKeyPair(priv interface{ Public() crypto.PublicKey }) error {\n\tpub, ok := priv.(ssh.CryptoPublicKey)\n\tif !ok {\n\t\treturn errors.New(\"key has no public part\")\n\t}\n\tif pub.CryptoPublicKey() == nil {\n\t\treturn errors.New(\"empty public key\")\n\t}\n\treturn nil\n}","typeGuard":"func isSSHPrivateKey(k interface{ Public() crypto.PublicKey }) bool {\n\t_, ok := k.(ssh.CryptoPublicKey)\n\treturn ok\n}","tryCatchPattern":null,"preventionTips":["Pass every available SSH private key as an []age.Identity to age.Decrypt instead of guessing which one matches","Verify key pairing with ssh-keygen -y -f <privkey> before encrypting/decrypting workflows","Keep a stable mapping between public keys used for encryption and the private key files that hold them","Rotate keys by re-encrypting files, never by overwriting private key files in place"],"tags":["ssh","age","key-mismatch","decryption"],"backgroundTag":"ssh-key-mismatch","analyzedSha":"b74dce4cdbe35b5e5f66c06d9612b72f89028758","analyzedAt":"2026-08-31T23:59:31.627Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T05:18:18.240Z"}