Java DES从文件加密/解密

Java DES从文件加密/解密,java,security,base64,des,Java,Security,Base64,Des,我正在编写一个程序,在这个程序中,我获取一个字符串,对其进行加密,然后将其写入一个文件。然后,我从文件中读取字符串,解密它,然后修改它。以下是我的DES加密/解密代码: /* class for crypting and decrypting a file */ class DESEncrypter { private Cipher encryptionCipher; private Cipher decryptionCipher; public DESEncrypter (SecretKey

我正在编写一个程序,在这个程序中,我获取一个字符串,对其进行加密,然后将其写入一个文件。然后,我从文件中读取字符串,解密它,然后修改它。以下是我的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);
}
} 
当服务器运行时,我可以从文件中修改该字符串(多次运行该函数)。问题是当我关闭服务器,然后再次打开它时,因为我得到了错误: 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); 
为什么我会犯这样的错误?我做错了什么?我必须以其他方式将加密字符串写入文件

稍后编辑:

我更改了从文件中读取字符串、解密、修改、加密然后写入文件的函数

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);
}
} 
在System.out.println的“on:”处,我从文件中读取的内容完全是我加密编写的内容,没有任何空格或新行。如果我用read(buffer)读取,其中buffer是byte[],这似乎会增加很多空间

虽然我已经做了所有这些修改,但仍然会出现错误javax.crypto.BadPaddingException:给定的最后一个块没有正确填充


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

我想是在初始化阶段

SecureRandom sr=新的SecureRandom()

cipher.init(cipher.DECRYPT_模式,desKey,sr)

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

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

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

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

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

这里有一些东西

private static String bytes2String(byte[] bytes)
是不可靠的,在此方法中,您正在将一个字节强制转换为一个字符,因此这里没有指定字符编码。要将字节转换为字符,只需使用字符串构造函数,该构造函数接受字节数组和编码。e、 g

    byte[] tmp = new byte[10];      
    String a = new String(tmp, "UTF-8");
小心使用BufferedReaders+.readLine()-这将在您读取文件时从文件中删除任何换行符,除非您将它们重新添加到缓冲区中。虽然我不认为这是你的问题

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

对以后编辑的响应:

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

    int ch;
    while ((ch = fis.read()) != -1)
    {
        sb.append((char)ch);
输入流正在重新调整字节,字节不是字符,仅将其强制转换为一个将导致问题。使用加密时,加密操作的输出是二进制数据,而解密操作的输入也是二进制数据。您正在加密文本这一事实是您在加密发生之前和解密发生之后要处理的事情。您的基本操作应该遵循以下步骤

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

  • 从文件中读取字节
  • 将字节传递给解密例程(作为字节!不涉及字符串/文本)
  • 取出out put字节并使用新字符串(byte[]bytes,String charsetName)重新构造字符串,指定与前面相同的编码
您可能会发现以下(未经测试,但应该有效)方法很有用:

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编码。

我的第一个猜测是您的读取方法。首先,使用字符串连接的方式是不好的做法。使用StringBuilder。其次,对二进制数据使用readline。第三,将(
getBytes
)编码为UTF8。请注意,Java标准是UTF16。这可能会中断您的读取。@Fildor-加密数据是Base64编码的,因此它不是二进制数据。此外,只要使用正确的编码进行编码和解码,getBytes()就可以了。java在内部使用UTF-16,但这与此无关。是的,它是一个字符串。但是字符串表示二进制数据。如果他把它读到一个字节[],然后创建一个字符串,那么至少在调试时他会有更多的控制权。新行将被删除。但是换行符在这里可能不是新行。换行符丢失不会影响加密过程。我已经完成了您所说的修改(我添加了最后一个编辑部分),但仍然得到了相同的例外。非常感谢。我自己也找到了解决办法,但就像你的例子一样。
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();
        }
    }           
}