【问题标题】:AES encryption, got extra trash characters in decrypted fileAES 加密,解密文件中有多余的垃圾字符
【发布时间】:2015-03-13 10:02:32
【问题描述】:

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

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

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

Android 应用日志部分:

public class FileLogger {

//file and folder name
public static String LOG_FILE_NAME = "my_log.txt";
public static String LOG_FOLDER_NAME = "my_log_folder";

static SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd_HH-mm-ss_SSS");

//My secret key, 16 bytes = 128 bit
static byte[] key = {1,2,3,4,5,6,7,8,9,0,1,2,3,4,5,6};

//Appends to a log file, using encryption
public static void appendToLog(Context context, Object msg) {

    String msgStr;
    String timestamp = "t:" + formatter.format(new java.util.Date());

    msgStr = msg + "|" + timestamp + "\n";

    File sdcard = Environment.getExternalStorageDirectory();
    File dir = new File(sdcard.getAbsolutePath() + "/" + LOG_FOLDER_NAME);
    if (!dir.exists()) {
        dir.mkdir();
    }

    File encryptedFile = new File(dir, LOG_FILE_NAME);

    try {
        
        //Encryption using my key above defined
        Key secretKey = new SecretKeySpec(key, "AES");
        Cipher cipher = Cipher.getInstance("AES");
        cipher.init(Cipher.ENCRYPT_MODE, secretKey);

        byte[] outputBytes = cipher.doFinal(msgStr.getBytes());

        //Writing to the file using append mode
        FileOutputStream outputStream = new FileOutputStream(encryptedFile, true);
        outputStream.write(outputBytes);
        outputStream.close();
        
        
    } catch (FileNotFoundException e) {
        e.printStackTrace();
    } catch (IOException e) {
        e.printStackTrace();
    } catch (NoSuchAlgorithmException e) {
        e.printStackTrace();
    } catch (NoSuchPaddingException e) {
        e.printStackTrace();
    } catch (IllegalBlockSizeException e) {
        e.printStackTrace();
    } catch (BadPaddingException e) {
        e.printStackTrace();
    } catch (InvalidKeyException e) {
        e.printStackTrace();
    }

}

}

这是解密JAVA程序:

public class Main {

    

//output file name after decryption
private static String decryptedFileName;
//input encrypted file
private static String fileSource;
//a prefix tag for output file name
private static String outputFilePrefix = "decrypted_";
//My key for decryption, its the same as in the encrypter program.
static byte[] key = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6 };

//Decrypting function
public static void decrypt(byte[] key, File inputFile, File outputFile) throws Exception {
    try {

        Key secretKey = new SecretKeySpec(key, "AES");
        Cipher cipher = Cipher.getInstance("AES");
        cipher.init(Cipher.DECRYPT_MODE, secretKey);

        FileInputStream inputStream = new FileInputStream(inputFile);
        byte[] inputBytes = new byte[(int) inputFile.length()];
        inputStream.read(inputBytes);

        byte[] outputBytes = cipher.doFinal(inputBytes);

        FileOutputStream outputStream = new FileOutputStream(outputFile, true);
        outputStream.write(outputBytes);

        inputStream.close();
        outputStream.close();

    } catch (Exception ex) {
        ex.printStackTrace();
    }
}

//first argument is the intput file source
public static void main(String[] args) {

    if (args.length != 1) {
        System.out.println("Add log file name as a parameter.");

    } else {
        fileSource = args[0];

        try {
            File sourceFile = new File(fileSource);
            if (sourceFile.exists()) {
                
                //Decrption
                decryptedFileName = outputFilePrefix + sourceFile.getName();
                File decryptedFile = new File(decryptedFileName);
                decrypt(key, sourceFile, decryptedFile);
            } else {
                System.out.println("Log file not found: " + fileSource);
            }

        } catch (Exception e) {
            e.printStackTrace();
        }

        System.out.println("Decryption done, output file: " + decryptedFileName);
    }

}

}

输出解密后的日志(用notepad++打开):

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

这是我第一次尝试使用 encrypt -decrypt,我做错了什么? 有什么想法吗?

【问题讨论】:

    标签: java android encryption cryptography


    【解决方案1】:

    AES 是一种仅适用于块的块密码。您要加密的明文可以是任意长度,因此密码必须始终填充明文以将其填充到块大小的倍数(或者当它已经是块大小的倍数时添加一个完整的块)。在此 PKCS#5/PKCS#7 填充中,每个填充字节表示填充字节数。

    简单的解决方法是在解密期间迭代outputBytes 并删除那些总是在下一行的填充字节。一旦您使用多行日志消息或使用语义安全模式(稍后会详细介绍),这将中断。

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

    您当前使用Cipher.getInstance("AES");,它是Cipher.getInstance("AES/ECB/PKCS5Padding"); 的非完全限定版本。 ECB 模式在语义上并不安全。它只是用 AES 和密钥加密每个块(16 个字节)。因此,相同的块在密文中将是相同的。这尤其糟糕,因为一些日志消息以相同的开头,攻击者可能能够区分它们。这也是为什么整个文件的解密工作尽管被分块加密的原因。您应该使用随机 IV 的 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”算法字符串来指定正确的填充,问题就解决了。没有多余的字符
    【解决方案2】:

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

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2013-07-25
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-01-31
      • 1970-01-01
      • 2020-03-26
      • 1970-01-01
      相关资源
      最近更新 更多