EPSON ESC/VP.net协议未公开认证算法:如何计算响应哈希?
问题:EPSON投影仪ESC/VP.net未公开认证哈希算法求解
我捕获了EPSON投影仪管理工具的多组认证流量,同时找到了ESC/VP.net协议的规范文档。规范文档5.6.3节说明明文密码认证的帧结构,但我的投影仪返回状态码0x45拒绝明文认证。流量捕获显示工具使用了未公开的认证流程:
从时间戳4.780750至4.795570的第5到第8个报文分析出以下流程:
- 发送常规密码帧,包含一个头部:
- 头部标识符:0x01 [Password]
- 头部属性值:0x03
- 头部信息:[空,16字节0x00]
- 收到状态码0x41(需要密码)的响应,包含一个头部:
- 头部标识符:0x01 [Password]
- 头部属性值:0x06
- 头部信息:
6a0c6fb3b41349da6b6432397c842779(疑似挑战值)
- 发送常规密码帧,包含两个头部:
第一个头部:- 头部标识符:0x01 [Password]
- 头部属性值:0x04
- 头部信息:
8470e087ed4ff59552780d1b39f669f6(疑似第一组计算哈希)
第二个头部: - 头部标识符:0x01 [Password]
- 头部属性值:0x05
- 头部信息:
ad1d9eea1aa9d4a5348e3fe9be984651(疑似第二组计算哈希)
我推测这两个头部的哈希组合为32字节,由挑战值和密码计算得出。考虑到32字节长度,我猜测是SHA2-256,但测试发现AI工具均指向MD5。
认证前已交换投影仪名称"EB86867F-boxcafe"、IM类型0x38及ESC/VP21版本0x21(Ver1.0),这些值可能也参与计算。
核心问题:已知上述挑战值和密码"admin",但最新规范未记录该算法,如何计算正确的哈希值?
附Python测试代码
import hashlib import hmac # known sample data challenge = "6a0c6fb3b41349da6b6432397c842779" password = "admin" # target hashes to match hash4 = "8470e087ed4ff59552780d1b39f669f6" hash5 = "ad1d9eea1aa9d4a5348e3fe9be984651" combined_hash = hash4 + hash5 combined_hash2 = hash5 + hash4 # data as bytes challenge_bytes = bytes.fromhex(challenge) password_bytes = password.encode('utf-8') password_bytes16 = password_bytes.ljust(16, b'\x00') # target hashes print("hash4: ", hash4) print("hash5: ", hash5) print("combined_hash: ", combined_hash) print("combined_hash2: ", combined_hash2) password_md5 = hashlib.md5(password_bytes).hexdigest() print("Password MD5: ", password_md5) password_md516 = hashlib.md5(password_bytes16).hexdigest() print("Password16 MD5: ", password_md516) challenge_md5 = hashlib.md5(challenge_bytes).hexdigest() print("Challenge MD5: ", challenge_md5) password_challenge = hashlib.sha256(password_bytes + challenge_bytes).hexdigest() print("Password + Challenge SHA256: ", password_challenge) challenge_password = hashlib.sha256(challenge_bytes + password_bytes).hexdigest() print("Challenge + Password SHA256: ", challenge_password) sha256_password = hashlib.sha256(password_bytes).hexdigest() print("Password SHA256: ", sha256_password) sha256_challenge = hashlib.sha256(challenge_bytes).hexdigest() print("Challenge SHA256: ", sha256_challenge) sha256_password_challenge = hashlib.sha256(password_bytes + challenge_bytes).hexdigest() print("SHA256(Password + Challenge): ", sha256_password_challenge) sha256_challenge_password = hashlib.sha256(challenge_bytes + password_bytes).hexdigest() print("SHA256(Challenge + Password): ", sha256_challenge_password) hmac_digest = hmac.new(password_bytes, challenge_bytes, hashlib.sha256).hexdigest() print("HMAC-SHA256: ", hmac_digest)
附Node.js实现代码
const Net = require('net'); const crypto = require('crypto'); const port = 3629; const host = '10.101.10.38'; const password = 'admin'; const helloMsgBuffer = Buffer.alloc(34); helloMsgBuffer.write('ESC/VP.net', 0, 'utf-8'); // Signature helloMsgBuffer.writeUInt8(0x20, 10); // Protocol version (0x20 = 2.0) helloMsgBuffer.writeUInt8(0x03, 11); // Message type (0x03 = CONNECTION) helloMsgBuffer.writeUInt16BE(0x0000, 12); // Reserved (must be 0) helloMsgBuffer.writeUInt8(0x00, 14); // Request helloMsgBuffer.writeUInt8(0x01, 15); // Header number (1 additional header) helloMsgBuffer.writeUInt8(0x01, 16); // Header identifier (0x01 = Password) helloMsgBuffer.writeUInt8(0x03, 17); // Header attributes (0x03 = Request MD5 Hash) helloMsgBuffer.write(''.padEnd(16, '\0'), 18, 'utf-8'); // Request data (empty password) const client = new Net.Socket(); client.connect({ port: port, host: host }, function() { console.log('TCP connection established.'); console.log(`Sending data: ${helloMsgBuffer.toString('hex')}`); client.write(helloMsgBuffer); }); client.on('data', function(chunk) { console.log(`Data received (hex): ${chunk.toString('hex')}`); console.log(`Data received (ascii): ${chunk.toString('ascii')}`); // Parse ESC/VP.net response if (chunk.length >= 16 && chunk.toString('ascii', 0, 10) === 'ESC/VP.net') { const status = chunk.readUInt8(14); const statusMap = { 0x20: 'OK', 0x40: 'Bad Request', 0x41: 'Unauthorized (Password Required)', 0x43: 'Forbidden (Wrong Password)', 0x45: 'Request Not Allowed', 0x53: 'Service Unavailable (Busy)', 0x55: 'Version Not Supported' }; console.log(`Response status: ${statusMap[status] || 'Unknown'} (0x${status.toString(16)})`); if (status in statusMap && status == 0x41) { // Calculate MD5 hash with responded salt of the password const salt = chunk.slice(18, 34); console.log(`Received salt from server: ${salt.toString('hex')}`); const hash = crypto.createHash('md5').update(salt + password).digest(); const authMsgBuffer = Buffer.alloc(52); authMsgBuffer.write('ESC/VP.net', 0, 'utf-8'); // Signature authMsgBuffer.writeUInt8(0x20, 10); // Protocol version (0x20 = 2.0) authMsgBuffer.writeUInt8(0x03, 11); // Message type (0x03 = CONNECTION) authMsgBuffer.writeUInt16BE(0x0000, 12); // Reserved (must be 0) authMsgBuffer.writeUInt8(0x00, 14); // Request authMsgBuffer.writeUInt8(0x02, 15); // Header number (2 additional headers) authMsgBuffer.writeUInt8(0x01, 16); // First header identifier (0x01 = Password) authMsgBuffer.writeUInt8(0x04, 17); // Header attributes (0x04 = MD5 Hash) hash.copy(authMsgBuffer, 18); // Request data (MD5 hash of salt + password) -> 16 bytes authMsgBuffer.writeUInt8(0x01, 34); // Second header identifier (0x01 = Password) authMsgBuffer.writeUInt8(0x05, 35); // Header attributes (0x05 = MD5 Hash) salt.copy(authMsgBuffer, 36); // Request data (Salt) -> 16 bytes console.log(`Sending authentication data to the server: ${authMsgBuffer.toString('hex')}`); client.write(authMsgBuffer); } } }); client.on('end', function() { console.log('Requested an end to the TCP connection'); }); client.on('error', function(err) { console.error(`Error: ${err.message}`); });
测试环境
本次捕获和测试基于EPSON EB-L260F投影仪,固件版本3X011864JFWWV116,测试时投影仪处于关机状态(仅接受有限通信,与本问题无关)。
内容的提问来源于stack exchange,提问作者Caspar
相关产品推荐
相关产品推荐

