OpenNHP/opennhp · critical

private key parse error

Error message

private key parse error %v

What it means

Raised in nhp-server's UDP Start after the listen socket is bound: base64 decoding of config.PrivateKeyBase64 — the server's identity private key — failed. The daemon logs 'private key parse error' and returns, refusing to run with an unusable Noise-protocol identity.

Solutions

  1. Regenerate keys with the daemon's keygen command (e.g. nhp-serverd keygen --curve --json) and paste the private key field verbatim
  2. Validate with: echo "$KEY" | base64 -d >/dev/null && echo ok
  3. Strip quotes, whitespace, and newlines from the configured value
  4. Confirm you are using the private key, not the public key, and the correct cipher scheme

Example fix

// before
privateKeyBase64 = "MFkwEwYHKoZI..." // hex or PEM fragment
// after
privateKeyBase64 = "O2onvM62pC1io6jQKm8Nc2UyFXcd4kOmOsBIoYtB2ik="
Defensive patterns

Strategy: validation

Validate before calling

key, err := base64.StdEncoding.DecodeString(cfg.PrivateKeyBase64)
if err != nil || len(key) != expectedKeyLen {
    return fmt.Errorf("privateKeyBase64 invalid: decode=%v len=%d", err, len(key))
}

Prevention

When it happens

Trigger: PrivateKeyBase64 contains whitespace, URL-safe base64 characters (-, _), raw binary pasted as text, or was generated by a tool using a different encoding.

Common situations: Operator pasting a hex or raw key instead of base64; config templating introducing quotes/newlines; keys generated with 'keygen --json' output field mixed up (public pasted as private).

Understand the failure class

Background: "Invalid value" and "allowed values are" config errors: what your library rejected and how to fix it — this error's family across 41 libraries.

Related errors


AI-assisted analysis of OpenNHP/opennhp@6e04ca5ff0 (2026-09-07). Data as JSON: /api/errors/abf04d68846bb180. Report an issue: GitHub.

Appendix: source

Thrown at endpoints/server/udpserver.go:266

		Port: s.config.ListenPort,
	})
	if err != nil {
		log.Error("listen error: %v", err)
		return fmt.Errorf("listen error %v", err)
	}

	// retrieve local port
	laddr := s.listenConn.LocalAddr()
	s.listenAddr, err = net.ResolveUDPAddr(laddr.Network(), laddr.String())
	if err != nil {
		log.Error("resolve local UDPAddr error: %v", err)
		return fmt.Errorf("resolve UDPAddr error %v", err)
	}

	prk, err := base64.StdEncoding.DecodeString(s.config.PrivateKeyBase64)
	if err != nil {
		log.Error("private key parse error: %v", err)
		return fmt.Errorf("private key parse error %v", err)
	}

	option := &core.DeviceOptions{
		DisableAgentPeerValidation: s.config.DisableAgentValidation,
	}
	s.device = core.NewDevice(core.NHP_SERVER, prk, option)
	if s.device == nil {
		log.Critical("failed to create device: %v", err)
		return fmt.Errorf("failed to create device %v", err)
	}

	// Stateless cookie signing key. In a multi-instance cluster all
	// nhp-server replicas must share the same value so any of them can
	// verify a cookie that a sibling minted. When the operator hasn't
	// configured one we mint a random per-process key — fine for a single
	// instance, broken for a cluster (the failure is silent: cookies
	// minted by replica A don't verify on replica B and the agent's RKN
	// stalls until timeout). Always log which mode we're in.

View on GitHub (pinned to 6e04ca5ff0)