AES 解密抛出 ValueError:输入字符串的长度必须是 16 的倍数

Posted

技术标签:

【中文标题】AES 解密抛出 ValueError:输入字符串的长度必须是 16 的倍数【英文标题】:AES Decryption throws ValueError: Input strings must be a multiple of 16 in length 【发布时间】:2019-03-24 01:08:09 【问题描述】:

我得到了 Github(@Cahlen Humphreys) 的当前代码,最初它无法正常工作,但经过细微更改后,它会运行以进行加密,

但解密给我以下错误:

 return self.func(*args)
  File "C:/Users/W4RL0RD/Desktop/websec.py", line 183, in cipher_open
    decrypt(filename,password)
  File "C:/Users/W4RL0RD/Desktop/websec.py", line 117, in decrypt
    decrypted = obj2.decrypt(ciphertext)
  File "C:\Users\W4RL0RD\Anaconda2\lib\site-packages\Crypto\Cipher\blockalgo.py", line 295, in decrypt
    return self._cipher.decrypt(ciphertext)
ValueError: Input strings must be a multiple of 16 in length

from Tkinter import *
from tkFileDialog import *
import tkMessageBox
import os 
import PIL
import math
from Crypto.Cipher import AES
import hashlib
import binascii

global password
# encryption method
# -----------------
def encrypt(imagename,password):
    # initialize variables
    plaintext = list()
    plaintextstr = ""

# load the image
im = PIL.Image.open(imagename)  # open target image
pix = im.load()

#print im.size   # print size of image (width,height)
width = im.size[0]
height = im.size[1]

# break up the image into a list, each with pixel values and then append to a string
for y in range(0,height):
    #print("Row: %d") %y  # print row number
    for x in range(0,width):
        #print pix[x,y]  # print each pixel RGB tuple
        plaintext.append(pix[x,y])

# add 100 to each tuple value to make sure each are 3 digits long.  being able to do this is really just a PoC 
# that you'll be able to use a raw application of RSA to encrypt, rather than PyCrypto if you wanted.
for i in range(0,len(plaintext)):
    for j in range(0,3):
        plaintextstr = plaintextstr + "%d" %(int(plaintext[i][j])+100)

# length save for encrypted image reconstruction
relength = len(plaintext)

# append dimensions of image for reconstruction after decryption
plaintextstr += "h" + str(height) + "h" + "w" + str(width) + "w"

# make sure that plantextstr length is a multiple of 16 for AES.  if not, append "n".  not safe in theory
# and i should probably replace this with an initialization vector IV = 16 * '\x00' at some point.  In practice
# this IV buffer should be random.
while (len(plaintextstr) % 16 != 0):
    plaintextstr = plaintextstr + "n"

# encrypt plaintext
obj = AES.new(password, AES.MODE_CBC, 'This is an IV456')
ciphertext = obj.encrypt(plaintextstr)

# write ciphertext to file for analysis
cipher_name = imagename + ".crypt"
g = open(cipher_name, 'w')
g.write(ciphertext)

# -----------------
# construct encrypted image (not currently using since Tkinter isn't very nice)
# -----------------
def construct_enc_image():
    # hexlify the ciphertext    
    asciicipher = binascii.hexlify(ciphertext)

    # replace function
    def replace_all(text, dic):
        for i, j in dic.iteritems():
            text = text.replace(i, j)
        return text

    # use replace function to replace ascii cipher characters with numbers
    reps = 'a':'1', 'b':'2', 'c':'3', 'd':'4', 'e':'5', 'f':'6', 'g':'7', 'h':'8', 'i':'9', 'j':'10', 'k':'11', 'l':'12', 'm':'13', 'n':'14', 'o':'15', 'p':'16', 'q':'17', 'r':'18', 's':'19', 't':'20', 'u':'21', 'v':'22', 'w':'23', 'x':'24', 'y':'25', 'z':'26'
    asciiciphertxt = replace_all(asciicipher, reps)

    # construct encrypted image
    step = 3
    encimageone=[asciiciphertxt[i:i+step] for i in range(0, len(asciiciphertxt), step)]
    # if the last pixel RGB value is less than 3-digits, add a digit a 1
    if int(encimageone[len(encimageone)-1]) < 100:
        encimageone[len(encimageone)-1] += "1"
    # check to see if we can divide the string into partitions of 3 digits.  if not, fill in with some garbage RGB values
    if len(encimageone) % 3 != 0:
        while (len(encimageone) % 3 != 0):
            encimageone.append("101")

    encimagetwo=[(int(encimageone[int(i)]),int(encimageone[int(i+1)]),int(encimageone[int(i+2)])) for i in range(0, len(encimageone), step)]    

    # make sizes of images equal
    while (int(relength) != len(encimagetwo)):
        encimagetwo.pop()

    # encrypted image
    encim = PIL.Image.new("RGB", (int(width),int(height)))
    encim.putdata(encimagetwo)

    #encim.show()
    # alert success and path to image
    enc_success(cipher_name)

construct_enc_image()

# decryption method
# -----------------
def decrypt(ciphername,password):

    # reach ciphertext into memory
    cipher = open(ciphername,'r')
    ciphertext = cipher.read()

# decrypt ciphertext with password
obj2 = AES.new(password, AES.MODE_CBC, 'This is an IV456')
decrypted = obj2.decrypt(ciphertext)

# parse the decrypted text back into integer string
decrypted = decrypted.replace("n","")

# extract dimensions of images
newwidth = decrypted.split("w")[1]
newheight = decrypted.split("h")[1]

# replace height and width with emptyspace in decrypted plaintext
heightr = "h" + str(newheight) + "h"
widthr = "w" + str(newwidth) + "w"
decrypted = decrypted.replace(heightr,"")
decrypted = decrypted.replace(widthr,"")

# reconstruct the list of RGB tuples from the decrypted plaintext
step = 3
finaltextone=[decrypted[i:i+step] for i in range(0, len(decrypted), step)]
finaltexttwo=[(int(finaltextone[int(i)])-100,int(finaltextone[int(i+1)])-100,int(finaltextone[int(i+2)])-100) for i in range(0, len(finaltextone), step)]    

# reconstruct image from list of pixel RGB tuples
newim = Image.new("RGB", (int(newwidth), int(newheight)))
newim.putdata(finaltexttwo)
newim.show()

# ---------------------
# GUI stuff starts here
# ---------------------

# empty password alert
def pass_alert():
   tkMessageBox.showinfo("Password Alert","Please enter a password.")

def enc_success(imagename):
   tkMessageBox.showinfo("Success","Encrypted Image: " + imagename) 

# image encrypt button event
def image_open():
    # useless for now, may need later
    global file_path_e

    # check to see if password entry is null.  if yes, alert
    enc_pass = passg.get()
if enc_pass == "":
    pass_alert()
else:
    password = hashlib.sha256(enc_pass).digest()
    filename = askopenfilename()
    file_path_e = os.path.dirname(filename)
    # encrypt the image
    encrypt(filename,password)

# image decrypt button event
def cipher_open():
# useless for now, may need later
    global file_path_d

# check to see if password entry is null.  if yes, alert
    dec_pass = passg.get()
    if dec_pass == "":
        pass_alert()
    else:    
        password = hashlib.sha256(dec_pass).digest()
        filename = askopenfilename()
        file_path_d = os.path.dirname(filename)
    # decrypt the ciphertext
        decrypt(filename,password)

# main gui app starts here
class App:
  def __init__(self, master):
    # make passg global to use in functions
    global passg
    # setup frontend titles etc blah blah
    title = "Image Encryption"
    author = "Kumod Arya\n16BCI0172"
    msgtitle = Message(master, text =title)
    msgtitle.config(font=('helvetica', 17, 'bold'), width=200)
    msgauthor = Message(master, text=author)
    msgauthor.config(font=('helvetica',10), width=200)

    # draw canvas
    canvas_width = 200
    canvas_height = 50
    w = Canvas(master, 
           width=canvas_width,
           height=canvas_height)

    # pack the GUI, this is basic, we shold use a grid system
    msgtitle.pack()
    msgauthor.pack()
    w.pack()

    # password field here above buttons
    passlabel = Label(master, text="Enter Encrypt/Decrypt Password:")
    passlabel.pack()
    passg = Entry(master, show="*", width=20)
    passg.pack()

    # add both encrypt/decrypt buttons here which trigger file browsers
    self.encrypt = Button(master, 
                         text="Encrypt", fg="black", 
                         command=image_open, width=25,height=5)
    self.encrypt.pack(side=LEFT)
    self.decrypt = Button(master,
                         text="Decrypt", fg="black",
                         command=cipher_open, width=25,height=5)
    self.decrypt.pack(side=RIGHT)



root = Tk()
root.wm_title("Image Encryption")
app = App(root)
root.mainloop()

【问题讨论】:

检查你的缩进 - 代码到处都是,它不清楚(对我来说)什么属于哪个功能。 你可能错过了 padding, read 【参考方案1】:

您的代码存在一些安全问题:

直接使用填充明文密码作为加密密钥是不安全的。请改用 scrypt 或 PBKDF2 等密钥派生函数。 固定的 IV 基本上是无用的。使用随机字节作为 IV 与密文一起存储。 最好使用 MAC(例如 HMAC 或 GCM)来验证解密数据。

话虽如此,我猜它为什么不工作是你要么在 Windows 上运行,要么有一个非拉丁默认字符集。

尝试在open() 调用中添加二进制模式:

open(cipher_name, 'w')open(cipher_name, 'wb')

open(ciphername,'r')open(ciphername,'rb')

【讨论】:

是的,解决了这个问题!谢谢。

以上是关于AES 解密抛出 ValueError:输入字符串的长度必须是 16 的倍数的主要内容,如果未能解决你的问题,请参考以下文章

在 Android 中的 iOS AES/CBC/PKCS7Padding 128 位算法中加密的解密字符串的问题

OpenSSL 一行 base64 输入。读取输入文件时出错

AES GCM 解密绕过 JAVA 中的身份验证

用c#做aes加密 为啥在线解密解不了

PHP中的AES解密

为啥我的 AES 加密会引发 InvalidKeyException?