使用 AES 和 MAC 进行 Java 加密

2 java encryption cryptography aes hmac

我正在尝试找到一种方法来完成我的 Java 课程作业。我需要加密一个文件,但老实说我并没有真正理解这些说明。我实现了文件加密和解密,但是我怀疑它是否按照说明进行操作。指令如下:

\n\n
    \n
  • 加密分为三个阶段:
  • \n
  • 生成 16 字节随机数据作为 CBC 模式所需的初始向量 (IV)
  • \n
  • 应用 AES 密码,使用 PKCS5 填充方案以 CBC 模式加密文件内容。
  • \n
  • 应用 MAC 密码(例如 \xe2\x80\x9cHmacSHA1\xe2\x80\x9d)来计算封装 IV 和密文的 MAC
  • \n
  • 加密文件将是以下数据的串联: 16 字节 IV || 密文|| 20 字节 HMAC
  • \n
\n\n

我写的代码是这样的,它成功加密了一个文本文件。这是我的应用程序的完整代码。

\n\n
import java.io.FileNotFoundException;\nimport java.io.*;\nimport java.util.Scanner;\n\nimport java.io.File;\nimport java.io.FileInputStream;\nimport java.io.FileOutputStream;\nimport java.security.AlgorithmParameters;\nimport java.security.SecureRandom;\nimport java.security.spec.KeySpec;\n\nimport javax.crypto.Cipher;\nimport javax.crypto.SecretKey;\nimport javax.crypto.SecretKeyFactory;\nimport javax.crypto.spec.IvParameterSpec;\nimport javax.crypto.spec.PBEKeySpec;\nimport javax.crypto.spec.SecretKeySpec;\n\npublic class AESFileEncryption {\n\n/*public AESFileEncryption(String nameoffile){\n\n}\npublic String FileReturn(String filename){\n    String fl = filename;       \n    return fl;      \n}*/\n\npublic static void main(String[] args) throws Exception {\n\n    File f = new File("plainfile.txt");\n    File g = new File("plainfile.txt.8102");\n    File fl = new File("plainfile.txt.8102");\n\n    if(g.exists() && !g.isDirectory()){\n        System.out.println("The file is already encrypted...");\n        String fname = fl.getAbsolutePath();\n        System.out.print("Absolute Encrypted File Pathname => "+ fname);\n        System.exit(0);\n    }       \n    else if(f.exists() && !f.isDirectory()) { \n         System.out.println(" The file is found.The encryption process is going to begin...");\n\n    }       \n    else{\n         System.out.println(" The file is missing!!!!");\n         System.exit(0);\n    }\n\n    // file to be encrypted\n    FileInputStream inFile = new FileInputStream("plainfile.txt");       \n\n    // encrypted file\n    FileOutputStream outFile = new FileOutputStream("plainfile.txt.8102");\n\n\n    // password to encrypt the file\n    Scanner scan= new Scanner(System.in);\n    System.out.println("Enter the password : => ");\n    String password= scan.nextLine();\n\n    //String password = "javapapers";\n\n    // password, iv and salt should be transferred to the other end\n    // in a secure manner\n\n    // salt is used for encoding\n    // writing it to a file\n    // salt should be transferred to the recipient securely\n    // for decryption\n    byte[] salt = new byte[8];\n    SecureRandom secureRandom = new SecureRandom();\n    secureRandom.nextBytes(salt);\n    FileOutputStream saltOutFile = new FileOutputStream("salt.enc");\n    saltOutFile.write(salt);\n    saltOutFile.close();\n\n    SecretKeyFactory factory = SecretKeyFactory\n            .getInstance("PBKDF2WithHmacSHA1");\n    KeySpec keySpec = new PBEKeySpec(password.toCharArray(), salt, 65536,\n            256);\n    SecretKey secretKey = factory.generateSecret(keySpec);\n    SecretKey secret = new SecretKeySpec(secretKey.getEncoded(), "AES");\n\n    //\n    Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");\n    cipher.init(Cipher.ENCRYPT_MODE, secret);\n    AlgorithmParameters params = cipher.getParameters();\n\n    // iv adds randomness to the text and just makes the mechanism more\n    // secure\n    // used while initializing the cipher\n    // file to store the iv\n    FileOutputStream ivOutFile = new FileOutputStream("iv.enc");\n    byte[] iv = params.getParameterSpec(IvParameterSpec.class).getIV();\n    ivOutFile.write(iv);\n    ivOutFile.close();\n\n    //file encryption\n    byte[] input = new byte[64];\n    int bytesRead;\n\n    while ((bytesRead = inFile.read(input)) != -1) {\n        byte[] output = cipher.update(input, 0, bytesRead);\n        if (output != null)\n            outFile.write(output);\n    }\n\n    byte[] output = cipher.doFinal();\n    if (output != null)\n        outFile.write(output);\n\n    inFile.close();\n    outFile.flush();\n    outFile.close();\n\n    System.out.println("File Encrypted.");\n\n    }\n\n}\n
Run Code Online (Sandbox Code Playgroud)\n

Art*_* B. 5

讲师表示应该应用 HMAC 来为密文创建身份验证标记。这称为先加密后 MAC。HMAC 是一种密钥哈希函数,它为拥有正确密钥的接收者提供真实性(和完整性,因为没有完整性的真实性没有任何意义)检查。由于它本质上是一个哈希函数,因此它通过更新内部状态来工作。

Mac mac = Mac.getInstance("HmacSHA1");
SecretKeySpec macKey = new SecretKeySpec(macKeyBytes, "HmacSHA1");
mac.init(macKey);
mac.update(iv); // update for IV
...
mac.update(output); // update for each ciphertext chunk
...
byte[] authTag = mac.doFinal();
outfile.write(authTag); // at the very end
Run Code Online (Sandbox Code Playgroud)

仍然存在一个问题,那就是macKeyBytes. 它不应与您通过 PBKDF2 从密码生成的加密密钥相同。您应该使用生成的密钥分别导出加密和 MAC 密钥。这通常是使用HKDF完成的,但您也可以再次使用 HMAC 来完成。伪代码:

byte[] encKeyBytes = hmacSha256(key, "Encryption");
byte[] macKeyBytes = hmacSha256(key, "Authentication");
Run Code Online (Sandbox Code Playgroud)

该指令没有提及任何有关密钥或密码的信息,因此我假设它应该是静态密钥(用于测试目的)。您使用 PBKDF2 的方式是可以的,但盐也必须写入文件中。否则,如果没有随机盐,接收者将无法导出相同的密钥。另外,salt 的长度应该是 16 个字节。


当前代码的另一个问题是您将 IV 写入单独的文件中。不要那样做。只需将 IV 写入密文文件的开头即可。IV 与 CBC 模式下的块大小相同,而 AES 的块大小固定为 16 字节。接收者将始终知道要为 IV 读取多少字节。