65.9K
CodeProject 正在变化。 阅读更多。
Home

CryptBox

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.45/5 (9投票s)

2013年2月18日

GPL3

5分钟阅读

viewsIcon

25972

downloadIcon

1350

CryptBox 帮助使用基于密码的加密 (PBE) 来加密和解密文件

引言

使用基于密码的加密 (PBE) 进行文件加密和解密。

基于密码的加密 (PBE) 从密码派生加密密钥。为了使攻击者从密码到密钥的转换过程非常耗时,大多数 PBE 实现(如下所示)会混合一个称为“盐”的随机数来创建密钥。

j2se1.4 支持 PBE 算法(SunJCE 和其他提供商的后续版本支持其他算法)。

有时,我们希望有效地选择加密密钥。我们可能想根据自己输入的密码短语来加密文件,以便我们能够记住它。在这种情况下,我们希望唯一保密的信息就是密码短语。

从用户生成的密码短语生成密钥的技术通常称为基于密码的加密 (PBE)。正如您可能想象的那样,这充满了挑战。特别是:

  • 我们的要求和安全要求通常会发生冲突:我们需要一个易于记忆的密码短语,或者至少是由可识别字符组成且足够短以便写下来的密码短语;但根据当今的标准进行安全加密,我们需要至少 128 位强随机位(理想情况下更多);
  • 基于密码的加密通常用于攻击者可以反复尝试猜测密码而不被发现,并且超出合法发送者/接收者控制的应用程序(如果密码用于登录服务器,则可以检测到如此多的无效尝试,在最坏的情况下可以关闭服务器以阻止进一步的尝试;但如果窃听者复制了我们使用的加密 ZIP 文件,我们将永远不知道他们拥有一个拥有 10 万个处理器的僵尸网络正在尝试暴力破解密码,并且他们可以长时间地这样做)。

PBE (基于密码的加密) = 哈希 + 对称加密

将一个 64 位随机数(盐)添加到密码中,并使用消息摘要算法(例如 MD5)进行哈希。
密码哈希的次数由迭代计数决定。添加随机数并多次哈希会扩大密钥空间。

在设置密码或哈希迭代次数以加密文件时要小心。

如果丢失了密码或哈希迭代次数,则可能无法找回任何内容。
这将导致加密文件解密失败,因此加密文件可能永远无法使用。

背景

我想保护我的文件不被他人打开。那时我才想到加密我的文件并将其存储在硬盘上。即使有人想打开它,他/她也必须先解密文件,然后才能打开。由于我使用密码进行文件加密,并且还添加了密码哈希迭代,因此任何人都不容易解密。

我的工作思路来自 http://cs.saddleback.edu/rwatkins/CS4B/Crypto/FileEncryptor.html,所有的功劳都归功于它在加密和解密方面的贡献。

屏幕截图

  • 可以通过两种方式将文件添加到 CryptBox 中进行加密或解密。
    • 要么单击“选择”按钮。选择“加密”单选按钮将帮助用户选择要加密的任何文件。选择“解密”单选按钮将帮助用户选择任何扩展名为“enc”的加密文件。
    • 通过从 Windows 资源管理器拖放一个或多个文件。CryptBox 将根据所选的单选按钮执行加密或解密。
  • 选定的文件将在“文件”列中显示完整路径。
  • 状态将设置为“排队中”,当加密或解密开始时,它将根据进程状态而变化。
  • “子状态”列将显示正在进行的中间工作。
  • 用户选择文件后,单击“开始”按钮。将弹出一个窗口。
  • CryptBox 使用 PBEWithMD5AndDES 作为消息摘要算法。
  • 输入密码哈希迭代次数,默认为 100。密码哈希的次数由迭代次数决定。
  • 输入用于保护文件的密码,该密码也将用于加密。
  • “重复密码”应与“密码”完全相同,以检查您想要的密码是否一致。
  • 解密加密文件时,用户需要与加密该文件时使用的消息摘要算法、哈希迭代次数和密码相同。

  • 在加密或解密文件时,用户可以通过“详细信息”按钮查看操作的详细状态。它还会显示完成百分比的状态进度条。

 

  • 如果在解密文件时用户提供了错误的消息摘要算法、密码哈希迭代次数或密码(与加密文件不匹配),则 CryptBox 会要求重新输入这些详细信息,以便能够成功解密。
  • 在加密时,为文件提供的任何消息摘要算法、密码哈希迭代次数或密码,在解密时都必须与加密时相同。

使用代码

加密方法

public int EncryptFile(String strFileName, String strMessageDigestAlgorithm, int intPasswordHashIteration, String strPassword, String strExtension) throws Exception
{
    FileInputStream objFileInputStream;
    FileOutputStream objFileOutputStream;

    try
    {
        // File to encrypt.  It does not have to be a text file!
        // Password must be at least 8 characters (bytes) long
        objFileInputStream = new FileInputStream(strFileName);
        objFileOutputStream = new FileOutputStream(strFileName + "." + strExtension);

        // Use PBEKeySpec to create a key based on a password.
        // The password is passed as a character array
        PBEKeySpec objPBEKeySpec = new PBEKeySpec(strPassword.toCharArray());
        //SecretKeyFactory objSecretKeyFactory = SecretKeyFactory.getInstance("PBEWithMD5AndDES");
        SecretKeyFactory objSecretKeyFactory = SecretKeyFactory.getInstance(strMessageDigestAlgorithm);
        SecretKey objSecretKey = objSecretKeyFactory.generateSecret(objPBEKeySpec);

        // PBE = hashing + symmetric encryption.  A 64 bit random
        // number (the salt) is added to the password and hashed
        // using a Message Digest Algorithm (MD5 in this example.).
        // The number of times the password is hashed is determined
        // by the interation count.  Adding a random number and
        // hashing multiple times enlarges the key space.
        byte[] byteSalt = new byte[8];
        Random objRandom = new Random();
        objRandom.nextBytes(byteSalt);
        //int intIteration = 100;
        int intIteration = intPasswordHashIteration;

        //Create the parameter spec for this salt and interation count
        PBEParameterSpec objPBEParameterSpec = new PBEParameterSpec(byteSalt, intIteration);

        // Create the cipher and initialize it for encryption.
        Cipher objCipher = Cipher.getInstance("PBEWithMD5AndDES");
        objCipher.init(Cipher.ENCRYPT_MODE, objSecretKey, objPBEParameterSpec);

        // Need to write the salt to the (encrypted) file.  The
        // salt is needed when reconstructing the key for decryption.
        objFileOutputStream.write(byteSalt);

        // Read the file and encrypt its bytes.
        byte[] byteInput = new byte[64];
        int intRead;
        while ((intRead = objFileInputStream.read(byteInput)) != -1)
        {
            byte[] byteOutput = objCipher.update(byteInput, 0, intRead);
            if (byteOutput != null)
            {
                objFileOutputStream.write(byteOutput);
            }
        }

        byte[] byteOutput = objCipher.doFinal();
        if (byteOutput != null)
        {
            objFileOutputStream.write(byteOutput);
        }

        objFileInputStream.close();
        objFileOutputStream.flush();
        objFileOutputStream.close();

        return 0;
    }
    catch(Exception ex)
    {
        JOptionPane.showMessageDialog(null, ex, "CryptBox Error", JOptionPane.ERROR_MESSAGE);

        return 1;
    }
} 

解密方法

public int DecryptFile(String strFileName, String strMessageDigestAlgorithm, int intPasswordHashIteration, String strPassword, String strExtension) throws Exception
{
    FileInputStream objFileInputStream;
    FileOutputStream objFileOutputStream;

    try
    {
        // File to decrypt.
        // Password must be at least 8 characters (bytes) long
        objFileInputStream = new FileInputStream(strFileName);
        objFileOutputStream = new FileOutputStream(strFileName.substring(0, strFileName.length() - strExtension.length() - 1));

        PBEKeySpec objPBEKeySpec = new PBEKeySpec(strPassword.toCharArray());
        //SecretKeyFactory objSecretKeyFactory = SecretKeyFactory.getInstance("PBEWithMD5AndDES");
        SecretKeyFactory objSecretKeyFactory = SecretKeyFactory.getInstance(strMessageDigestAlgorithm);
        SecretKey objSecretKey = objSecretKeyFactory.generateSecret(objPBEKeySpec);

        // Read in the previouly stored salt and set the iteration count.
        byte[] byteSalt = new byte[8];
        objFileInputStream.read(byteSalt);
        //int intIteration = 100;
        int intIteration = intPasswordHashIteration;

        PBEParameterSpec objPBEParameterSpec = new PBEParameterSpec(byteSalt, intIteration);

        // Create the cipher and initialize it for decryption.
        Cipher objCipher = Cipher.getInstance("PBEWithMD5AndDES");
        objCipher.init(Cipher.DECRYPT_MODE, objSecretKey, objPBEParameterSpec);

        byte[] byteInput = new byte[64];
        int intRead;
        while ((intRead = objFileInputStream.read(byteInput)) != -1)
        {
            byte[] byteOutput = objCipher.update(byteInput, 0, intRead);
            if (byteOutput != null)
            {
                objFileOutputStream.write(byteOutput);
            }
        }

        byte[] byteOutput = objCipher.doFinal();
        if (byteOutput != null)
        {
            objFileOutputStream.write(byteOutput);
        }

        objFileInputStream.close();
        objFileOutputStream.flush();
        objFileOutputStream.close();

        return 0;
    }
    catch(Exception ex)
    {
        JOptionPane.showMessageDialog(null, ex, "CryptBox Error", JOptionPane.ERROR_MESSAGE);

        return 1;
    }
}
  • 这 2 个(即加密和解密)是 CryptBox 中的主要方法。还有一种方法可以在加密或解密后删除前一个文件。
  • 加密或解密所需的时间取决于文件大小。例如,如果一个文件是 1 GB,加密或解密大约需要 8 分钟。
  • CryptBox 使用 SwingWorker 实现,这是一个抽象类。SwingWorker 专为需要在后台线程中运行长时间运行的任务,并在完成时或处理过程中提供 UI 更新的情况而设计。

兴趣点 

如果用户提供了错误的信息来解密加密文件,它将永远不会这样做,除非提供了正确的信息。

历史

这是第一个版本,提交于 2013 年 2 月 18 日。

© . All rights reserved.