From 9b22bd21e40fca76fafbd73b56806122ba921d00 Mon Sep 17 00:00:00 2001 From: LordOfPolls Date: Mon, 15 Dec 2025 21:03:26 +0000 Subject: [PATCH 1/3] fix: actually raise error on encryption fail --- interactions/api/voice/voice_gateway.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/interactions/api/voice/voice_gateway.py b/interactions/api/voice/voice_gateway.py index c20c0560c..5f8735ded 100644 --- a/interactions/api/voice/voice_gateway.py +++ b/interactions/api/voice/voice_gateway.py @@ -197,7 +197,12 @@ async def dispatch_opcode(self, data, op) -> None: self.logger.info(f"Voice connection established; using {data['mode']}") self.selected_mode = data["mode"] self.secret = data["secret_key"] - self.encryptor = Encryption(self.secret) + try: + self.encryptor = Encryption(self.secret) + self.logger.debug(f"Encryption initialized successfully for mode {data['mode']}") + except Exception as e: + self.logger.error(f"Failed to initialize encryption: {e}", exc_info=True) + raise self.ready.set() if self.cond: with self.cond: From ad94a2b6ee2f78f10e229d95678d82cc43e84897 Mon Sep 17 00:00:00 2001 From: LordOfPolls Date: Mon, 15 Dec 2025 21:06:10 +0000 Subject: [PATCH 2/3] feat: add support for new voice encryption reqs pynacl intentionally doesnt support them, so adding cryptography dep --- interactions/api/voice/encryption.py | 73 +++++++++++++++++++++++++--- pyproject.toml | 1 + 2 files changed, 68 insertions(+), 6 deletions(-) diff --git a/interactions/api/voice/encryption.py b/interactions/api/voice/encryption.py index 4c9310abf..240887bed 100644 --- a/interactions/api/voice/encryption.py +++ b/interactions/api/voice/encryption.py @@ -1,6 +1,7 @@ import struct +import secrets -__all__ = ("Encryption",) +__all__ = ("Encryption", "Decryption") from abc import ABC, abstractmethod @@ -11,21 +12,35 @@ except ImportError: nacl_imported = False +try: + from cryptography.hazmat.primitives.ciphers.aead import ChaCha20Poly1305, AESGCM + + cryptography_imported = True +except ImportError: + cryptography_imported = False + class Crypt(ABC): SUPPORTED = ( + # todo: need deprecating "xsalsa20_poly1305_lite", "xsalsa20_poly1305_suffix", "xsalsa20_poly1305", + + + "aead_xchacha20_poly1305_rtpsize", + "aead_aes256_gcm_rtpsize" ) def __init__(self, secret_key) -> None: - if not nacl_imported: + if not nacl_imported or not cryptography_imported: raise RuntimeError("Please install interactions[voice] to use voice components.") self.box: secret.SecretBox = secret.SecretBox(bytes(secret_key)) - self._xsalsa20_poly1305_lite_nonce: int = 0 + self.xchacha20 = ChaCha20Poly1305(bytes(secret_key)) + self.aes_gcm = AESGCM(bytes(secret_key)) + @abstractmethod def xsalsa20_poly1305_lite(self, header: bytes, data) -> bytes: raise NotImplementedError @@ -38,6 +53,14 @@ def xsalsa20_poly1305_suffix(self, header: bytes, data) -> bytes: def xsalsa20_poly1305(self, header: bytes, data) -> bytes: raise NotImplementedError + @abstractmethod + def aead_xchacha20_poly1305_rtpsize(self, header: bytes, data) -> bytes: + raise NotImplementedError + + @abstractmethod + def aead_aes256_gcm_rtpsize(self, header: bytes, data) -> bytes: + raise NotImplementedError + class Encryption(Crypt): def encrypt(self, mode: str, header: bytes, data) -> bytes: @@ -48,6 +71,10 @@ def encrypt(self, mode: str, header: bytes, data) -> bytes: return self.xsalsa20_poly1305_suffix(header, data) case "xsalsa20_poly1305": return self.xsalsa20_poly1305(header, data) + case "aead_xchacha20_poly1305_rtpsize": + return self.aead_xchacha20_poly1305_rtpsize(header, data) + case "aead_aes256_gcm_rtpsize": + return self.aead_aes256_gcm_rtpsize(header, data) case _: raise RuntimeError(f"Unsupported encryption type requested: {mode}") @@ -56,7 +83,7 @@ def xsalsa20_poly1305_lite(self, header: bytes, data) -> bytes: nonce[:4] = struct.pack(">I", self._xsalsa20_poly1305_lite_nonce) self._xsalsa20_poly1305_lite_nonce += 1 - if self._xsalsa20_poly1305_lite_nonce > 2**32: + if self._xsalsa20_poly1305_lite_nonce > 2 ** 32: self._xsalsa20_poly1305_lite_nonce = 0 return header + self.box.encrypt(bytes(data), bytes(nonce)).ciphertext + nonce[:4] @@ -71,6 +98,22 @@ def xsalsa20_poly1305(self, header: bytes, data) -> bytes: return header + self.box.encrypt(bytes(data), bytes(nonce)).ciphertext + def aead_xchacha20_poly1305_rtpsize(self, header: bytes, data) -> bytes: + nonce_suffix = secrets.token_bytes(4) + nonce = bytearray(24) + nonce[:4] = nonce_suffix + ciphertext = self.xchacha20.encrypt(bytes(nonce), bytes(data), header) + + return header + ciphertext + nonce_suffix + + def aead_aes256_gcm_rtpsize(self, header: bytes, data) -> bytes: + nonce_suffix = secrets.token_bytes(4) + nonce = bytearray(12) + nonce[:4] = nonce_suffix + ciphertext = self.aes_gcm.encrypt(bytes(nonce), bytes(data), header) + + return header + ciphertext + nonce_suffix + class Decryption(Crypt): def decrypt(self, mode: str, header: bytes, data) -> bytes: @@ -81,6 +124,10 @@ def decrypt(self, mode: str, header: bytes, data) -> bytes: return self.xsalsa20_poly1305_suffix(header, data) case "xsalsa20_poly1305": return self.xsalsa20_poly1305(header, data) + case "aead_xchacha20_poly1305_rtpsize": + return self.aead_xchacha20_poly1305_rtpsize(header, data) + case "aead_aes256_gcm_rtpsize": + return self.aead_aes256_gcm_rtpsize(header, data) case _: raise RuntimeError(f"Unsupported decryption type requested: {mode}") @@ -98,11 +145,25 @@ def xsalsa20_poly1305_lite(self, header: bytes, data) -> bytes: def xsalsa20_poly1305_suffix(self, header: bytes, data) -> bytes: nonce = data[-24:] - return self.box.decrypt(bytes(data[:-24]), bytes(nonce)) def xsalsa20_poly1305(self, header: bytes, data) -> bytes: nonce = bytearray(24) nonce[:12] = header - return self.box.decrypt(bytes(data), bytes(nonce)) + + def aead_xchacha20_poly1305_rtpsize(self, header: bytes, data) -> bytes: + nonce_suffix = data[-4:] + ciphertext = data[:-4] + nonce = bytearray(24) + nonce[:4] = nonce_suffix + + return self.xchacha20.decrypt(bytes(nonce), bytes(ciphertext), header) + + def aead_aes256_gcm_rtpsize(self, header: bytes, data) -> bytes: + nonce_suffix = data[-4:] + ciphertext = data[:-4] + nonce = bytearray(12) + nonce[:4] = nonce_suffix + + return self.aes_gcm.decrypt(bytes(nonce), bytes(ciphertext), header) \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml index 381487001..c2559695c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -36,6 +36,7 @@ pre-commit = { version = "*", optional = true } [tool.poetry.group.voice.dependencies] PyNaCl = "^1.5.0,<1.6" +cryptography = "46.0.3" [tool.poetry.group.speedup.dependencies] aiodns = "*" From 910b3719352fa733d314e2cbe9506fd42218ed4e Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 15 Dec 2025 21:11:08 +0000 Subject: [PATCH 3/3] ci: correct from checks. --- interactions/api/voice/encryption.py | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/interactions/api/voice/encryption.py b/interactions/api/voice/encryption.py index 240887bed..f2ae67276 100644 --- a/interactions/api/voice/encryption.py +++ b/interactions/api/voice/encryption.py @@ -1,7 +1,7 @@ import struct import secrets -__all__ = ("Encryption", "Decryption") +__all__ = ("Decryption", "Encryption") from abc import ABC, abstractmethod @@ -26,10 +26,8 @@ class Crypt(ABC): "xsalsa20_poly1305_lite", "xsalsa20_poly1305_suffix", "xsalsa20_poly1305", - - "aead_xchacha20_poly1305_rtpsize", - "aead_aes256_gcm_rtpsize" + "aead_aes256_gcm_rtpsize", ) def __init__(self, secret_key) -> None: @@ -83,7 +81,7 @@ def xsalsa20_poly1305_lite(self, header: bytes, data) -> bytes: nonce[:4] = struct.pack(">I", self._xsalsa20_poly1305_lite_nonce) self._xsalsa20_poly1305_lite_nonce += 1 - if self._xsalsa20_poly1305_lite_nonce > 2 ** 32: + if self._xsalsa20_poly1305_lite_nonce > 2**32: self._xsalsa20_poly1305_lite_nonce = 0 return header + self.box.encrypt(bytes(data), bytes(nonce)).ciphertext + nonce[:4] @@ -166,4 +164,4 @@ def aead_aes256_gcm_rtpsize(self, header: bytes, data) -> bytes: nonce = bytearray(12) nonce[:4] = nonce_suffix - return self.aes_gcm.decrypt(bytes(nonce), bytes(ciphertext), header) \ No newline at end of file + return self.aes_gcm.decrypt(bytes(nonce), bytes(ciphertext), header)