利用 AES256 算法加解密文件(by Java)

本文要介绍的是基于 Java 语言实现 AES256 加解密文件功能,主要流程包括

  • 读取文件明文数据,通过 AES256 加密算法进行加密,将加密后的数据写回文件
  • 读取文件密文数据,通过 AES256 解密算法进行解密,将解密后的数据写回文件


    AES 文件加解密流程.png

AES256 算法简介

AES(高级加密标准,Advanced Encryption Standard),对称加密算法,不同于 RSA 等非对称加密,其只使用一个密钥参与加密和解密。

密钥

AES256 中的 256 代表的是密钥的长度为 256位,此外还存在 AES128、AES192,AES256 的安全性最高,AES128性能最高,本质原因是它们的加密处理轮数不同。

填充

AES 算法在对明文加密的时候,并不是直接对明文数据进行加密,而是将明文拆分成一个一个独立的明文段,每一段长度为 128bit。然后这些明文段经过 AES 加密器处理,生成密文段,将密文段合并到一起,得到加密结果。
因为明文段是按照 128bit 长度进行拆分,就会存在长度不足 128bit 的情况,所以需要对不足的明文段进行填充
Nopadding
不做任何填充,但是要求铭文必须是 16字节 的整数倍。
PKCS5Padding
不足的明文段,在末尾补足相应数量的字符,且每个字节的值等于缺少的字符数。
ISO10126Padding
不足的明文段,在末尾补足相应数量的字符,最后一个字符值等于缺少的字符数,其他字符值填充随机数。

模式

AES 的工作模式,分为 ECB、CBC、CTR、CFB、OFB。本文使用 CBC 模式,该模式会使用一个初始向量 IV,加密的时候,第一个明文段会首先和初始向量 IV 做异或操作,然后再经过密钥加密,然后第一个密文块又会作为第二个明文段的加密向量来异或,依次类推下去,这样相同的明文段加密出来的密文块就是不同的,因此更加安全。

文件加密

本文主要介绍媒体文件的加密,所以不需要对文件的全部数据进行加密,仅仅加密头部部分数据即可,因为没有头部数据,这些媒体文件也是无法成功进行解码。使用部分数据进行加密,从而提高加密性能。

public static int encryptFile(File file, SecretKey secretKey, int encryptLength) {
        try {
            // 以 byte 的形式读取,不改变文件数据的编码格式
            byte[] bytes = Files.readAllBytes(file.toPath());

            // 仅加密 encryptLength 长度的数据
            byte[] substring = new byte[encryptLength];
            System.arraycopy(bytes, 0, substring, 0, encryptLength);

            // 加密
            byte[] encrypt = encrypt(substring, secretKey);

            // 使用密文替换老数据
            byte[] newContent = new byte[encrypt.length + bytes.length - encryptLength];
            System.arraycopy(encrypt, 0, newContent, 0, encrypt.length);
            System.arraycopy(bytes, encryptLength, newContent, encrypt.length, bytes.length - encryptLength);

            // 覆盖写入文件
            Files.write(file.toPath(), newContent);

            return encrypt.length;
        } catch (Exception e) {
            e.printStackTrace();
        }

        return 0;
    }

加密

private static byte[] encrypt(byte[] content, SecretKey secretKey) {
        byte[] str = null;
        try {
            Cipher cipher = Cipher.getInstance(ALGORITHM);
            cipher.init(Cipher.ENCRYPT_MODE, secretKey, new IvParameterSpec(iv));
            str = cipher.doFinal(content);
        } catch (Exception e) {
            e.printStackTrace();
        }

        return str;
    }

文件解密

因为数据加密后的长度与明文是不一致的,而文件加密只是部分加密,所以需要记录下密文的长度,从而读取密文,完成解密

public static void decryptFile(File file, SecretKey secretKey, int decryptLength) {
        try {
            // 以 byte 的形式读取,不改变文件数据的编码格式
            byte[] bytes = Files.readAllBytes(file.toPath());

            // 截取密文数据
            byte[] substring = new byte[decryptLength];
            System.arraycopy(bytes, 0, substring, 0, decryptLength);

            // 解密
            byte[] decrypt = decrypt(substring, secretKey);

            // 使用明文替换加密数据
            byte[] newContent = new byte[decrypt.length + bytes.length - decryptLength];
            System.arraycopy(decrypt, 0, newContent, 0, decrypt.length);
            System.arraycopy(bytes, decryptLength, newContent, decrypt.length, bytes.length - decryptLength);

            // 覆盖写入文件
            Files.write(file.toPath(), newContent);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

解密

private static byte[] decrypt(byte[] bytes, SecretKey secretKey) {
        byte[] decryptStr = null;
        try {
            Cipher cipher = Cipher.getInstance(ALGORITHM);
            cipher.init(Cipher.DECRYPT_MODE, secretKey, new IvParameterSpec(iv));
            decryptStr = cipher.doFinal(bytes);
        } catch (Exception e) {
            e.printStackTrace();
        }

        return decryptStr;
    }

源码

import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.PBEKeySpec;
import javax.crypto.spec.SecretKeySpec;
import java.io.*;
import java.nio.file.Files;

/**
 * AES 加解密
 */
public class AESEncrypt {
    private static final String ALGORITHM = "AES/CBC/PKCS5Padding";
    private static final byte[] iv = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};

    /**
     * 生成 SecretKey
     * @param secret
     * @param salt
     * @return
     */
    public static SecretKey generateSecretKey(String secret, String salt) {
        SecretKey secretKey = null;
        try {
            SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256");
            PBEKeySpec keySpec = new PBEKeySpec(secret.toCharArray(), salt.getBytes(), 65536, 256);
            secretKey = new SecretKeySpec(factory.generateSecret(keySpec).getEncoded(), "AES");
        } catch (Exception e) {
            e.printStackTrace();
        }

        return secretKey;
    }

    /**
     * 加密
     * @param content
     * @param secretKey
     * @return
     */
    private static byte[] encrypt(byte[] content, SecretKey secretKey) {
        byte[] str = null;
        try {
            Cipher cipher = Cipher.getInstance(ALGORITHM);
            cipher.init(Cipher.ENCRYPT_MODE, secretKey, new IvParameterSpec(iv));
            str = cipher.doFinal(content);
        } catch (Exception e) {
            e.printStackTrace();
        }

        return str;
    }

    /**
     * 解密
     * @param bytes
     * @param secretKey
     * @return
     */
    private static byte[] decrypt(byte[] bytes, SecretKey secretKey) {
        byte[] decryptStr = null;
        try {
            Cipher cipher = Cipher.getInstance(ALGORITHM);
            cipher.init(Cipher.DECRYPT_MODE, secretKey, new IvParameterSpec(iv));
            decryptStr = cipher.doFinal(bytes);
        } catch (Exception e) {
            e.printStackTrace();
        }

        return decryptStr;
    }

    /**
     * 文件加密
     * @param file
     * @param secretKey
     */
    public static int encryptFile(File file, SecretKey secretKey, int encryptLength) {
        try {
            // 以 byte 的形式读取,不改变文件数据的编码格式
            byte[] bytes = Files.readAllBytes(file.toPath());

            // 仅加密 encryptLength 长度的数据
            byte[] substring = new byte[encryptLength];
            System.arraycopy(bytes, 0, substring, 0, encryptLength);

            // 加密
            byte[] encrypt = encrypt(substring, secretKey);

            // 使用密文替换老数据
            byte[] newContent = new byte[encrypt.length + bytes.length - encryptLength];
            System.arraycopy(encrypt, 0, newContent, 0, encrypt.length);
            System.arraycopy(bytes, encryptLength, newContent, encrypt.length, bytes.length - encryptLength);

            // 覆盖写入文件
            Files.write(file.toPath(), newContent);

            return encrypt.length;
        } catch (Exception e) {
            e.printStackTrace();
        }

        return 0;
    }

    /**
     * 文件解密
     * @param file
     * @param secretKey
     * @param decryptLength
     */
    public static void decryptFile(File file, SecretKey secretKey, int decryptLength) {
        try {
            // 以 byte 的形式读取,不改变文件数据的编码格式
            byte[] bytes = Files.readAllBytes(file.toPath());

            // 截取密文数据
            byte[] substring = new byte[decryptLength];
            System.arraycopy(bytes, 0, substring, 0, decryptLength);

            // 解密
            byte[] decrypt = decrypt(substring, secretKey);

            // 使用明文替换加密数据
            byte[] newContent = new byte[decrypt.length + bytes.length - decryptLength];
            System.arraycopy(decrypt, 0, newContent, 0, decrypt.length);
            System.arraycopy(bytes, decryptLength, newContent, decrypt.length, bytes.length - decryptLength);

            // 覆盖写入文件
            Files.write(file.toPath(), newContent);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
        try {
            // generate secret key
            SecretKey secretKey = generateSecretKey("password", "salt");

            File file = new File(args[0]);

            long encryptStart = System.currentTimeMillis();
            int encryptLength = encryptFile(file, secretKey, 128);
            long encryptEnd = System.currentTimeMillis();
            System.out.printf("Encrypt %s cost %d%n", args[0], (encryptEnd - encryptStart));

            decryptFile(file, secretKey, encryptLength);
            System.out.printf("Decrypt %s cost %d%n", args[0], (System.currentTimeMillis() - encryptEnd));

        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

你可能感兴趣的:(利用 AES256 算法加解密文件(by Java))