{"record":{"id":"3700c315673d178e","repo":"AlexxIT/go2rtc","slug":"w-unable-to-encrypt","errorCode":null,"errorMessage":"%w: unable to encrypt","messagePattern":"%w: unable to encrypt","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"pkg/tutk/dtls/cipher.go","lineNumber":201,"sourceCode":"\t\t)\n\t} else {\n\t\taead, err = NewChaCha20Poly1305Cipher(\n\t\t\tkeys.ServerWriteKey, keys.ServerWriteIV,\n\t\t\tkeys.ClientWriteKey, keys.ClientWriteIV,\n\t\t)\n\t}\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tc.aead.Store(aead)\n\treturn nil\n}\n\nfunc (c *TLSEcdhePskWithChacha20Poly1305Sha256) Encrypt(pkt *recordlayer.RecordLayer, raw []byte) ([]byte, error) {\n\taead, ok := c.aead.Load().(*ChaCha20Poly1305Cipher)\n\tif !ok {\n\t\treturn nil, fmt.Errorf(\"%w: unable to encrypt\", errCipherSuiteNotInit)\n\t}\n\treturn aead.Encrypt(pkt, raw)\n}\n\nfunc (c *TLSEcdhePskWithChacha20Poly1305Sha256) Decrypt(h recordlayer.Header, raw []byte) ([]byte, error) {\n\taead, ok := c.aead.Load().(*ChaCha20Poly1305Cipher)\n\tif !ok {\n\t\treturn nil, fmt.Errorf(\"%w: unable to decrypt\", errCipherSuiteNotInit)\n\t}\n\treturn aead.Decrypt(h, raw)\n}\n\nfunc CustomCipherSuites() []dtls.CipherSuite {\n\treturn []dtls.CipherSuite{\n\t\tNewTLSEcdhePskWithChacha20Poly1305Sha256(),\n\t}\n}\n","sourceCodeStart":183,"sourceCodeEnd":219,"githubUrl":"https://github.com/AlexxIT/go2rtc/blob/c245815e75e2a5fd60b4290f12bfc04e55a984d3/pkg/tutk/dtls/cipher.go#L183-L219","documentation":"The ChaCha20-Poly1305 cipher suite stores its AEAD instance atomically and it is only initialized after the handshake computes keys. Calling Encrypt before that initialization completes means the suite is not ready, so the library returns errCipherSuiteNotInit wrapped with 'unable to encrypt' rather than using a nil cipher.","triggerScenarios":"Calling Encrypt on TLSEcdhePskWithChacha20Poly1305Sha256 before the handshake populates c.aead — e.g. application data sent immediately after Dial before key derivation finished, or concurrent use racing the handshake.","commonSituations":"Application writes racing DTLS handshake completion; attempting to send data on a half-open session; missing/broken handshake trigger so aead is never set; misuse of the cipher suite directly instead of through the DTLS conn.","solutions":["Wait for the DTLS handshake to complete (Connect/Handshake result) before writing application data.","Serialize sends behind handshake completion (mutex or done-channel) to avoid racing key derivation.","Verify the cipher suite was actually negotiated and initialized — check CustomCipherSuites() includes the ChaCha20 suite and the peer supports it.","Re-create the session if the error persists; the suite instance may be permanently uninitialized."],"exampleFix":"// before\nconn.Write(data) // may race handshake\n// after\nselect {\ncase <-conn.HandshakeDone():\n\tconn.Write(data)\ncase <-time.After(5 * time.Second):\n\treturn errors.New(\"dtls handshake not completed\")\n}","handlingStrategy":"try-catch","validationCode":"// Gate sends on handshake completion\nselect {\ncase <-conn.HandshakeDone():\n\t// ok to encrypt now\ncase <-time.After(5 * time.Second):\n\treturn errors.New(\"handshake incomplete; cannot encrypt\")\n}","typeGuard":null,"tryCatchPattern":"out, err := suite.Encrypt(pkt, raw)\nif err != nil {\n\tif errors.Is(err, errCipherSuiteNotInit) {\n\t\t<-handshakeDone // wait then retry once\n\t\treturn suite.Encrypt(pkt, raw)\n\t}\n\treturn err\n}","preventionTips":["Never send application data before the DTLS handshake completes","Serialize writes behind a done-channel or mutex tied to handshake state","Confirm the ChaCha20-Poly1305 suite is negotiated on both ends","Recreate the session if the suite stays uninitialized"],"tags":["dtls","crypto","tls","initialization"],"backgroundTag":"invalid-state-transition","analyzedSha":"c245815e75e2a5fd60b4290f12bfc04e55a984d3","analyzedAt":"2026-09-07T11:47:02.965Z","contentChangedAt":"2026-09-07T11:47:02.965Z","schemaVersion":2},"datasetVersion":"2026-09-15T23:17:13.987Z"}