DES从文件加密/解密

时间:2012-12-13 21:47:19

标签: java security base64 des

我正在写一个程序,我拿一个字符串,加密它然后把它写在一个文件中。然后,我从文件中读取字符串,解密然后修改它。这是我的DES加密/解密代码:

/* class for crypting and decrypting a file */
class DESEncrypter
{
private Cipher encryptionCipher;
private Cipher decryptionCipher;

public DESEncrypter (SecretKey key) throws Exception
{
encryptionCipher = Cipher.getInstance("DES/ECB/PKCS5Padding");
encryptionCipher.init(Cipher.ENCRYPT_MODE, key);
decryptionCipher = Cipher.getInstance("DES/ECB/PKCS5Padding");
decryptionCipher.init(Cipher.DECRYPT_MODE, key);
}

/* write to 'out' the encryption of the information read from 'in' */
public String encrypt(String unencryptedString)
{
    String encryptedString = "";

    try {
        byte[] unencryptedByteArray = unencryptedString.getBytes("UTF8");

        byte[] encryptedBytes = this.encryptionCipher.doFinal(unencryptedByteArray);

        encryptedString = new sun.misc.BASE64Encoder().encode(encryptedBytes);

    } catch (Exception ex) {
        Logger.getLogger(DESEncrypter.class.getName()).log(Level.SEVERE, null, ex);
    }

    return encryptedString;
}

private static String bytes2String(byte[] bytes)
{

    StringBuffer stringBuffer = new StringBuffer();
    for (int i = 0; i < bytes.length; i++) 
    {
        stringBuffer.append((char) bytes[i]);
    }

    return stringBuffer.toString();
}

/* write to 'out' the information obtained by decrypting the information read from 'in' */
public String decrypt (String encryptedString) throws UnsupportedEncodingException
{
    byte[] unencryptedByteArray = new byte[4096];

    try {
        // Encode bytes to base64 to get a string
        byte[] decodedBytes = new sun.misc.BASE64Decoder().decodeBuffer(encryptedString);

       // Decrypt
       unencryptedByteArray = this.decryptionCipher.doFinal(decodedBytes);     
    } catch (Exception ex) {
        Logger.getLogger(DESEncrypter.class.getName()).log(Level.SEVERE, null, ex);
    }

    return bytes2String(unencryptedByteArray);
}
} 

这是我在文件中编写加密字符串的函数:

public void writeToFileEncrypted(String filename, String owner, String departament)
{
try 
    {
        BufferedReader br = new BufferedReader(new FileReader(new File("files_encrypted")));
        String crypt = "";
        String aux;
        while ((aux = br.readLine()) != null)
        {
            crypt += aux;
        }
        br.close();

        String info = this.server.crypt.decrypt(crypt);
        info += filename + " " + owner + " " + departament + "\n";

        /* delete the old encryption */
        File temp = new File("files_encrypted");
        temp.delete();

        String infoCrypt = this.server.crypt.encrypt(info); 

        File newFiles = new File("files_encrypted");
        if (newFiles.createNewFile() == false) 
        {
    log.severe("Failed to re-create the 'files_encrypted' file when trying to add a new file");
    return; 
        }

        BufferedWriter bw = new BufferedWriter(new FileWriter(newFiles));
        bw.write(infoCrypt);
        bw.close();
    }
    catch (Exception e)
    {
        log.warning("An exception was caught while trying to remove '" + clientName + "' from the banned list");
        e.printStackTrace();
        return;
}
}

当服务器运行时,我可以从文件中修改该String(多次运行该函数)。问题是当我关闭服务器然后再次打开它因为我收到错误: javax.crypto.BadPaddingException:给定最终块未正确填充

这是我在服务器打开时从文件中读取的方式:

BufferedReader br = new BufferedReader(new FileReader(new File("files_encrypted")));
String crypto = new String();
String aux;
while ((aux = br.readLine()) != null)
{
    crypto += aux;
    readBytes++;
}
br.close();
System.out.println(readBytes);
info = this.crypt.decrypt(crypto); 

为什么我会收到错误?我做错了什么?我必须以其他方式在文件中写入加密的String吗?

LATER EDIT:

我已经更改了从文件读取字符串的函数,解密,修改,加密然后将其写入文件。

public void writeToFileEncrypted(String filename, String owner, String departament)
{
try 
    {
        File f = new File("files_encrypted");
        int nrRead = 0;
        String info = null;
        FileInputStream fis = new FileInputStream(f);
        StringBuffer sb = new StringBuffer();
        int ch;
        while ((ch = fis.read()) != -1)
        {
            sb.append((char)ch);
            nrRead++;
        }
        fis.close();

        StringBuilder sba = null;
        if (nrRead != 0)
        {
            info = this.server.crypt.decrypt(new String(sb.toString().getBytes("UTF-8"), "UTF-8"));
            sba = new StringBuilder(info);
            sba.append(filename + " " + owner + " " + departament + " ");
        }
        else
        {
            sba = new StringBuilder(filename + " " + owner + " " + departament + " ");
        }

        /* delete the old encryption */
        File temp = new File("files_encrypted");
        temp.delete();
        //System.out.println("before: " + sba.toString());
        String infoCrypt = this.server.crypt.encrypt(sba.toString()); 
        //System.out.println("after: " + infoCrypt);
        File newFiles = new File("files_encrypted");
        if (newFiles.createNewFile() == false) 
        {
    log.severe("Failed to re-create the 'files_encrypted' file when trying to add a new file");
    return; 
        }

        FileOutputStream fos = new FileOutputStream(newFiles);
        fos.write(infoCrypt.getBytes("UTF-8"));
        fos.flush();
        fos.close();
    }
    catch (Exception e)
    {
        log.warning("An exception was caught while trying to remove '" + clientName + "' from the banned list");
        e.printStackTrace();
        return;
}
}

我还修改了第一次打开服务器时从文件中读取信息的位置:

FileInputStream fis = new FileInputStream(f);
StringBuffer sb = new StringBuffer();
int ch;
while ((ch = fis.read()) != -1)
{
    sb.append((char)ch);
    readBytes++;
}

fis.close();
if (readBytes != 0)
{
    System.out.println("on: " + sb.toString());
    info = this.crypt.decrypt(new String(sb.toString().getBytes("UTF-8"), "UTF-8"));                
    System.out.println("load: " + info);
}
} 

在带有“on:”的System.out.println中,我从文件中读取的内容正是我加密编写的内容,没有任何空格或新行。如果我用read(缓冲区)读取缓冲区是byte [],那么它似乎会增加很多空格。

虽然我做了所有这些修改但仍然得到错误javax.crypto.BadPaddingException:给定最终块没有正确填充

有人知道这里发生了什么吗?

4 个答案:

答案 0 :(得分:1)

你有几个问题。读取和解密过程应与加密和写入过程对称。但

  • 使用getBytes("UTF8")将您的String转换为byte [],这很好,但是您不使用new String(byte[], "UTF8")来执行相反的操作。
  • 您将整个字符串写入文件,包括潜在的换行符,但是您逐行读取并连接每一行,从而在此过程中丢失换行符。您必须阅读已写入的每个字符。

此外,不应该依赖未记录的,不受支持的类,如sun.misc.Base64Encoder / Decoder。使用Apache commons-codec查找记录的Base64编码,保证在下一个JDK出现时仍然存在,并且可以在每个JVM上使用,包括非Sun JVM。

答案 1 :(得分:1)

这里有一些事情。

private static String bytes2String(byte[] bytes)

狡猾,你在这个方法中将一个字节转换为char,因此这里没有指定字符编码。要将字节转换为字符,您应该只使用带有字节数组和编码的String构造函数。 e.g。

    byte[] tmp = new byte[10];      
    String a = new String(tmp, "UTF-8");

小心使用BufferedReaders + .readLine() - 除非将它们添加回缓冲区,否则在读取文件时会从文件中删除任何换行符。虽然我不认为这是你的问题。

但我认为简化代码的最佳方法是通过OutputStream将编码的字节直接写入文件。除非您需要通过不喜欢二进制数据的传输发送文件的内容,否则不需要base64编码。只需使用Input / OutputStreams将加密的字节直接写入磁盘。

对后期编辑的回应:

您仍然混淆使用二进制数据(字节)和字符数据(字符串/字符)。你做不到这样的事情:

    int ch;
    while ((ch = fis.read()) != -1)
    {
        sb.append((char)ch);

输入流是重新调整字节,一个字节不是一个字符,只是将其转换为一个字符会导致问题。当使用加密时,加密操作的输出是二进制数据,并且解密操作的输入也是二进制数据。您正在加密文本的事实是您在加密发生之前以及解密发生之后处理的事情。你的基本操作应该遵循以下几行。

  • 获取要加密的文本并将其转换为字节,在String上使用.getBytes(String charsetName)指定编码。
  • 将这些字节传递到加密例程
  • 将结果字节直接写入磁盘

要解密:

  • 从文件中读取字节
  • 将字节传递给解密例程(作为字节!不涉及字符串/文本)
  • 取出输出字节并使用新的String(byte [] bytes,String charsetName)重新构造String,指定与之前相同的编码。

您可能会发现以下(未经测试,但应该可行)方法有用:

public byte[] readBinaryFile(File f) throws IOException
{       
    byte[] contents = new byte[(int)f.length()];
    BufferedInputStream bis = null;
    try
    {
        bis = new BufferedInputStream(new FileInputStream(f));
        DataInputStream dis = new DataInputStream(bis);
        dis.readFully(contents);
    }
    finally
    {
        if(bis != null)
        {
            bis.close();
        }
    }           
    return contents;            
}

public void writeBinaryFile(byte[] contents, File f) throws IOException
{
    BufferedOutputStream bos = null;
    try
    {
        bos = new BufferedOutputStream(new FileOutputStream(f));
        bos.write(contents);
    }
    finally
    {
        if(bos != null)
        {
            bos.close();
        }
    }           
}

因此,您还需要更改加密和解密方法的接口和内部,以便它们获取并返回字节数组,并放弃base64编码。

答案 2 :(得分:0)

我认为这是在初始化

SecureRandom sr = new SecureRandom();
   cipher.init(Cipher.DECRYPT_MODE,desKey,sr);

答案 3 :(得分:0)

不确定这是主要问题,但是当您从decrypt()返回解密的字符串时,您应该使用:

return new String(unencryptedByteArray, "UTF-8");