You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

EPSON ESC/VP.net协议未公开认证算法:如何计算响应哈希?

问题:EPSON投影仪ESC/VP.net未公开认证哈希算法求解

我捕获了EPSON投影仪管理工具的多组认证流量,同时找到了ESC/VP.net协议的规范文档。规范文档5.6.3节说明明文密码认证的帧结构,但我的投影仪返回状态码0x45拒绝明文认证。流量捕获显示工具使用了未公开的认证流程:

从时间戳4.780750至4.795570的第5到第8个报文分析出以下流程:

  1. 发送常规密码帧,包含一个头部:
    • 头部标识符:0x01 [Password]
    • 头部属性值:0x03
    • 头部信息:[空,16字节0x00]
  2. 收到状态码0x41(需要密码)的响应,包含一个头部:
    • 头部标识符:0x01 [Password]
    • 头部属性值:0x06
    • 头部信息:6a0c6fb3b41349da6b6432397c842779(疑似挑战值)
  3. 发送常规密码帧,包含两个头部:
    第一个头部:
    • 头部标识符: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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.11 12:15:54