Java使用密码加密文件

Java使用密码加密文件,java,encryption,key,aes,Java,Encryption,Key,Aes,嗨,我是新用户,这是我的第一个问题: 我声明我对密码学没有广泛的知识。 我正在尝试使用用户提供的密码加密文件,我发现了以下方法: fileProcessor(Cipher.ENCRYPT_MODE,key,inputFile,newFile); static void fileProcessor(int cipherMode,String key,File inputFile,File outputFile) { try { Key secretKey = new Se

嗨,我是新用户,这是我的第一个问题: 我声明我对密码学没有广泛的知识。 我正在尝试使用用户提供的密码加密文件,我发现了以下方法:

fileProcessor(Cipher.ENCRYPT_MODE,key,inputFile,newFile);

static void fileProcessor(int cipherMode,String key,File inputFile,File outputFile) {
    try {
        Key secretKey = new SecretKeySpec(key.getBytes(), "AES");
        Cipher cipher = Cipher.getInstance("AES");
        cipher.init(cipherMode, secretKey);

        FileInputStream inputStream = new FileInputStream(inputFile);
        byte[] inputBytes = new byte[(int) inputFile.length()];
        inputStream.read(inputBytes);

        byte[] outputBytes = cipher.doFinal(inputBytes);

        FileOutputStream outputStream = new FileOutputStream(outputFile);
        outputStream.write(outputBytes);

        inputStream.close();
        outputStream.close();

        } catch (NoSuchPaddingException | NoSuchAlgorithmException 
                     | InvalidKeyException | BadPaddingException
                 | IllegalBlockSizeException | IOException e) {
        e.printStackTrace();
            }
}
问题是,只有输入16字节的密码(我认为即使是密码的倍数也可以),程序才能工作。 如何使用不一定是16字节倍数的密码?

密钥(
SecretKeySpec
)是加密密钥,而不是用户提供的简单明文密码。AES标准指定以下密钥大小:128、192或256位。 例如,可以使用密钥派生函数从文本密码创建密钥

如注释中所述,
Cipher.getInstance(“AES”)
会导致ECB模式下的AES加密,即。是基于AES算法的强批准模式

此外,在继续学习示例代码之前,您需要了解以下概念:

// The number of times that the password is hashed during the derivation of the symmetric key
private static final int PBKDF2_ITERATION_COUNT = 300_000;
private static final int PBKDF2_SALT_LENGTH = 16; //128 bits
private static final int AES_KEY_LENGTH = 256; //in bits
// An initialization vector size
private static final int GCM_NONCE_LENGTH = 12; //96 bits
// An authentication tag size
private static final int GCM_TAG_LENGTH = 128; //in bits

private static byte[] encryptAES256(byte[] input, String password) {
  try {
    SecureRandom secureRandom = SecureRandom.getInstanceStrong();
    // Derive the key, given password and salt
    SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA512");
    // A salt is a unique, randomly generated string
    // that is added to each password as part of the hashing process
    byte[] salt = new byte[PBKDF2_SALT_LENGTH];
    secureRandom.nextBytes(salt);
    KeySpec keySpec =
        new PBEKeySpec(password.toCharArray(), salt, PBKDF2_ITERATION_COUNT, AES_KEY_LENGTH);
    byte[] secret = factory.generateSecret(keySpec).getEncoded();
    SecretKey key = new SecretKeySpec(secret, "AES");

    // AES-GCM encryption
    Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
    // A nonce or an initialization vector is a random value chosen at encryption time
    // and meant to be used only once
    byte[] nonce = new byte[GCM_NONCE_LENGTH];
    secureRandom.nextBytes(nonce);
    // An authentication tag
    GCMParameterSpec gcmParameterSpec = new GCMParameterSpec(GCM_TAG_LENGTH, nonce);
    cipher.init(Cipher.ENCRYPT_MODE, key, gcmParameterSpec);
    byte[] encrypted = cipher.doFinal(input);
    // Salt and nonce can be stored together with the encrypted data
    // Both salt and nonce have fixed length, so can be prefixed to the encrypted data
    ByteBuffer byteBuffer = ByteBuffer.allocate(salt.length + nonce.length + encrypted.length);
    byteBuffer.put(salt);
    byteBuffer.put(nonce);
    byteBuffer.put(encrypted);
    return byteBuffer.array();
  } catch (Exception e) {
    throw new RuntimeException(e);
  }
}

private static byte[] decryptAES256(byte[] encrypted, String password) {
  try {
    // Salt and nonce have to be extracted
    ByteBuffer byteBuffer = ByteBuffer.wrap(encrypted);
    byte[] salt = new byte[PBKDF2_SALT_LENGTH];
    byteBuffer.get(salt);
    byte[] nonce = new byte[GCM_NONCE_LENGTH];
    byteBuffer.get(nonce);
    byte[] cipherBytes = new byte[byteBuffer.remaining()];
    byteBuffer.get(cipherBytes);

    SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA512");
    KeySpec keySpec =
        new PBEKeySpec(password.toCharArray(), salt, PBKDF2_ITERATION_COUNT, AES_KEY_LENGTH);
    byte[] secret = factory.generateSecret(keySpec).getEncoded();
    SecretKey key = new SecretKeySpec(secret, "AES");

    Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
    // If encrypted data is altered, during decryption authentication tag verification will fail
    // resulting in AEADBadTagException
    GCMParameterSpec gcmParameterSpec = new GCMParameterSpec(GCM_TAG_LENGTH, nonce);
    cipher.init(Cipher.DECRYPT_MODE, key, gcmParameterSpec);
    return cipher.doFinal(cipherBytes);
  } catch (Exception e) {
    throw new RuntimeException(e);
  }
}

public static void main(String[] args) throws Exception {
  String password = "Q8yRrM^AvV5r8Yx+"; //Password still has to be strong ehough
  String input = "Sample text to encrypt";
  byte[] encrypted = encryptAES256(input.getBytes(UTF_8), password);
  System.out.println(Base64.getEncoder().encodeToString(encrypted));
  //s+AwwowLdSb3rFZ6jJlxSXBvzGz7uB6+g2e97QXGRKUY5sHPgf94AOoybkzuR3rNREMj56Ik1+Co682s4vT2sAQ/
  byte[] decrypted = decryptAES256(encrypted, password);
  System.out.println(new String(decrypted, UTF_8));
  //Sample text to encrypt
}
  • 什么是保密性、完整性和真实性
示例代码:

// The number of times that the password is hashed during the derivation of the symmetric key
private static final int PBKDF2_ITERATION_COUNT = 300_000;
private static final int PBKDF2_SALT_LENGTH = 16; //128 bits
private static final int AES_KEY_LENGTH = 256; //in bits
// An initialization vector size
private static final int GCM_NONCE_LENGTH = 12; //96 bits
// An authentication tag size
private static final int GCM_TAG_LENGTH = 128; //in bits

private static byte[] encryptAES256(byte[] input, String password) {
  try {
    SecureRandom secureRandom = SecureRandom.getInstanceStrong();
    // Derive the key, given password and salt
    SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA512");
    // A salt is a unique, randomly generated string
    // that is added to each password as part of the hashing process
    byte[] salt = new byte[PBKDF2_SALT_LENGTH];
    secureRandom.nextBytes(salt);
    KeySpec keySpec =
        new PBEKeySpec(password.toCharArray(), salt, PBKDF2_ITERATION_COUNT, AES_KEY_LENGTH);
    byte[] secret = factory.generateSecret(keySpec).getEncoded();
    SecretKey key = new SecretKeySpec(secret, "AES");

    // AES-GCM encryption
    Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
    // A nonce or an initialization vector is a random value chosen at encryption time
    // and meant to be used only once
    byte[] nonce = new byte[GCM_NONCE_LENGTH];
    secureRandom.nextBytes(nonce);
    // An authentication tag
    GCMParameterSpec gcmParameterSpec = new GCMParameterSpec(GCM_TAG_LENGTH, nonce);
    cipher.init(Cipher.ENCRYPT_MODE, key, gcmParameterSpec);
    byte[] encrypted = cipher.doFinal(input);
    // Salt and nonce can be stored together with the encrypted data
    // Both salt and nonce have fixed length, so can be prefixed to the encrypted data
    ByteBuffer byteBuffer = ByteBuffer.allocate(salt.length + nonce.length + encrypted.length);
    byteBuffer.put(salt);
    byteBuffer.put(nonce);
    byteBuffer.put(encrypted);
    return byteBuffer.array();
  } catch (Exception e) {
    throw new RuntimeException(e);
  }
}

private static byte[] decryptAES256(byte[] encrypted, String password) {
  try {
    // Salt and nonce have to be extracted
    ByteBuffer byteBuffer = ByteBuffer.wrap(encrypted);
    byte[] salt = new byte[PBKDF2_SALT_LENGTH];
    byteBuffer.get(salt);
    byte[] nonce = new byte[GCM_NONCE_LENGTH];
    byteBuffer.get(nonce);
    byte[] cipherBytes = new byte[byteBuffer.remaining()];
    byteBuffer.get(cipherBytes);

    SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA512");
    KeySpec keySpec =
        new PBEKeySpec(password.toCharArray(), salt, PBKDF2_ITERATION_COUNT, AES_KEY_LENGTH);
    byte[] secret = factory.generateSecret(keySpec).getEncoded();
    SecretKey key = new SecretKeySpec(secret, "AES");

    Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
    // If encrypted data is altered, during decryption authentication tag verification will fail
    // resulting in AEADBadTagException
    GCMParameterSpec gcmParameterSpec = new GCMParameterSpec(GCM_TAG_LENGTH, nonce);
    cipher.init(Cipher.DECRYPT_MODE, key, gcmParameterSpec);
    return cipher.doFinal(cipherBytes);
  } catch (Exception e) {
    throw new RuntimeException(e);
  }
}

public static void main(String[] args) throws Exception {
  String password = "Q8yRrM^AvV5r8Yx+"; //Password still has to be strong ehough
  String input = "Sample text to encrypt";
  byte[] encrypted = encryptAES256(input.getBytes(UTF_8), password);
  System.out.println(Base64.getEncoder().encodeToString(encrypted));
  //s+AwwowLdSb3rFZ6jJlxSXBvzGz7uB6+g2e97QXGRKUY5sHPgf94AOoybkzuR3rNREMj56Ik1+Co682s4vT2sAQ/
  byte[] decrypted = decryptAES256(encrypted, password);
  System.out.println(new String(decrypted, UTF_8));
  //Sample text to encrypt
}
再多说几句。如果只有少数记录使用相同的密钥加密,那么随机的nonce不会带来风险。但是,如果使用相同的密钥对大量记录进行加密,则风险可能会变得相关

一次重复的nonce通常足以完全恢复 连接的身份验证密钥。在这种错误的实现中, 真实性将丢失,攻击者可以操纵 受TLS保护的内容

出于安全原因,应避免随机的nonce,并应使用计数器

我正在尝试使用用户提供的密码加密文件
如何使用不一定是16字节倍数的密码

要从用户提供的密码创建加密密钥,您可以检查,通常搜索“基于密码的加密”

下面是如何使用用户密码创建加密密钥的示例

 private static final String PBKDF_ALG = "PBKDF2WithHmacSHA256";
 private static final int PBKDF_INTERATIONS = 800000;

// create key from password
 SecretKeyFactory secKeyFactory = SecretKeyFactory.getInstance(PBKDF_ALG);
 KeySpec pbeSpec = new PBEKeySpec(password.toCharArray(), psswdSalt, PBKDF_INTERATIONS, SYMMETRIC_KEY.length*8);
 SecretKey pbeSecretKey = secKeyFactory.generateSecret(pbeSpec);
SecretKey secKey = new SecretKeySpec(pbeSecretKey.getEncoded(), SYMMETRIC_KEY_ALG);

Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(cipherMode, secKey) ;


你听说过填充吗?不,它是什么?寻找关于PBKDF2(PBKDF是一个基于密码的密钥派生函数)的文章。忽略并否决所有关于直接使用SHA-256的文章,请记住,即使您使用PBKDF2等增强功能,您仍然需要一个非常安全的密码。如果您不懂密码,那么“找到的方法”安全的可能性大约为零。您最好使用一个特定于加密的库(Fernet、CMS、PGP),这样至少有一些选择是安全的。例如,您正在使用上面的ECB模式,该模式会立即泄漏有关文件的信息,并且也不会提供消息完整性或身份验证。该代码具有糟糕的流和错误处理,顺便说一句,我会将其用作如何不实现文件加密的示例。每行代码大约有一个错误。
Cipher.getInstance(“AES”)。。。哦,我已经警告过欧洲央行了,对吧?迭代次数也不超过2019个规格。在发布之前,请确保您发布的内容是安全的,密码有一个严重的问题,即代码可以完美运行,但仍然不安全。@Maarten,谢谢。更新了该示例代码以在PBKDF2中使用AES-GCM和更多迭代。