flask_socketio+pyautogui实现的具有加密传输功能的极简远程桌面

flask_socketio+pyautogui实现的具有加密传输功能的极简远程桌面:

python 复制代码
import base64
import pyautogui
from flask import Flask
import time
from flask_socketio import SocketIO, emit
from io import BytesIO
import pyperclip
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad,unpad
from Crypto.Random import get_random_bytes


# 32字节AES密钥
global AES_KEY
AES_KEY='11111111112222222222333333333344'

# 加密函数
def aes_encrypt(plain_text, key):
    """AES加密"""
    key_bytes=key.encode('utf-8')
    iv=get_random_bytes(AES.block_size)
    # 初始化AES加密器
    
    cipher = AES.new(key_bytes, AES.MODE_CBC, iv)
    # 对明文进行填充
    plain_text_bytes=plain_text.encode('utf-8')
    padded_bytes=pad(plain_text_bytes,AES.block_size)
    cipher_text_bytes=cipher.encrypt(padded_bytes)
    # 合并IV和密文
    combined_bytes=iv+cipher_text_bytes
    encrypted_base64=base64.b64encode(combined_bytes).decode('utf-8')
    return encrypted_base64

# 解密函数
def aes_decrypt(encrypted_base64, key):
    """AES解密"""
    key_bytes=key.encode('utf-8')
    # 从base64编码中提取IV和密文
    combined_bytes=base64.b64decode(encrypted_base64)
    iv=combined_bytes[:AES.block_size]
    cipher_text_bytes=combined_bytes[AES.block_size:]
    # 初始化AES解密器
    cipher = AES.new(key_bytes, AES.MODE_CBC, iv)
    # 解密密文
    padded_bytes=cipher.decrypt(cipher_text_bytes)
    # 移除填充
    plain_text_bytes=unpad(padded_bytes,AES.block_size)
    plain_text=plain_text_bytes.decode('utf-8')
    return plain_text



# original_text='Hello, World!'
# # 加密原始文本
# encrypted_text=aes_encrypt(original_text,AES_KEY)
# print('加密后的文本:', encrypted_text)

# # 解密加密后的文本
# decrypted_text=aes_decrypt(encrypted_text,AES_KEY)
# print('解密后的文本:', decrypted_text)


app=Flask(__name__)
app.config['SECRET_KEY'] = 'secret!'


socketio = SocketIO(app)

@socketio.on('connect')
def handle_connect():
    """客户端连接时触发"""
    print('客户端已连接')
    emit('server_response', {'data': '连接成功'})
    # 连接成功后,获取最新的远程屏幕
    #client_get_screen()

@socketio.on('client_send_keyboard_input')
def client_send_keyboard_input(json):
    """处理来自客户端的消息"""
    print('收到客户端消息:', json)
    # 模拟键盘输入
    client_input=json['data']
    print('客户端输入:', client_input)
    # typewrite只会输入字母,其他字符会被忽略
    pyautogui.typewrite(client_input)
    client_get_screen()


@socketio.on('client_send_paste_key')
def client_send_paste_key(json):
    """处理来自客户端的粘贴消息"""
    print('收到客户端粘贴消息:', json)
    # 模拟粘贴操作
    client_paste_text=json['data']
    print('客户端粘贴文本:', client_paste_text)
    # 先将文本复制到剪贴板
    pyperclip.copy(client_paste_text)
    # 模拟粘贴键
    pyautogui.hotkey('ctrl', 'v')
    client_get_screen()
    

@socketio.on('client_get_screen')
def client_get_screen():
    """处理获取远程屏幕请求"""
    print('收到获取远程屏幕请求')    
    # 循环刷新3次
    for i in range(5):
        # 延迟0.2秒
        time.sleep(0.2)
        screen = pyautogui.screenshot()
        # 将屏幕图片转换为base64编码
        buffered = BytesIO()
        screen.save(buffered, format="PNG")
        # 得到图片的base64编码
        screen_base64 = base64.b64encode(buffered.getvalue()).decode('utf-8')
        #print('原始图片base64编码:', screen_base64[:100])
        # 将图片base64编码进行aes加密
        global AES_KEY
        encrypted_screen_base64=aes_encrypt(screen_base64,AES_KEY)
        # 发送aes加密后的屏幕图片给所有客户端
        emit('remote_screen_update', {'data': encrypted_screen_base64},broadcast=False)
        #emit('remote_screen_update', {'data': screen_base64},broadcast=False)

@socketio.on('client_click_screen')
def handle_client_click(json):
    """处理来自客户端的点击事件"""
    print('收到客户端点击事件:', json)
    x, y = json['x'], json['y']
    # 映射到主机屏幕坐标
    screen_width, screen_height = pyautogui.size()
    x, y = int(x * screen_width), int(y * screen_height)
    pyautogui.moveTo(x, y)
    pyautogui.click(x, y)
    # emit('server_response', {'data': f'已点击屏幕位置: ({x}, {y})'},broadcast=False)
    # 点击鼠标事件后,获取最新的远程屏幕
    client_get_screen()

@socketio.on('client_dbclick_screen')
def handle_client_dbclick(json):
    """处理来自客户端的双击事件"""
    print('收到客户端双击事件:', json)
    x, y = json['x'], json['y']
    # 映射到主机屏幕坐标
    screen_width, screen_height = pyautogui.size()
    x, y = int(x * screen_width), int(y * screen_height)
    pyautogui.doubleClick(x, y)
    # 双击鼠标事件后,获取最新的远程屏幕
    client_get_screen()

@socketio.on('client_send_enter_key')
def handle_client_send_enter_key():
    """处理来自客户端的Enter键事件"""
    print('收到客户端Enter键事件')
    # 模拟键盘输入Enter键
    pyautogui.press('enter')
    client_get_screen()



@socketio.on('client_send_delete_key')
def handle_client_send_delete_key():
    """处理来自客户端的Delete键事件"""
    print('收到客户端Delete键事件')
    # 模拟键盘输入Delete键
    pyautogui.press('backspace')
    client_get_screen()

@socketio.on('client_send_scroll_up_key')
def handle_client_send_scroll_up_key():
    """处理来自客户端的上翻键事件"""
    print('收到客户端上翻键事件')
    # 模拟键盘输入上翻键
    pyautogui.scroll(100)
    client_get_screen()

@socketio.on('client_send_scroll_down_key')
def handle_client_send_scroll_down_key():
    """处理来自客户端的下翻键事件"""
    print('收到客户端下翻键事件')
    # 模拟键盘输入下翻键
    pyautogui.scroll(-100)
    client_get_screen()

@app.route('/')
def index():
    """渲染主页"""
    return '''
        <!DOCTYPE html>
        <html>
        <head>
            <meta lang="zh-CN">
            <meta charset="UTF-8">
            <meta name="viewport" content="width=device-width, initial-scale=1.0">
            <title>远程桌面</title>
            <style>
                body{
                    background-color:black;
                }
                #telescreen {
                    position: relative;
                    background-color:skyblue;
                    box-shadow: 0 0 1em rgba(0, 0, 0, 0.5);
                    border:1em solid gray;
                    border-radius: 1em;
                    resize: both;
                    overflow: auto;
                }
                #remote_screen {
                    display: none;
                    position: relative;
                    top: 0;
                    left: 0;
                    width: 100%;
                }
                #control_panel{
                    background-color: gray;
                    padding: 1em;
                    border-radius: 1em;                    
                }
                button,input
                {
                    margin: 0.5em;
                    padding: 0.5em 1em;
                    border-radius: 0.5em;
                    border: 1px solid #666;
                }
                input{
                    width: 80%;
                }
            </style>
            <script src="https://cdnjs.cloudflare.com/ajax/libs/socket.io/4.0.1/socket.io.js"></script>
            <script src="https://cdnjs.cloudflare.com/ajax/libs/crypto-js/4.1.1/crypto-js.min.js"></script>
            <script type="text/javascript">

                // 解密AES函数
                function decryptAES(ciphertext, key, options = {}) {
                    try {
                        // 设置默认选项
                        const mode = options.mode || 'CBC';
                        const padding = options.padding || 'Pkcs7';
                        
                        // 准备解密参数
                        const decryptOptions = {
                            padding: CryptoJS.pad[padding]
                        };
                        
                        // 获取对应的加密模式
                        const cryptoMode = CryptoJS.mode[mode];
                        
                        // 如果指定了IV,使用指定的IV;否则尝试从密文中提取IV
                        if (options.iv) {
                            decryptOptions.iv = CryptoJS.enc.Utf8.parse(options.iv);
                        } else if (mode === 'CBC' || mode === 'CFB' || mode === 'OFB') {
                            // 注意:这部分逻辑取决于加密时IV的存储方式
                            // 有些实现会将IV附加在密文前面一起进行base64编码
                            // 这里提供一个常见的处理方式作为示例
                            const rawData = CryptoJS.enc.Base64.parse(ciphertext);
                            const iv = CryptoJS.lib.WordArray.create(rawData.words.slice(0, 4), 16); // 前16字节作为IV
                            const actualCiphertext = CryptoJS.lib.WordArray.create(rawData.words.slice(4)); // 剩余部分作为实际密文
                            
                            decryptOptions.iv = iv;
                            ciphertext = CryptoJS.enc.Base64.stringify(actualCiphertext);
                        }
                        
                        // 确保密钥格式正确
                        let keyWordArray;
                        if (key.length === 16 || key.length === 24 || key.length === 32) {
                            // 如果密钥长度符合AES标准,直接使用
                            keyWordArray = CryptoJS.enc.Utf8.parse(key);
                        } else {
                            // 否则使用MD5哈希生成一个32字节的密钥
                            console.warn('密钥长度不符合AES标准,将使用MD5哈希处理');
                            keyWordArray = CryptoJS.MD5(key);
                        }
                        
                        // 执行解密
                        const decrypted = CryptoJS.AES.decrypt(ciphertext, keyWordArray, decryptOptions);
                        
                        // 将解密结果转换为字符串并返回
                        return decrypted.toString(CryptoJS.enc.Utf8);
                    } catch (error) {
                        console.error('AES解密失败:', error);
                        throw new Error('解密失败,请检查密文和密钥是否正确');
                    }
                }
            

                // 连接到服务器
                document.addEventListener('DOMContentLoaded', () => {
                    // 连接到服务器
                    const socket = io.connect(location.protocol + '//' + document.domain + ':' + location.port);
                    
                    // 接收服务器消息
                    socket.on('server_response', (data) => {
                        console.log("收到服务器响应:", data);
                    });

                    // 接收远程屏幕
                    socket.on('remote_screen_update', (data) => {
                        console.log("收到远程屏幕:", data);
                        // 解密远程屏幕数据
                        let secret_key = document.getElementById('secret_key').value.trim();
                        console.log('密钥:', secret_key);
                        if (!secret_key) {
                            console.log('请输入密钥');
                            return;
                        }
                        let decryptedImageData = decryptAES(data.data, secret_key);
                        console.log('解密后的远程屏幕数据:', decryptedImageData);
                        if (!decryptedImageData) {
                            console.log('密钥错误');
                            return;
                        }
                        
                        // 清除旧的远程屏幕图片
                        while (document.getElementById('telescreen').firstChild) {
                            document.getElementById('telescreen').removeChild(document.getElementById('telescreen').firstChild);
                        }
                        //创建新的远程屏幕图片
                        const remote_screen = document.createElement('img');
                        remote_screen.id = 'remote_screen';                        
                        remote_screen.src=`data:image/png;base64,${decryptedImageData}`;
                        remote_screen.style.display = 'block';
                        //remote_screen.src=`data:image/png;base64,${data.data}`;
                        remote_screen.alt = '远程屏幕';
                        remote_screen.addEventListener('click', (e) => {
                            const rect = e.target.getBoundingClientRect();
                            const x = (e.clientX - rect.left) / rect.width;
                            const y = (e.clientY - rect.top) / rect.height;
                            console.log(`点击位置: (${x}, ${y})`);
                            socket.emit('client_click_screen', {x, y});
                        });
                        remote_screen.addEventListener('dbclick', (e) => {
                            const rect = e.target.getBoundingClientRect();
                            const x = (e.clientX - rect.left) / rect.width;
                            const y = (e.clientY - rect.top) / rect.height;
                            console.log(`点击位置: (${x}, ${y})`);
                            socket.emit('client_dbclick_screen', {x, y});
                        });
                        document.getElementById('telescreen').appendChild(remote_screen);
                    });

                    // 绑定获取屏幕按钮事件
                    document.getElementById('get_screen').addEventListener('click', () => {
                        socket.emit('client_get_screen');
                    });
                    
                    // 绑定发送按钮事件
                    document.getElementById('send_button').addEventListener('click', (e) => {
                        const input = document.getElementById('message_input');
                        socket.emit('client_send_keyboard_input', {data: input.value});
                        input.value = '';
                    });

                    // 绑定粘贴按钮事件
                    document.getElementById('paste_button').addEventListener('click', (e) => {
                        const input = document.getElementById('message_input');
                        socket.emit('client_send_paste_key', {data: input.value});
                        input.value = '';
                    });

                   
                    // 绑定Enter键事件
                    document.getElementById('enter_button').addEventListener('click', (e) => {
                        socket.emit('client_send_enter_key');
                    });

                    // 绑定Delete键事件
                    document.getElementById('delete_button').addEventListener('click', (e) => {
                        socket.emit('client_send_delete_key');
                    });

                    // 绑定上翻键事件
                    document.getElementById('scroll_up_button').addEventListener('click', (e) => {
                        socket.emit('client_send_scroll_up_key');
                    });

                    // 绑定下翻键事件
                    document.getElementById('scroll_down_button').addEventListener('click', (e) => {
                        socket.emit('client_send_scroll_down_key');
                    });


                });
            </script>
        </head>
        <body>

            <div id="telescreen">
                <img id="remote_screen" src="" alt="远程屏幕">
            </div>

            <div id="control_panel">

                <input type="password" id="secret_key" placeholder="输入正确密钥才能刷新远程屏幕">

                <br/>

                <button id="get_screen">刷新远程屏幕</button>

                <br/>

                <input type="text" id="message_input" placeholder="输入要发送给远程屏幕的消息">

                <br/>

                <button id="send_button">发送输入文本</button>

                <br/>

                <button id="paste_button">粘贴输入文本</button>

                <button id="delete_button">Delete</button>

                <br/>

                <button id="enter_button">Enter</button>

                <button id="scroll_up_button">上翻</button>
                <button id="scroll_down_button">下翻</button>


            </div>


        </body>
        </html>


        '''

if __name__ == '__main__':
    socketio.run(app, debug=True,host='0.0.0.0',port=5000)
相关推荐
老白干1 天前
jjwt 0.9.1 在 JDK 11+ 上的两个“坑”与完整解决方案
java·python·log4j
笨鸟先飞,勤能补拙1 天前
AI 赋能网络安全:技术全景、成熟度评估与实战案例
人工智能·python·安全·web安全·网络安全·sqlite·github
长和信泰光伏储能1 天前
京津冀光伏发电:绿色能源的未来之路
python·能源
浦信仿真大讲堂1 天前
从重复操作到自动化闭环:如何让 CST 与 Python 真正协同起来
python·自动化·cst·仿真软件·达索软件
Gu Gu Study1 天前
ScoutLoop开放域深度研究引擎(agent的初步设计想法)
人工智能·python
卷无止境1 天前
写代码这件事,到底该讲究点什么?
后端·python
卷无止境1 天前
循环复杂度到底在算什么,Python 代码怎么才能写得让人一看就懂
后端·python
lpfasd1231 天前
MediaCrawler 项目深度分析
chrome·python·chrome devtools
Dxy12393102161 天前
Python项目打包成EXE完整教程(PyInstaller实战避坑)
开发语言·python
bamb001 天前
一个项目带你入门AI应用开发01
python