繁体   English   中英

从客户端到服务器的Java NIO + AES加密 - ByteBuffer问题

[英]Java NIO + AES Encryption from Client to Server - ByteBuffer issue

我是一个关于加密和NIO的新手,我有以下客户端代码:

    String key1 = "1234567812345678";
    byte[] key2 = key1.getBytes();
    SecretKeySpec secret = new SecretKeySpec(key2, "AES");
    Cipher cipher = Cipher.getInstance("AES");
    cipher.init(Cipher.ENCRYPT_MODE, secret);
    byte[] encrypted = cipher.doFinal(msg.getBytes());
    System.out.println("Encrypted info: " + encrypted);

    String send = encrypted.toString();
    bytebuf = ByteBuffer.allocate(48);
    bytebuf.clear();
    bytebuf.put(send.getBytes());

    bytebuf.flip();

    while(bytebuf.hasRemaining()) {
        nBytes += client.write(bytebuf);
    }

以及服务器的以下代码:

    // Server receives data and decrypts

    SocketChannel socket = (SocketChannel) key.channel();
    ByteBuffer buf = ByteBuffer.allocate(1024);
    nBytes = socket.read(buf);
    String data = new String(buf.array()).trim();
    String key1 = "1234567812345678";
    byte[] key2 = key1.getBytes();
    SecretKeySpec secret = new SecretKeySpec(key2, "AES");

    Cipher cipher = Cipher.getInstance("AES");

    cipher.init(Cipher.DECRYPT_MODE, secret);
    byte[] decrypted = cipher.doFinal(data.getBytes());
    System.out.println("Decrypted Info: " + new String(decrypted));

当一条消息从客户端发送到服务器时,“HELLO”例如被加密到[B @ 34d74aa5,而在服务器端我得到*数据包被发现为[B @ 34d74aa5]

直到这里一切看起来都很好,但我得到以下异常:

javax.crypto.IllegalBlockSizeException: Input length must be multiple of 16 when decrypting with padded cipher

我怀疑数据从服务器端缓冲区出来的方式有问题吗? 有什么想法吗?

更新:

**根据Erickson的回答,这是最终的解决方案

javax.crypto.BadPaddingException: Given final block not properly padded

客户代码:

            String key1 = "1234567812345678";
        byte[] key2 = key1.getBytes();
            byte[] iv = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
        IvParameterSpec ivspec = new IvParameterSpec(iv);
        SecretKeySpec secret = new SecretKeySpec(key2, "AES");
        Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
        cipher.init(Cipher.ENCRYPT_MODE, secret, ivspec);

        byte[] encrypted = cipher.doFinal(msg.getBytes(StandardCharsets.UTF_8));
        String text = DatatypeConverter.printBase64Binary(encrypted);

        System.out.println("Encrypted info: " + text);

        bytebuf = ByteBuffer.allocate(32);
        bytebuf.clear();

        bytebuf.put(text.getBytes());

        bytebuf.flip();

        while(bytebuf.hasRemaining()) {
            nBytes += client.write(bytebuf);
        }

服务器代码:

            LOGGER.info("Confirming write");
        String data = new String(buf.array());

        LOGGER.info("Data packet found as {}", data);

        /*******************************************************/
        byte[] iv = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
        IvParameterSpec ivspec = new IvParameterSpec(iv);
        String key1 = "1234567812345678";
        byte[] key2 = key1.getBytes();
        SecretKeySpec secret = new SecretKeySpec(key2, "AES");
        Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");

        cipher.init(Cipher.DECRYPT_MODE, secret, ivspec);

        byte[] encrypted = DatatypeConverter.parseBase64Binary(data);
        byte[] decrypted = cipher.doFinal(encrypted);

        System.out.println("Decrypted Info: " + new String(decrypted, StandardCharsets.UTF_8));

您的密文, encrypted ,是一个byte[]和调用toString()的阵列不呈现阵列内容上,则返回类型( [B和散列码( @34d74aa5 )的信息由所描述Object.toString()

您不能只使用new String(encrypted) 当字节数组被解码为文本时,解码器将用替换字符\� ( )替换任何无效字节序列。 因此,信息丢失,随后的解密将失败。

使用类似base-64的编码将字节序列转换为可打印字符。 不要为此使用第三方库来破解您的代码; 你可以使用javax.xml.bind.DatatypeConverter

/* Client: */
byte[] encrypted = cipher.doFinal(msg.getBytes(StandardCharsets.UTF_8));
String text = DatatypeConverter.printBase64Binary(encrypted);
…

/* Server: */
byte[] encrypted = DatatypeConverter.parseBase64Binary(data);
byte[] decrypted = Cipher.doFinal(encrypted);
System.out.println(new String(decrypted, StandardCharsets.UTF_8);

您还应明确选择模式和填充(如“AES / CBC / PKCS5Padding”),因为无法保证收件人将使用相同的提供程序,或者同一提供程序将使用相同的默认值。 指定字符编码也是如此,例如UTF-8。

您正在将密文( byte[] )转换为String

byte[] encrypted = cipher.doFinal(msg.getBytes());
String send = encrypted.toString();

这是不正确的。 您也不能执行new String(byte[])因为byte[]是随机的,而不是new String(byte[])假定的平台默认编码中的字符数据流。 您应该使用十六进制或base64编码将byte[]数据转换为String (我建议使用Apache Commons Codec ),例如

hexEncodedCipherText = new String(Hex.encodeHex(binaryCipherText))

在服务器端,使用相反的操作在解密之前将十六进制或base64编码数据转换回byte[] ,例如

binaryCipherText = Hex.decodeHex(hexEncodedCipherText.toCharArray());

更新:

由于初始化向量的使用不正确,更新的问题在解密期间不起作用。 在加密期间不指定IV,这意味着Java将生成随机IV。 您需要通过在加密后调用cipher.getIV()从密码中获取此随机IV(或明确指定它,尽管生成随机的更安全)。 然后,在解密期间,使用加密期间创建的IV创建IvParameterSpec 此外,您需要以与密文相同的方式对IV进行编码/解码,因为它也是二进制数据。

更新2:

我看到你用IV更新了你的问题,但你使用的是null IV。 通常,当您为发送的每封邮件都有唯一的密钥时,这只是“安全”。 如果您的密钥被修复或重复使用了很长时间,您应该为每个加密/解密生成一个唯一的IV。 否则,您将基于使用相同密钥和IV加密的多个密文进行密码分析。

AES方案是一种“分组密码”,它适用于固定大小的数据块。 您正在创建一个“原始”Cipher实例,它希望您确保传递给密码的每个字节数组都与密码的“本机”块长度对齐。 这通常不是你想要做的。

您在使用密码“raw”时遇到的另一个问题是,虽然它不会导致实际错误,但如果您要在不同的时间将相同的数据块传递给它,那么每次都会加密该块同样地,因此给攻击者提供了关于数据结构的线索。 同样,这通常不是您在实际应用中想要做的事情。

通常,您需要指定两个额外的东西: 填充方案 ,用于确定当数据部分未与块大小完全对齐时发生的情况;以及块模式 ,用于确定密码将使用哪种方案来避免相同的输入块被加密到相同的输出块。 块模式通常需要使用称为初始化向量的“起始状态”进行初始化 (您可以使用默认状态“全零”,但这不太安全)。

所以你需要做两件事:

  • 您需要使用填充方案和块模式初始化密码,例如“AES / CBC / PKCS5PADDING”

  • 为了提高安全性,您通常还会设置(并在数据之前传输)随机初始化向量。 有关更多信息, 请参阅此示例

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM