Java AES加密,在解密文件中获得额外的垃圾字符

Java AES加密,在解密文件中获得额外的垃圾字符,java,android,encryption,cryptography,Java,Android,Encryption,Cryptography,我正在android应用程序中制作调试日志功能。 我有一个简单的类,它使用128位AES加密记录到.txt文件 记录完成后,我用一个简单的JAVA程序解密记录的文件 问题是当我解密加密的日志时,我得到了一些奇怪的内容,我也得到了加密的内容,但还有一些额外的字符,见下文 Android应用程序日志部分: 这是解密程序JAVA程序: 使用记事本++打开的输出解密日志: 有有效的内容,但您也可以看到额外的鞭打字符。如果我用默认的windows文本编辑器打开,我也会得到鞭打字符,但不同的字符 这是我第一

我正在android应用程序中制作调试日志功能。 我有一个简单的类,它使用128位AES加密记录到.txt文件

记录完成后,我用一个简单的JAVA程序解密记录的文件

问题是当我解密加密的日志时,我得到了一些奇怪的内容,我也得到了加密的内容,但还有一些额外的字符,见下文

Android应用程序日志部分:

这是解密程序JAVA程序:

使用记事本++打开的输出解密日志:

有有效的内容,但您也可以看到额外的鞭打字符。如果我用默认的windows文本编辑器打开,我也会得到鞭打字符,但不同的字符

这是我第一次尝试加密-解密,我做错了什么?
有什么想法吗?

您已经用不同的加密上下文加密了每条日志消息。当您对cipher对象调用doFinal方法时,明文被填充为16的倍数。实际上,您的日志文件是由许多小的加密消息组成的序列。但是,在解密时,您将忽略这些消息边界,并将文件视为单个加密消息。结果是填充字符没有被正确剥离。您看到的“垃圾”字符很可能是这些填充字节。您需要重新设计日志文件格式,或者保留消息边界,以便解密程序能够发现它们,或者完全消除它们


另外,不要在Java加密中使用默认值:它们是不可移植的。例如,Cipher.getInstance采用alg/mode/padding格式的字符串。始终指定所有三个。我注意到您还使用了默认的no args String.getBytes方法。始终指定一个字符集,并且几乎总是UTF8是最佳选择。

您已经使用不同的加密上下文对每个日志消息进行了加密。当您对cipher对象调用doFinal方法时,明文被填充为16的倍数。实际上,您的日志文件是由许多小的加密消息组成的序列。但是,在解密时,您将忽略这些消息边界,并将文件视为单个加密消息。结果是填充字符没有被正确剥离。您看到的“垃圾”字符很可能是这些填充字节。您需要重新设计日志文件格式,或者保留消息边界,以便解密程序能够发现它们,或者完全消除它们


另外,不要在Java加密中使用默认值:它们是不可移植的。例如,Cipher.getInstance采用alg/mode/padding格式的字符串。始终指定所有三个。我注意到您还使用了默认的no args String.getBytes方法。始终指定一个字符集,并且几乎总是UTF8是最佳选择。

AES是一种仅适用于块的分组密码。要加密的明文可以是任意长度,因此密码必须始终填充明文,使其填充到块大小的倍数,或者在已经是块大小的倍数时添加完整块。在此PKCS5/PKCS7填充中,每个填充字节表示填充的字节数

简单的解决方法是在解密过程中迭代outputBytes,并删除总是在下一行的填充字节。当您使用多行日志消息或稍后使用语义安全模式时,这将立即中断

更好的解决方法是在消息之前写入每个日志消息的字节数,读取该字节数并仅解密该字节数。这也可能更容易用文件流实现

您当前使用的是Cipher.getInstanceAES;这是Cipher.getInstanceAES/ECB/PKCS5Padding;的非完全限定版本;。ECB模式在语义上不安全。它只是用AES和密钥对每个块加密16个字节。所以相同的块在密文中也是相同的。这尤其糟糕,因为某些日志消息的开头相同,攻击者可能能够区分它们。这也是为什么尽管分块加密,但整个文件的解密仍然有效的原因。你应该使用随机静脉注射的CBC模式

以下是在CBC模式下正确使用AES的一些示例代码,其中使用了随机IV流:

private static SecretKey key = generateAESkey();
private static String cipherString = "AES/CBC/PKCS5Padding";

public static void main(String[] args) throws Exception {
    ByteArrayOutputStream log = new ByteArrayOutputStream();
    appendToLog("Test1", log);
    appendToLog("Test2 is longer", log);
    appendToLog("Test3 is multiple of block size!", log);
    appendToLog("Test4 is shorter.", log);

    byte[] encLog = log.toByteArray();

    List<String> logs = decryptLog(new ByteArrayInputStream(encLog));

    for(String logLine : logs) {
        System.out.println(logLine);
    }
}

private static SecretKey generateAESkey() {
    try {
        return KeyGenerator.getInstance("AES").generateKey();
    } catch (NoSuchAlgorithmException e) {
        e.printStackTrace();
    }
    return null;
}

private static byte[] generateIV() {
    SecureRandom random = new SecureRandom();
    byte[] iv = new byte[16];
    random.nextBytes(iv);
    return iv;
}

public static void appendToLog(String s, OutputStream os) throws Exception {
    Cipher cipher = Cipher.getInstance(cipherString);
    byte[] iv = generateIV();
    cipher.init(Cipher.ENCRYPT_MODE, key, new IvParameterSpec(iv));
    byte[] data = cipher.doFinal(s.getBytes("UTF-8"));
    os.write(data.length);
    os.write(iv);
    os.write(data);
}

public static List<String> decryptLog(InputStream is) throws Exception{
    ArrayList<String> logs = new ArrayList<String>();
    while(is.available() > 0) {
        int len = is.read();
        byte[] encLogLine = new byte[len];
        byte[] iv = new byte[16];
        is.read(iv);
        is.read(encLogLine);

        Cipher cipher = Cipher.getInstance(cipherString);
        cipher.init(Cipher.DECRYPT_MODE, key, new IvParameterSpec(iv));
        byte[] data = cipher.doFinal(encLogLine);
        logs.add(new String(data, "UTF-8"));
    }
    return logs;
}

AES是一种仅适用于块的分组密码。要加密的明文可以是任意长度,因此密码必须始终填充明文,使其填充到块大小的倍数,或者在已经是块大小的倍数时添加完整块。在此PKCS5/PKCS7填充中,每个填充字节表示填充的字节数

简单的解决方法是在解密过程中迭代outputBytes,并删除总是在下一行的填充字节。当您使用多行日志消息或稍后使用语义安全模式时,这将立即中断

更好的解决方法是在日志消息之前写入每个日志消息的字节数 重新读取消息,读取该消息并仅解密那么多字节。这也可能更容易用文件流实现

您当前使用的是Cipher.getInstanceAES;这是Cipher.getInstanceAES/ECB/PKCS5Padding;的非完全限定版本;。ECB模式在语义上不安全。它只是用AES和密钥对每个块加密16个字节。所以相同的块在密文中也是相同的。这尤其糟糕,因为某些日志消息的开头相同,攻击者可能能够区分它们。这也是为什么尽管分块加密,但整个文件的解密仍然有效的原因。你应该使用随机静脉注射的CBC模式

以下是在CBC模式下正确使用AES的一些示例代码,其中使用了随机IV流:

private static SecretKey key = generateAESkey();
private static String cipherString = "AES/CBC/PKCS5Padding";

public static void main(String[] args) throws Exception {
    ByteArrayOutputStream log = new ByteArrayOutputStream();
    appendToLog("Test1", log);
    appendToLog("Test2 is longer", log);
    appendToLog("Test3 is multiple of block size!", log);
    appendToLog("Test4 is shorter.", log);

    byte[] encLog = log.toByteArray();

    List<String> logs = decryptLog(new ByteArrayInputStream(encLog));

    for(String logLine : logs) {
        System.out.println(logLine);
    }
}

private static SecretKey generateAESkey() {
    try {
        return KeyGenerator.getInstance("AES").generateKey();
    } catch (NoSuchAlgorithmException e) {
        e.printStackTrace();
    }
    return null;
}

private static byte[] generateIV() {
    SecureRandom random = new SecureRandom();
    byte[] iv = new byte[16];
    random.nextBytes(iv);
    return iv;
}

public static void appendToLog(String s, OutputStream os) throws Exception {
    Cipher cipher = Cipher.getInstance(cipherString);
    byte[] iv = generateIV();
    cipher.init(Cipher.ENCRYPT_MODE, key, new IvParameterSpec(iv));
    byte[] data = cipher.doFinal(s.getBytes("UTF-8"));
    os.write(data.length);
    os.write(iv);
    os.write(data);
}

public static List<String> decryptLog(InputStream is) throws Exception{
    ArrayList<String> logs = new ArrayList<String>();
    while(is.available() > 0) {
        int len = is.read();
        byte[] encLogLine = new byte[len];
        byte[] iv = new byte[16];
        is.read(iv);
        is.read(encLogLine);

        Cipher cipher = Cipher.getInstance(cipherString);
        cipher.init(Cipher.DECRYPT_MODE, key, new IvParameterSpec(iv));
        byte[] data = cipher.doFinal(encLogLine);
        logs.add(new String(data, "UTF-8"));
    }
    return logs;
}

对我来说,我必须使用RSA/ECB/PKCS1Padding而不是默认的RSA算法字符串来指定正确的填充,问题就解决了。没有更多的额外字符我必须使用RSA/ECB/PKCS1Padding来指定正确的填充,而不是默认的RSA算法字符串,问题就解决了。没有更多的字符
private static SecretKey key = generateAESkey();
private static String cipherString = "AES/CBC/PKCS5Padding";

public static void main(String[] args) throws Exception {
    ByteArrayOutputStream log = new ByteArrayOutputStream();
    appendToLog("Test1", log);
    appendToLog("Test2 is longer", log);
    appendToLog("Test3 is multiple of block size!", log);
    appendToLog("Test4 is shorter.", log);

    byte[] encLog = log.toByteArray();

    List<String> logs = decryptLog(new ByteArrayInputStream(encLog));

    for(String logLine : logs) {
        System.out.println(logLine);
    }
}

private static SecretKey generateAESkey() {
    try {
        return KeyGenerator.getInstance("AES").generateKey();
    } catch (NoSuchAlgorithmException e) {
        e.printStackTrace();
    }
    return null;
}

private static byte[] generateIV() {
    SecureRandom random = new SecureRandom();
    byte[] iv = new byte[16];
    random.nextBytes(iv);
    return iv;
}

public static void appendToLog(String s, OutputStream os) throws Exception {
    Cipher cipher = Cipher.getInstance(cipherString);
    byte[] iv = generateIV();
    cipher.init(Cipher.ENCRYPT_MODE, key, new IvParameterSpec(iv));
    byte[] data = cipher.doFinal(s.getBytes("UTF-8"));
    os.write(data.length);
    os.write(iv);
    os.write(data);
}

public static List<String> decryptLog(InputStream is) throws Exception{
    ArrayList<String> logs = new ArrayList<String>();
    while(is.available() > 0) {
        int len = is.read();
        byte[] encLogLine = new byte[len];
        byte[] iv = new byte[16];
        is.read(iv);
        is.read(encLogLine);

        Cipher cipher = Cipher.getInstance(cipherString);
        cipher.init(Cipher.DECRYPT_MODE, key, new IvParameterSpec(iv));
        byte[] data = cipher.doFinal(encLogLine);
        logs.add(new String(data, "UTF-8"));
    }
    return logs;
}