admin管理员组

文章数量:1639831

1.背景

之前为某同事写了一个很小的工具,用于:批量将指定目录下的文件进行AES加密和解密。

同事用了一段时间。

今天同事说:报错了。

2.报错信息

Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
    at com.sun.crypto.provider.CipherCore.doFinal(CipherCore.java:956)
    at com.sun.crypto.provider.CipherCore.doFinal(CipherCore.java:845)
    at com.sun.crypto.provider.AESCipher.engineDoFinal(AESCipher.java:446)
    at javax.crypto.Cipher.doFinal(Cipher.java:2165)
    ...

3.错误分析

4.1.日志分析

从日志很容易分析出报错原因:内存溢出

找到错误代码之处:

/**
 * 对byte[]进行AES加密解密
 * @param strKey 密钥
 * @param encryptType 加密还是解密:Cipher.ENCRYPT_MODE  Cipher.DECRYPT_MODE
 * @param oriBytes 原始文件的byte[]数组
 * @return 加密或解密后的文件byte[]数组
 */
private static byte[] AESEncodeAndDecode(String strKey,int encryptType, byte[] oriBytes){
    byte[] result = null;
    try {
        //指定为AES加密,不区分大小写
        KeyGenerator keygen = KeyGenerator.getInstance(AES);
        //根据传入的key,生成随机流
        keygen.init(128,new SecureRandom(strKey.getBytes()));
        //产生原始对称key
        SecretKey originKey = keygen.generateKey();
        //获取原始对称key的字节数组
        byte[] keyBytes = originKey.getEncoded();
        //生成AES密钥
        SecretKey key = new SecretKeySpec(keyBytes,AES);
        //生成密码器
        Cipher cipher = Cipher.getInstance(AES);
        //初始化为加密器
        cipher.init(encryptType,key);
        //加密
        result = cipher.doFinal(oriBytes);
    } catch (Exception e) {
        //...
    } finally{
        //...
    }
    return result;
}

具体报错代码:

//加密
result = cipher.doFinal(oriBytes);
3.2.当前加密策略分析

当前的加密策略:

  1. 将文件转换成byte[]。
  2. 对byte[]数组进行AES加密或解密。
  3. 将加密或解密完成的byte[]转换成文件。
3.3.分析结论

结合报错信息与加密策略,分析得出:

  1. 报错原因:内存溢出
  2. 具体原因:在使用Cipher进行加密解密时,处理的byte[]数组超出了内存限制。

验证:

让同事使用较小的文件进行加解密,并未报错。

4.错误解决

由上面的结论可知,原来的加密策略并不可用。因为,只要文件超过一定大小,都会内存溢出。

因此,尝试采用流的方式进行加密,即:CipherOutputStreamCipherInputStream

对加密进行测试,代码如下:

/**
 * 通过AES 以流的形式 对文件进行加密
 * @param strKey
 * @param oFileName 源文件路径
 * @param rFileName 目标文件路径
 * @return
 */
public static boolean AESEncodeFileByStream(String strKey, String oFileName, String rFileName) throws Exception {
    //指定为AES加密,不区分大小写
    KeyGenerator keygen = KeyGenerator.getInstance(AES);
    //根据传入的key,生成随机流
    keygen.init(128,new SecureRandom(strKey.getBytes()));
    //产生原始对称key
    SecretKey originKey = keygen.generateKey();
    //获取原始对称key的字节数组
    byte[] keyBytes = originKey.getEncoded();
    //生成AES密钥
    SecretKey key = new SecretKeySpec(keyBytes,AES);
    //生成密码器
    Cipher cipher = Cipher.getInstance(AES);
    //初始化为加密器
    cipher.init(Cipher.ENCRYPT_MODE,key);

    //定义缓存区,用于存储字节流
    byte[] buffer = new byte[1024];
    //定义输入流
    InputStream in = new FileInputStream(oFileName);
    //定义输出流
    OutputStream out = new FileOutputStream(rFileName);
    //定义加密输出流
    CipherOutputStream cipherOut = new CipherOutputStream(out,cipher);
    //定义游标
    int index;
    //读写
    while ((index = in.read(buffer)) != -1){
        cipherOut.write(buffer,0,index);
    }
    //关闭IO流
    cipherOut.close();
    in.close();
    return true;
}

经测试,分别对大小为900MB2.38GB的文件进行加密,均成功。

5.总结

从这个问题,看文件和文件流

  • 直接处理文件:需要把整个文件加载到内存中,当文件很大时,这种方式十分不可行。
  • 文件流处理文件:能够分阶段将相应的数据写入缓冲区,不仅能够解决内存溢出的问题,而且极大地提高了相应的操作效率。

本文标签: 内存加密解密AESOutOfMemoryErrorSpace