近期因为一些原因,从内网传输数据到外网的行为受到限制,便研究了些非正常手段进行数据传输
使用二维码
原理是在内网系统的屏幕上生成二维码,每秒刷新一次;在外网用拍照或者截屏的方式读取二维码图片,解码为数据。
单个QR码国际标准二维码,二进制模式最多可存储2953字节,2KB多一点,所以使用二维码进行数据传输其速率应该在几KB/S。
生成二维码
需要使用 PIL(pillow)
图像库,qrcode
二维码生成库,以及tkinter
UI界面库。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
把比特流转换为字节(略)