我目前正在设计一个隐写术web应用程序作为我的初级计算机科学暑期课程的一部分。由于对HTML、Flask以及SQL和jquery等相互关联的语言的理解非常有限,我遇到了一些障碍。到目前为止,我已经看了很多初学者的flask教程,但它们都倾向于专注于创建用户论坛和发布功能。
我对web应用程序的愿景是有一个标题为"加密"它需要两个字段(文本/消息和用于放置文本的图像)和一个提交按钮,然后运行我的python程序并向用户提供包含隐藏消息的图像。通过大量的网络搜索,我找到了用两个字段制作表单的方法,以及如何上传到文件夹。
然而,在中间的某个地方,我的web应用程序开始变得非常像弗兰肯斯坦的怪物,从各个地方取来的代码片段拼接成一个变形的、可能(读可能)不连贯的代码。当然,这样做的结果是,我的代码不能做它应该做的事情。我的隐写代码工作得很好(显示给我的目标实现更好的理解。这个更大的目标更适用,因为它旨在使用多个用户提交的值来运行python程序,而不涉及注册成员,这是大多数教程忽略的一个愿望):
def packing(s):
'''converts a characters 8 bits into 4 two-bit values and adds them to a
list using a for loop'''
l = []
for i in range(len(s)):
x = ord(s[i])
top2 = (x & 0xC0) >> 6
middletop2 = (x & 0x30) >> 4
lowertop2 = (x & 0xC) >> 2
lower2 = (x & 0x3)
l.extend([top2, middletop2, lowertop2, lower2])
length = len(l)
h1 = (length & 0xC0000000) >> 30
h2 = (length & 0x30000000) >> 28
h3 = (length & 0x0C000000) >> 26
h4 = (length & 0x03000000) >> 24
h5 = (length & 0x00C00000) >> 22
h6 = (length & 0x00300000) >> 20
h7 = (length & 0x000C0000) >> 18
h8 = (length & 0x00030000) >> 16
h9 = (length & 0x0000C000) >> 14
hA = (length & 0x00003000) >> 12
hB = (length & 0x00000C00) >> 10
hC = (length & 0x00000300) >> 8
hD = (length & 0x000000C0) >> 6
hE = (length & 0x00000030) >> 4
hF = (length & 0x0000000C) >> 2
hF1 = (length & 0x00000003)
l = ([h1] + [h2] + [h3] + [h4] + [h5] + [h6] + [h7] + [h8] + [h9] +
[hA] + [hB] + [hC] + [hD] + [hE] + [hF] + [hF1] + l)
return l
def bitsIntoImage(pic, l):
'''wipes the last two bits of each R, G and B value for every pixel
nevessary to import the message. Then writes the rest of the image onto the
new image to return a complete image.'''
pic = Image.open( pic )
draw = ImageDraw.Draw(pic)
(width, height) = pic.size
newPic = Image.new('RGB', (width,height))
drawnewPic = ImageDraw.Draw(newPic)
if len(l) % 3 == 1:
l = l + [0,0]
if len(l) % 3 == 2:
l = l + [0]
redL = l[0::3]
greenL = l[1::3]
blueL = l[2::3]
for y in xrange(height):
for x in xrange(width):
if len(redL) > 0:
openRed = pic.getpixel((x,y))[0] &~ 0x3
openGreen = pic.getpixel((x,y))[1] &~ 0x3
openBlue = pic.getpixel((x,y))[2] &~ 0x3
codedRed = openRed | redL[0]
codedGreen = openGreen | greenL[0]
codedBlue = openBlue | blueL[0]
redL = redL[1:]
greenL = greenL[1:]
blueL = blueL[1:]
drawnewPic.point([x,y], (codedRed, codedGreen, codedBlue))
else:
(R, G, B) = pic.getpixel((x,y))
drawnewPic.point([x,y], (R, G, B))
return newPic
def step1(pic, s):
'''pic = picture, s = message/string. Turns the string into a list of
double-bit information, then imports that string of information into the image'''
l = packing(s)
picture = bitsIntoImage(pic, l)
return picture
但是我很确定我的实际代码无法保存用户提交的图像和消息,因此我的隐写程序实际上可以使用这些值:
import os
import tempfile
import re
from flask.ext.wtf import Form
from wtforms import StringField, TextAreaField, FileField, validators
from wtforms.validators import DataRequired
from flask_wtf.file import FileAllowed, FileRequired
from werkzeug import secure_filename
from PIL import Image, ImageDraw
from steganography import packing, bitsIntoImage, step1, MinaB, unpacking, step2
UPLOAD_FOLDER = '/Users/AustinMossEnnis/practice/uploads/'
ALLOWED_EXTENSIONS = set(['png'])
def allowed_file(filename):
return '.' in filename and
filename.rsplit('.', 1)[1] in ALLOWED_EXTENSIONS
app = Flask(__name__)
app.config['SECRET_KEY'] = 'string'
app.config['UPLOAD_FOLDER'] = UPLOAD_FOLDER
app.config['MAX_CONTENT_LENGTH'] = 16 * 1024 * 1024
class EncryptForm(Form):
image = FileField(u'Upload your image:', validators = ['png'])
message = TextAreaField(u'Enter your message:', [validators.Length(min=1)])
def validate_image(form, field):
if field.data:
field.data = re.sub(r'[^a-z0-9_.-]', '_', field.data)
@app.route('/')
def index():
return render_template('index.html')
@app.route('/encrypt', methods=['GET', 'POST'])
def encrypt():
form = EncryptForm(request.form)
if request.method == "POST" and 'docs' in request.files:
#I had tried using "if form.image.data and form.validate():" to no avail
image_data = request.FILES[form.image.name].read()
step1(image_data, form.message())
file.save(os.path.join(app.config['UPLOAD_FOLDER'], newPic))
redirect(url_for('uploaded_file',
newPic=filename))
return render_template('encrypt.html',
title='Encrpyt',
form=form)
@app.route('/encrypt/<filename>')
def encrypted_file(filename):
return send_from_directory(app.config['UPLOAD_FOLDER'],
filename)
if __name__ == '__main__':
app.run(
host="0.0.0.0",
port=int("5000"),
debug=True
)
你可以从我的剩余进口中看出,我已经尝试了大量的东西,结果好坏参半。在此过程中,我得到了"错误","无效表达式","TypeError: 'str'对象不是可调用的",以及其他错误消息。
总之,我的问题是如何正确地获取用户提交的值,将它们集成到我的程序中,然后返回该程序的产品?
如果有一个临时文件夹会更好吗?是否有必要创建数据库,而不管我是否有用户?我已经尝试了很多,但由于无法正确执行文本或由于不理解我试图执行的代码而失败。
您希望用户提供自己的图像文件,因此必须上传该文件。带有两个文本字段的第一个链接只传递来自请求表单的两个字符串。除非服务器在本地有一个文件名与从文本字段传递过来的文件名相同的文件(它不应该有,因为您试图上传一个它没有的文件),否则它无法访问数据。你提供的第二个链接几乎包含了90%的解决方案。我们将把重点放在这一点上,并根据我们的需要稍微修改一下。
表单块是html文件中唯一重要的部分。它创建一个文件输入来上传文件,创建一个提交输入来发送表单。您可以在这里阅读更多关于各种类型输入的信息。我们只需要一个额外的文本输入。
<form action="upload" method="post" enctype="multipart/form-data">
<input type="file" name="file"><br>
<input type="text" name="message"><br><br>
<input type="submit" value="Upload">
</form>
在python方面,我们只需要更改upload
函数。但是让我们先了解一下它是做什么的。
def upload():
file = request.files['file']
if file and allowed_file(file.filename):
filename = secure_filename(file.filename)
file.save(os.path.join(app.config['UPLOAD_FOLDER'], filename))
return redirect(url_for('uploaded_file', filename=filename))
request
对象包含我们提交的表单中的所有数据。你可以通过打印dir(request)
看到它的所有属性/方法。request.files
保存所有上传文件的数据。通过扩展,request.values
保存来自文本字段的数据。这两个都是特殊的字典结构,但就像普通的字典一样,您可以使用键访问值。由于在html文件中我们将输入称为"file"one_answers"message",所以我们就是这样访问它们的。
用request.files['file']
访问的上传文件存储在另一个称为FileStorage的特殊数据结构中。从这个类中,我们可以访问我们用filename
属性上传的文件的名称,我们可以用save()
方法将实际数据保存到我们的服务器。os.path.join
函数只是将上传文件夹路径与文件名连接起来,这样我们就可以在服务器上定义保存文件的目的地。
在这个数据结构中有一个我们感兴趣的新东西,就是stream
属性。这将以缓冲流的形式返回上传文件的实际二进制数据。我们可以将其与我们的信息一起使用,并将它们传递给您的隐写功能。总之,修改后的代码应该是这样的。
def upload():
file = request.files['file']
text = request.values['message']
if file and allowed_file(file.filename) and text:
# =============================================================
# We do our embedding here and return the modified image stream
file.stream = embed(file.stream.read(), text)
# =============================================================
fname = secure_filename(file.filename)
file.save(os.path.join(app.config['UPLOAD_FOLDER'], fname))
return redirect(url_for('uploaded_file', filename=fname))
最后一个修改与嵌入函数有关。当您向Image.open
提供文件名或字节流时,您创建了一个Image对象。为了从中创建一个文件流,您通常会将数据保存到一个文件中,然后使用open('filename', 'rb').read()
之类的东西再次加载它。但由于我们想在内存中完成所有这些,我们可以使用StringIO。基本上,你的嵌入函数应该是这样的:
from io import BytesIO
from StringIO import StringIO
def embed(stream, text):
image = Image.open(stream)
bits = packing(text)
# pixel modification goes here...
output = StringIO.StringIO()
image.save(output, format='png')
contents = BytesIO(output.getvalue())
output.close()
return contents
虽然这是可选的,但我提供了一些建议来提高嵌入代码的可读性。你有一些重复,可以精简,你可以避免创建第二个图像对象,如果你要丢弃第一个。如果只有几位要嵌入,也不应该遍历所有像素。对于大图像,这可能会产生严重的影响。
def packing(s):
'''Convert a message into 2-bit groups with a size header'''
# Store the size of the message bit groups first
length = len(s) * 4
bit_groups = [(length >> i) & 0x03 for i in xrange(30, -1, -2)]
for char in s:
byte = ord(char)
byte_decomposition = [(byte >> i) & 0x03 for i in xrange(6, -1, -2)]
bit_groups.extend(byte_decomposition)
return bit_groups
def embed(stream, text):
'''Embed a message in the RGB pixels of an image in groups of 2 bits.'''
image = Image.open(stream)
bits = packing(text)
mask = ~0x03
max_width = image.size[0]
height = 0
width = 0
color = 0
for b in bits:
if color == 0:
pixel = list(image.getpixel((width, height)))
pixel[color] = (pixel[color] & mask) | b
color += 1
if color == 3:
image.putpixel((width, height), tuple(pixel))
color = 0
width += 1
if width == max_width:
width = 0
height += 1
# Convert the Image object to a ByteIO stream as shown above and return
return contents