AlexxIT/go2rtc · error
: unable to decrypt
Error message
%w: unable to decrypt
What it means
Mirror of the Encrypt path: Decrypt on TLSEcdhePskWithChacha20Poly1305Sha256 requires the AEAD cipher to be initialized by the handshake. If the atomic pointer does not hold a *ChaCha20Poly1305Cipher (typically nil before key derivation), the library returns errCipherSuiteNotInit wrapped with 'unable to decrypt'.
Solutions
- Ensure the DTLS handshake fully completes before processing records; gate reads on handshake completion.
- Confirm the ChaCha20-Poly1305 suite is negotiated on both ends so aead gets populated.
- Protect against concurrent access — initialize/read c.aead through the DTLS layer's synchronization.
- Re-establish the session if aead remains uninitialized; the suite instance is unusable.
Example fix
// before
out, err := suite.Decrypt(h, raw)
// after
if !suite.Initialized() { // or <-handshakeDone
return errors.New("cipher suite not ready; handshake incomplete")
}
out, err := suite.Decrypt(h, raw) Defensive patterns
Strategy: try-catch
Validate before calling
// Only process records after key derivation
if !handshakeCompleted.Load() {
return errors.New("records cannot be decrypted before handshake")
} Type guard
func suiteReady(s *TLSEcdhePskWithChacha20Poly1305Sha256) bool {
_, ok := s.aead.Load().(*ChaCha20Poly1305Cipher)
return ok
} Try / catch
out, err := suite.Decrypt(h, raw)
if err != nil {
if errors.Is(err, errCipherSuiteNotInit) {
return errors.New("handshake incomplete — wait for handshake done signal")
}
return err
} Prevention
- Complete the DTLS handshake before feeding records to the suite
- Confirm both sides negotiate the ChaCha20-Poly1305 suite so aead is set
- Use the DTLS conn layer (which manages suite lifecycle) rather than raw suites
- Re-establish the session if initialization never happens
When it happens
Trigger: Calling Decrypt (directly or via record-layer processing) before the handshake has set c.aead — reading records on a session whose key material was never derived, or concurrent decrypts during handshake.
Common situations: Receiving encrypted flight data before handshake completes; using the cipher suite object standalone without running handshake; handshake aborted partway leaving aead unset; negotiated suite mismatch so the concrete type never loads.
Understand the failure class
Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.
Related errors
AI-assisted analysis of AlexxIT/go2rtc@c245815e75 (2026-09-07).
Data as JSON: /api/errors/01079bbc1339ac51.
Report an issue: GitHub.
Appendix: source
Thrown at pkg/tutk/dtls/cipher.go:209
return err
}
c.aead.Store(aead)
return nil
}
func (c *TLSEcdhePskWithChacha20Poly1305Sha256) Encrypt(pkt *recordlayer.RecordLayer, raw []byte) ([]byte, error) {
aead, ok := c.aead.Load().(*ChaCha20Poly1305Cipher)
if !ok {
return nil, fmt.Errorf("%w: unable to encrypt", errCipherSuiteNotInit)
}
return aead.Encrypt(pkt, raw)
}
func (c *TLSEcdhePskWithChacha20Poly1305Sha256) Decrypt(h recordlayer.Header, raw []byte) ([]byte, error) {
aead, ok := c.aead.Load().(*ChaCha20Poly1305Cipher)
if !ok {
return nil, fmt.Errorf("%w: unable to decrypt", errCipherSuiteNotInit)
}
return aead.Decrypt(h, raw)
}
func CustomCipherSuites() []dtls.CipherSuite {
return []dtls.CipherSuite{
NewTLSEcdhePskWithChacha20Poly1305Sha256(),
}
}
View on GitHub (pinned to c245815e75)