如何解密Discord接收的xsalsa20_poly1305加密语音数据?
解密Discord xsalsa20_poly1305加密语音数据失败问题排查
我正尝试解密Discord接收的采用xsalsa20_poly1305加密模式的语音数据,目标是录制音频并与AI聊天,但解密一直失败,求帮忙排查问题。
我的代码
async def record_audio(udp_socket, ssrc, secret_key): box = nacl.secret.SecretBox(bytes(secret_key)) # TODO: Fix decryption xsalsa20_poly1305 print("Listening for audio data...") try: response, _ = udp_socket.recvfrom(74) print(f"Received response: {response}") # Process the response... except socket.timeout: print("IP discovery timeout") except Exception as e: print(f"Unexpected error during IP discovery: {e}") return None, None while True: print("Waiting for audio data...") try: ready, _, _ = select.select([udp_socket], [], [], 5.0) if udp_socket in ready: data, addr = udp_socket.recvfrom(65536) # Adjust buffer size as necessary print(f"Received {len(data)} bytes from {addr}: {data.hex()}") if len(data) > 12: # Extract the RTP header header = data[:12] # Construct the nonce nonce = header + b'\x00' * 12 print(f"Nonce: {len(nonce)} bytes") # Get the encrypted audio data encrypted = data[12:] print(f"Encrypted audio data: {len(encrypted)} bytes") #The rest of the data is the encrypted audio data (Should be 48 - 24 = 24 bytes) #nonce = data[:12] #print(f"Nonce: {nonce}") #if len(nonce) < 12: # nonce.ljust(24, b'\x00') #remaining 12 bytes can be zeros or another fixed pattern #nonce = nonce_part + bytes(12) #copy the RTP header to get the nonce #nonce = bytearray(24) #nonce[:12] = data[:12]#data[:12] #get the encrypted audio data #encrypted = data[12:] print(f"Encrypted audio data: {bytes(encrypted)}") try: audio_data = box.decrypt(bytes(data), bytes(nonce)) print("Received audio data") except Exception as e: print(f"Decryption error: {e}") except Exception as e: print(f"Error receiving audio data: {e}") break
编辑说明
密钥直接取自Discord返回的opcode 4对象。
输出日志
Waiting for audio data... Received 48 bytes from ('66.22.243.22', 50023): 81c9000700013adfaf6439133ca81bfcd2b35eb743f2a4af0165e3cf0517d8efee5dae36ec6653c88a2d625064af33d6 Nonce: 24 bytes Nonce: b'\x81\xc9\x00\x07\x00\x01:\xdf\xafd9\x13\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' Encrypted audio data: 36 bytes Encrypted audio data: b'<\xa8\x1b\xfc\xd2\xb3^\xb7C\xf2\xa4\xaf\x01e\xe3\xcf\x05\x17\xd8\xef\xee]\xae6\xecfS\xc8\x8a-bPd\xaf3\xd6' Decryption error: Decryption failed. Ciphertext failed verification
测试数据
{'op': 4, 'd': {'video_codec': 'H264', 'secure_frames_version': 0, 'secret_key': [20, 115, 239, 10, 206, 186, 11, 248, 52, 47, 193, 69, 170, 89, 146, 187, 215, 181, 4, 177, 173, 132, 50, 212, 141, 194, 52, 217, 219, 17, 111, 5], 'mode': 'xsalsa20_poly1305', 'media_session_id': '77d90ef5c4aa124c0dcd6d39bbe88f9f', 'audio_codec': 'opus'}} Udp socket: <socket.socket fd=604, family=2, type=2, proto=0, laddr=('0.0.0.0', 56866)> SSRC: 112825 Secret key: [20, 115, 239, 10, 206, 186, 11, 248, 52, 47, 193, 69, 170, 89, 146, 187, 215, 181, 4, 177, 173, 132, 50, 212, 141, 194, 52, 217, 219, 17, 111, 5] Received data: b'\x81\xc9\x00\x07\x00\x00gIZ.Y\xaf\xf8\x94\xb4a}?gm"\xc6R\x02\\\x13\xaf>@\xf0\xe8\xca\xd0\x90\xf3\x16\x89h\x14\x81s\xa0\x00\xf3$v\x99|'
数据字节数组形式
Data: [129, 201, 0, 7, 0, 1, 87, 149, 132, 179, 156, 19, 161, 42, 79, 112, 160, 142, 72, 43, 68, 43, 225, 201, 66, 97, 38, 88, 120, 123, 192, 102, 18, 163, 126, 210, 96, 21, 113, 212, 66, 63, 102, 7, 123, 24, 141, 1] RTP header: [129, 201, 0, 7, 0, 1, 87, 149, 132, 179, 156, 19] Nonce: [129, 201, 0, 7, 0, 1, 87, 149, 132, 179, 156, 19, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] Voice data: [161, 42, 79, 112, 160, 142, 72, 43, 68, 43, 225, 201, 66, 97, 38, 88, 120, 123, 192, 102, 18, 163, 126, 210, 96, 21, 113, 212, 66, 63, 102, 7, 123, 24, 141, 1]
问题排查与修复建议
核心错误:解密参数传错
你调用box.decrypt(bytes(data), bytes(nonce))时,第一个参数传了完整的UDP数据包(包含12字节RTP头),但Discord的密文是RTP头之后的部分,也就是你已经提取的encrypted变量。正确的调用应该是:audio_data = box.decrypt(encrypted, nonce)因为RTP头仅用于生成nonce,不属于密文内容,传入完整数据包会导致校验失败。
Nonce构造验证
Discord xsalsa20_poly1305的nonce规则是RTP头(12字节) + 全0字节(12字节),你当前的构造方式是正确的,无需修改。密钥与密文长度验证
- 你从opcode4获取的32字节密钥,转成bytes后初始化SecretBox是正确的,符合xsalsa20_poly1305的密钥要求。
- 密文长度应该包含16字节的Poly1305校验标签,解密后得到的音频数据长度应为
len(encrypted) - 16,可以在解密后验证这一点,确认是否符合Opus帧的长度。
修改后重新测试,应该能解决解密校验失败的问题。
内容的提问来源于stack exchange,提问作者Peter Till
相关产品推荐
相关产品推荐

