BadPaddingException Python 2和Java 1.6之间的AES 128 CBC

时间:2011-03-19 19:47:18

标签: java python encoding aes

我有一个Python服务器脚本和一个Java客户端应用程序,目标是通过套接字加密数据。数据始终是一个字符串。 (我是Java新手)

应该发生什么:
Python脚本创建一个套接字并侦听连接,接受新连接,以纯文本形式发送IV用于AES CBC PKCS5Padding,设置密码,然后从Java应用程序接收加密数据(解密,然后在终端打印出来) ,然后将加密数据发送到Java应用程序,然后关闭连接。

Java应用程序连接到python套接字,接收IV,设置相同的密码,加密一个简单的填充字符串,将其发送到python服务器,然后等待回复,解密回复并将其打印到屏幕。然后连接关闭。

实际发生的事情
服务器设置套接字,java客户端连接并接收IV,两者都设置相同的加密/解密密码,然后java客户端发送加密的填充字符串。 Python服务器成功接收密文,解密字符串和unpad,字符串正确显示。然后,服务器将加密的填充字符串发送到java客户端,客户端接收字符串,然后在解密时由于BadPaddingException而失败。

根据RFC,我已经验证了python端的PKCS5 Padding是正确的。我尝试过其他填充方法(零填充等),但没有任何工作。我还尝试了几种不同的字符串编码,我也尝试了M2Crypto(现在使用pycrypto)等等。我尝试在客户端的套接字上使用CipherInputStream,结果相同。

没有任何作用。我仍然认为Python和Java之间可能存在编码问题,但我只是为了解释为什么Java客户端在解密时失败了。

Python服务器:

#!/usr/bin/env python

import os, time, threading, json
from socket import *
import sys, base64

from Crypto.Cipher import AES
from socket import *

### Settings
serverHost = '' # localhost
serverPort = 5555 # non-reserved
masterkey = 'mysecretpassword'

BLOCK_SIZE = 16 # Block-size for cipher (16, 24 or 32 for AES)
#PADDING = '{' # block padding for AES

# PKCS5 Padding
pad = lambda s: s + (BLOCK_SIZE - len(s) % BLOCK_SIZE) * chr(BLOCK_SIZE - len(s) % BLOCK_SIZE)
unpad = lambda s : s[0:-ord(s[-1])]

# generate new IV's - why you ask? ... just look at WEP
def createCipher(key):
    iv = os.urandom(16)
    return (AES.new(key, AES.MODE_CBC, iv), iv)

class IRCTalkServer(threading.Thread):
    def __init__(self, masterkey, host='', port=5555):
        try:
            self.sockobj = socket(AF_INET, SOCK_STREAM) # create TCP socket obj
            self.sockobj.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1) # make port reusable
            self.sockobj.bind((host, port)) # bind socket to port
            self.sockobj.listen(5)                     # listen, allow 5 pending connects
            if host == '': host='localhost'
            print "Started server on %s:%d" % (host, port)
        except:
            print "Error starting server"
            sys.exit(0)
        threading.Thread.__init__(self)
        self.die = False # loop killer
        self.masterkey = masterkey
        self.host = host
        self.port = port

    def run(self):
        try:
            while True and not self.die: # infinite loop unless called to quit
                connection, address = self.sockobj.accept()
                print 'Server connected by ', address
                # generate cipher and get first IV - prevent same WEP hacks
                #paddedKey = keypad(masterkey)
                #print "master key:", masterkey, " - padded key:", paddedKey, " - diff key:", keypad('test')
                self.cipher, self.iv = createCipher(masterkey)
                print "IV:", self.iv.encode('hex'), "Sending IV: ", repr(self.iv)
                connection.send("%s%s" % (self.iv.encode('hex'),'\n')) # send iv first
                while True and not self.die: # read from client
                    print "waiting for client"
                    data = connection.recv(10485760)
                    print "recieved from client:", repr(data.rstrip())
                    if not data:
                                                print "NO DATA"
                                                break
                    dataCheck, JSON = self.decryptData(data.rstrip())
                    print 'Recieved from Client:', repr(JSON)
                    #print 'Recieved from Client:', repr(data)
                    #if dataCheck:
                        #senddata = self.encryptData('SUCCESS')
                        #print "Size of compresseddata:", len(senddata)
                        #connection.send(senddata)
                        #morestuff = 'abc123def456'*int(10000/9)
                        #senddata = self.encryptData(['test', morestuff, 'test1', {'key': 'value'}, 2223])
                        #print "Size of compresseddata:", len(senddata)
                        #connection.send(senddata)
                    print "Sending reply to client..."
                    senddata = self.encryptData('test')
                    print "reply data:", repr("%s%s" % (senddata, "\n"))
                    connection.send("%s%s" % (senddata, "\n"))
                    #connection.send("Hello back mr android!")
                    #successReply = connection.recv(256) # only for "END REQUEST"
                                        break
                                print "Closing connection... \n\n"
                connection.close()
        except:
                        print "exception on try loop"
            pass # an error occurred, just drop it, the client will try again later

    def encryptData(self, plaintext):
        # convert to json string, pad the string, then encrypt, then compress
        #JSON = json.dumps(plaintext, separators=(',',':'))
        JSON = plaintext
        #print "Size of JSON:", len(JSON)
        ciphertext = pad(unicode(JSON))
        print "padded text:", repr(ciphertext)
        ciphertext = self.cipher.encrypt(ciphertext)
        print "ciphertext:", repr(ciphertext), "|", len(ciphertext), "|", ciphertext
        ciphertext = ciphertext.encode('hex').upper()
        print "hexified text:", repr(ciphertext)
        #ciphertext = self.cipher.encrypt(pad(JSON)).encode('hex').upper()
        print "Size of ciphertext:", len(ciphertext)
        return ciphertext

    def decryptData(self, ciphertext):
        try:
            # decompress data to ciphertext, decrypt, convert to json
            print "length of ciphertext:", len(ciphertext)
            ptext = ciphertext.decode('hex')
            print "unhexifed:", repr(ptext)
            ptext = self.cipher.decrypt(ptext)
            print "decrypted:", repr(ptext)
            ptext = unpad(ptext)
            print "unpadded:", repr(ptext)
            #ptext = unpad(self.cipher.decrypt(ciphertext.decode('hex')))
            print "ptext: ", repr(ptext)
            JSON = ptext
            #plaintext = unpad(self.cipher.decrypt(ciphertext))
            ##JSON = json.loads(plaintext)
            #JSON = plaintext
        except:
            print "Error on decryption"
            JSON = None
        return (True, JSON)

    def addToQueue(self, data):
        pass

    def getFromQueue(self):
        pass

testserver =IRCTalkServer(masterkey)
testserver.start()
testserver.join()

Java客户端:(此代码的部分内容仍需正确记入 - [我还没有完成])

import java.io.*;
import java.util.*;
import java.net.*;

import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.spec.IvParameterSpec;
//import javax.crypto.CipherInputStream;
//import javax.crypto.CipherOutputStream;

import java.security.spec.AlgorithmParameterSpec;
import javax.crypto.spec.SecretKeySpec;

class clientHandler {
    /*
     *  This class sets up AES CBC encryption for the socket, 
     *  new instance for every connection since the IV changes
     */

    // Declare variables
    private Cipher ecipher;
    private Cipher dcipher;
    Socket testSocket = null;
    //DataOutputStream out = null;
    //DataInputStream in = null;
    PrintWriter out = null;
    BufferedReader in = null;
    String masterkey = "mysecretpassword";
    static final String HEXES = "0123456789ABCDEF";

    public static String byteToHex( byte [] raw ) {
        if ( raw == null ) {
            return null;
        }
        final StringBuilder hex = new StringBuilder( 2 * raw.length );
        for ( final byte b : raw ) {
            hex.append(HEXES.charAt((b & 0xF0) >> 4))
                .append(HEXES.charAt((b & 0x0F)));
        }
        return hex.toString();
    }

    public static byte[] hexToByte(String hexString) {
        int len = hexString.length();
        byte[] ba = new byte[len / 2];
        for (int i = 0; i < len; i += 2) {
            ba[i/2] = (byte) ((Character.digit(hexString.charAt(i), 16) << 4) + Character.digit(hexString.charAt(i+1), 16));
        }
        return ba;
    }

    public String encrypt(String plaintext) {
        // encrypt string
        try {
            byte[] cipherbyte = ecipher.doFinal(plaintext.getBytes("UTF-8")); 
            //String ciphertext = new String(ecipher.doFinal(plaintext.getBytes("UTF8")));
            return byteToHex(cipherbyte);
        } catch (Exception e){
            e.printStackTrace();
            return null;
        }
    }

    public String decrypt(String ciphertext) {
        // decrypt hex string
        try {
            System.out.println("decrypt byte length: " + hexToByte(ciphertext).length);
            String tp = new String(hexToByte(ciphertext), "UTF-8");
            System.out.println("toString(): " + tp);
            String plaintext = new String(dcipher.doFinal(hexToByte(ciphertext.trim())), "UTF-8");
            //String plaintext = new String(dcipher.doFinal(ciphertext.getBytes("UTF8")), "UTF-8");
            return plaintext;
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    public void setupCrypto(byte[] iv, String key) {
        // setup AES CBC encryption
        //  convert IV and key to byte array for crypto
        try {
             System.out.println("Setting up Crypto...");
             //byte[] ivb = iv.getBytes("UTF8");
             byte[] keyb = key.getBytes("UTF8");
             AlgorithmParameterSpec paramSpec = new IvParameterSpec(iv);
             SecretKeySpec skey = new SecretKeySpec(keyb, "AES");
             ecipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
             dcipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
             ecipher.init(Cipher.ENCRYPT_MODE, skey, paramSpec);
             dcipher.init(Cipher.DECRYPT_MODE, skey, paramSpec);
        } catch (Exception e) {
            System.err.println("Error:" + e);
        }
    }

    public void startServer() {
        // starts server
        try {
            System.out.println("Connecting to Server");
            testSocket = new Socket("localhost", 5555);
            //out = new DataOutputStream(testSocket.getOutputStream());
            //in = new DataInputStream(testSocket.getInputStream());
            out = new PrintWriter(testSocket.getOutputStream(), true);
            in = new BufferedReader(new InputStreamReader(testSocket.getInputStream()));
        } catch (UnknownHostException e) {
            System.err.println("Uknown Host");
        } catch (IOException e) {
            System.err.println("Couldn't get I/O for the connection");
        }
    }

     public void androidClientHandler() {
         // actual communication
         if (testSocket != null && out != null && in != null) {
             try {
                 System.out.println("Waiting for IV..");
                 String iv;
                 iv = in.readLine();
                 byte [] ivb = hexToByte(iv);
                 System.out.println("Got IV..." + iv);
                 setupCrypto(ivb, masterkey);
                 String ciphertext;
                 String plaintext;
                 System.out.println("Sending \"test\" to server");
                 //out.writeBytes("test\n");
                 ciphertext = encrypt("test");
                 System.out.println("Sent: " + ciphertext);
                 out.println(ciphertext);
                 System.out.println("Waiting for Server to reply");
                 String responseLine;
                 responseLine = in.readLine().replaceAll("\\\\n", "");
                 System.out.println("Recieved from Server: " + responseLine + " - length: " + responseLine.length());
                 plaintext = decrypt(responseLine);
                 System.out.println("Recieved from Server: " + plaintext);
                 System.out.println("Closing Connection");
                 out.close();
                 in.close();
                 testSocket.close();
             } catch (UnknownHostException e) {
                 System.err.println("Trying to connect to unknown host:" + e);
             } catch (IOException e) {
                 System.err.println("IOException: " + e);
             }
         }
     }

     public void javaIsGay() {
         startServer();
         androidClientHandler();
     }

     public static void main(String[] args) {
         System.setProperty("file.encoding", "UTF-8");
         clientHandler c = new clientHandler();
         c.javaIsGay();
     }
}

Python服务器的输出:

Started server on localhost:5555
Server connected by  ('127.0.0.1', 59683)
IV: c54aae0a5c43f547f0355ee7a0ee38c1 Sending IV:  '\xc5J\xae\n\\C\xf5G\xf05^\xe7\xa0\xee8\xc1'
waiting for client
recieved from client: 'BBE7E09093625204CD3F7B755066419D'
length of ciphertext: 32
unhexifed: '\xbb\xe7\xe0\x90\x93bR\x04\xcd?{uPfA\x9d'
decrypted: 'test\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c'
unpadded: 'test'
ptext:  'test'
Recieved from Client: 'test'
Sending reply to client...
padded text: u'test\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c'
ciphertext: '\x9fT\xfc\xf0\xa8\xc2\xd3N*\x8f\x8e~\xc7\x8a\xbfR' | 16 | �T�����N*~NJ�R
hexified text: '9F54FCF0A8C2D34E2A8F8E7EC78ABF52'
Size of ciphertext: 32
reply data: '9F54FCF0A8C2D34E2A8F8E7EC78ABF52\n'
Closing connection... 

Java客户端的输出:

Connecting to Server
Waiting for IV..
Got IV...c54aae0a5c43f547f0355ee7a0ee38c1
Setting up Crypto...
Sending "test" to server
Sent: BBE7E09093625204CD3F7B755066419D
Waiting for Server to reply
Recieved from Server: 9F54FCF0A8C2D34E2A8F8E7EC78ABF52 - length: 32
decrypt byte length: 16
toString(): �T����N*��~NJ�R
javax.crypto.BadPaddingException: Given final block not properly padded
    at com.sun.crypto.provider.CipherCore.doFinal(CipherCore.java:811)
    at com.sun.crypto.provider.CipherCore.doFinal(CipherCore.java:676)
    at com.sun.crypto.provider.AESCipher.engineDoFinal(AESCipher.java:317)
    at javax.crypto.Cipher.doFinal(Cipher.java:1813)
    at clientHandler.decrypt(SocketClient.java:70)
    at clientHandler.androidClientHandler(SocketClient.java:134)
    at clientHandler.javaIsGay(SocketClient.java:151)
    at clientHandler.main(SocketClient.java:157)
Recieved from Server: null
Closing Connection

5 个答案:

答案 0 :(得分:2)

我可能为时已晚,但这就是我所得到的: 你的python填充例程有一个问题。如果明文的大小是16的倍数,则不会添加填充,而Java需要16'\ x10'。

这当然不能解决您在运输过程中的腐败问题。你最好的选择是检查问题是来自密码还是传输:如何将编码文本写入文件并从中读取Java,只是为了确定?

答案 1 :(得分:0)

在Java程序中,您使用2 Cipher个实例进行加密和解密,并使用相同的IV初始化;在python代码中,您使用相同的对象进行解密和加密。我不熟悉 pycrypto 库(并且不熟悉python),但加密对象很可能包含内部状态,尤其是在CBC模式下。如果您在初始解密后没有重置密码,我不确定您是否使用预期的IV加密回复,这可以解释异常。

答案 2 :(得分:0)

当我使用Java JDK的扩展安全功能(特别是AES加密和解密)时,我抛出了同样的异常。实现这项工作的关键是为我的生产JDK安装扩展安全策略文件。您也可以尝试这样做。

您可以在此处下载http://www.oracle.com/technetwork/java/javase/downloads/index.html(在页面底部搜索Java Cryptography Extension)。下载后,您必须将* .jar文件复制到JDK_HOME / jre / lib / security并覆盖以前的文件。

希望这有帮助,问候。

答案 3 :(得分:0)

您提到尝试nopadding,您是否尝试切换密码模式?对于您的应用,CTR应该与CBC一样安全,并且不需要填充。

答案 4 :(得分:0)

我遇到了类似的问题,我不确定下面的代码是否会给你一个想法。

python代码

import base64
from Crypto.Cipher import AES
BS = AES.block_size
pad = lambda s: s + (BS - len(s) % BS) * chr(BS - len(s) % BS)
unpad = lambda s : s[0:-ord(s[-1])]

class MyAESCipher:
    def __init__( self, key ):
        """
        Requires hex encoded param as a key
        """
        self.key = key

    def encrypt( self, raw ):
        """
        Returns hex encoded encrypted value!
        """
        raw = pad(raw)
        cipher = AES.new(self.key, AES.MODE_ECB, self.key)
        return base64.b64encode(cipher.encrypt(raw))

// java code

private static final String INSTANCE = "AES/CBC/PKCS5Padding";
public static String decrypt(String sSrc, String sKey) throws Exception {
        try {
            // 判断Key是否正确  
            if (sKey == null) {
          //      LOGGER.error("Key should not be null");
                return null;
            }  
            if (sKey.length() != 16) {
                return null;
            }
            byte[] raw = sKey.getBytes();
            SecretKeySpec skeySpec = new SecretKeySpec(raw, "AES");
            Cipher cipher = Cipher.getInstance(INSTANCE);
            IvParameterSpec iv = new IvParameterSpec(sKey.getBytes());
            cipher.init(Cipher.DECRYPT_MODE, skeySpec, iv);
            byte[] encrypted1 = Base64.decodeBase64(sSrc.getBytes());
            try {
                byte[] original = cipher.doFinal(encrypted1);
                return new String(original,"UTF-8");
            } catch (Exception e) {
                return null;
            }
        } catch (Exception ex) {
          //  LOGGER.error("decrypt fail", ex);
            return null;
        }
    }