编程技术记录

世界你好!

近期因为一些原因,从内网传输数据到外网的行为受到限制,便研究了些非正常手段进行数据传输

使用二维码

原理是在内网系统的屏幕上生成二维码,每秒刷新一次;在外网用拍照或者截屏的方式读取二维码图片,解码为数据。

单个QR码国际标准二维码,二进制模式最多可存储2953字节,2KB多一点,所以使用二维码进行数据传输其速率应该在几KB/S。

生成二维码

需要使用 PIL(pillow)图像库,qrcode二维码生成库,以及tkinterUI界面库。tkinter默认随Python一起安装。

from PIL import Image, ImageTk
import qrcode
import base64
import tkinter

def  generateTkImages(data):
    tkImgs = []
    for i in range(4):  # 单次生成4张二维码图片,这里需要自己根据屏幕适配
        content  = b'需要传输的数据' 

        #理论上不需要base64编码,但是实际测试使用pyzbar解码字节流二维码时会乱码,所以使用base64将字节流转为字符串
        bs = base64.b64encode(content)

        #创建二维码容器,指定单个区块尺寸和边框尺寸
        qr = qrcode.QRCode(box_size = 2,border = 2)
        #添加数据
        qr.add_data(bs)
        #生成二维码图片
        img = qr.make_image()

        #转换成tkinter支持的图像
        tkImg = ImageTk.PhotoImage(image = img)
        tkImgs.append(tkImg) # 使用数组存储,避免因为tkinter的bug只显示最后一张
    return tkImgs

class QrApp(tkinter.Frame):
    def __init__(self,master = None):
        super().__init__(master)
        self.master = master

        self.pack()
        self.labels = []
        for i in range(4):
            lb = tkinter.Label(self)
            lb.grid(row = int(i /2) , column = i % 2 ) # 2行 2 列
            self.labels.append(lb)

    def  test(self):
        tkImgs = generateTkImages(需要传输的数据)
        for i in range(4):
            self.labels[i].configure(image=tkImgs[i])
            self.master.update() #更新界面
            self.master.after(1000) # 保持1000毫秒

root = tkinter.Tk()
root.geometry('1030x550+0+0')
app = QrApp(root)
root.after(500,app.test)
root.mainloop()

二维码解码

本文使用pyzbar解码,图片来自截屏


from pyzbar import pyzbar
from PIL import ImageGrab
import base64

# 截屏
img = ImageGrab.grab()
#使用pyzbar扫描并解码
barcodes = pyzbar.decode(img,symbols=[pyzbar.ZBarSymbol.QRCODE])

for j in barcodes:
    data = j.data.decode("utf-8")
    data = base64.b64decode(data)
    print(data)

使用音频

使用音频传输数据的思路是,用波形的高低代表0和1 ,将数据转换成音频文件,并播放出来,然后录音,最后分析录音文件,并还原为数据。

理论上,44100采样率的音频每秒传输数据极限不超过 44100 / 8 = 5512字节,实际上音频播放时和录音时会有不同程度的音频比特丢失,所以实际传输速率会受影响;而为保证传输精度,单个数据比特需要占有将连续的相邻音频比特,也就是多个音频比特才能表示一个数据比特,所以实际传输速率会更低。

生成Wave文件

import wave

data = b"数据"
audioBits = []
for Byte in range(len(data)):
    for i in range(8):
        bit = (Byte >> (7-i)) & 1
        for k in range(30): #每30个音频比特表示一个数据比特,这里需要根据录音精度调整
            audioBits.append( 255 * bit ) 

wavPath = "wave音频文件存储路径"
with wave.open(wavPath,"wb") as fd:
    fd.setparams((1,1,44100,44100,"NONE","not compressed"))
    fd.writeframes(bits)

Windows 录音

使用pyaudio

import pyaudio
import wave

#获取录音设备序号
def findRecordingDeviceID(pd ):
        name = '立体声混音'
        for i in range(pd.get_device_count()):
            devInfo = pd.get_device_info_by_index(i)
            if devInfo['name'].find(name) >=0 and devInfo['hostApi'] == 0 :
                return i
        return -1

def record()
    pd = pyaudio.PyAudio()
    #查找设备
    dev_id = findRecordingDeviceID(pd)
    if dev_id < 0 :
        print('无法找到录音设备!')
        return

    #打开设备
    record_stream = pd.open(input_device_index=dev_id,
                        format=pyaudio.paInt16,
                        channels=1,
                        rate=44100,
                        input=True,
                        frames_per_buffer=1024)

    #循环读取流
    frames = []
    while(True):
        data = record_stream.read(1024)
        frames.append(data)
        if (len(frames) > 1000): # 设置停止条件,这里只是演示
            break

    #停止读取流  
    record_stream.stop_stream()
    #关闭流
    record_stream.close()
   #停止资源占用
    pd.terminate()

    wavPath = "wave音频文件存储路径"
    with wave.open(wavPath,"wb") as fd:
        fd.setparams((1,1,44100,44100,"NONE","not compressed"))
        fd.writeframes(b''.join(frames))

解码Wave为数据

示意代码

wavPath = "wave音频文件存储路径"
song = wave.open(wavPath)
frame_bytes = song.readframes(song.getnframes())

# 需要先检测音频比特的有效开端,例如连续100个高波形作为开端,后面为有效传输

# 假设已经检测到有效传输
sz = len(frame_bytes)
offset = 0
bits = []
while sz >= 30:
    sum = 0
    for k in range(30):
        sum += frame_bytes[offset+k]
    if (sum / 30) > 50: # 这里认为大于50就是 bit 1
        bits.append(1)
    else:
        bits.append(0)
    sz -= 30
    offset += 30

把比特流转换为字节(略)

© Beli. All Rights Reserved.