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)
相关推荐
Y.9994 小时前
Python 题目练习 Day1.2
开发语言·python
闲人编程4 小时前
使用Celery处理Python Web应用中的异步任务
开发语言·前端·python·web·异步·celery
MYX_3095 小时前
第四章 神经网络的学习
python·神经网络·学习
未来之窗软件服务6 小时前
操作系统应用开发(十四)RustDesk服务器配置——东方仙盟筑基期
运维·服务器·远程桌面·rustdesk·仙盟创梦ide·东方仙盟
郝学胜-神的一滴6 小时前
Effective Python 第38条:简单的接口应该接受函数,而不是类的实例
开发语言·python·软件工程
海琴烟Sunshine6 小时前
leetcode 35.搜索插入的位置 python
python·算法·leetcode
海琴烟Sunshine7 小时前
leetcode 28. 找出字符串中第一个匹配项的下标 python
linux·python·leetcode
小蕾Java7 小时前
PyCharm快速上手指南,数据类型篇
ide·python·pycharm
飞翔的佩奇8 小时前
【完整源码+数据集+部署教程】 白血球图像分割系统: yolov8-seg-repvit
python·yolo·计算机视觉·数据集·yolo11·白血球图像分割系统·、yolov8