Android数据传输加密(二):AES加密

前言:不关心原理的小伙伴们可直接阅读第4部分。

1.AES加密简介

AES是一种对称加密算法,即加密解密使用同一把秘钥。非对称加密则是加密解密使用不同的秘钥(如RSA加密,下一篇文章会介绍)。

AES通常是这样使用的:

客户端每次随机生成一个秘钥,对待传输的数据加密,然后再用其他加密手段(如RSA),对秘钥本身进行加密,最后将加密后的数据和AES秘钥一同发送给服务端。

服务端接到数据后,首先对AES秘钥解密,得到原始秘钥,然后使用AES秘钥进行数据解密。

数据加密后,为了便于传输,通常还要转码为Base64格式,流程图中省略了Base64的转码和解码。关于Base64,请参考上一篇文章:Android数据传输加密(一):Base64转码算法_base64转码是哪个包_Vincent(朱志强)的博客-CSDN博客

2.AES加密模式

AES采用分块加密,根据具体的处理策略,分为多种加密模式。Android中支持6种,以下四种较为常见:

ECB 模式

最早采用和最简单的模式,它将待加密的数据分成若干块,每块的大小跟加密密钥长度相同,然后分别对每个数据块用同一密钥进行加密。

特点: 简单,有利于并行计算(每个数据块用同一密钥单独加密),误差不会传递(一个数据块出错不会影响其他数据块)。

由于所有分组的加密方式一致,明文中的重复内容会在密文中有所体现,因此难以抵抗统计分析攻击。

因此,ECB模式一般只适用于小数据量的字符信息的安全性保护。

CBC 模式

CBC模式对于每个待加密的密码块在加密前会先与前一个密码块的密文异或,然后再用加密器加密。第一个明文块与一个叫初始化向量的数据块异或。

特点:CBC模式相比ECB有更高的保密性,适合传输长的报文,但由于对每个数据块的加密依赖与前一个数据块的加密所以加密无法并行,误差也会传递,与ECB一样,不是很适合对流数据进行加密。需要初始化向量。

CFB 模式

OFB 模式

这两种模式属于反馈模式,加密规则更加复杂,安全性也就优于CBC,复杂到没有深入研究的必要性,我们只需知道他们也是需要初始化向量,不能并行运算,误差也会传递,而且它们可以以比数据块小的单元进行加密,从而将块密文转为流密文。

另外两种模式为CTR和CTS,不常见。

3.填充模式

AES是分块加密,块的大小与密钥长度一致,AES密钥的长度只能是128位(16字节)、192位(24字节)或256位(32字节),默认为256位。就像切蛋糕一样,按照固定大小去切,最后一块往往是残缺的,这个时候可以选择如何填充空缺位,当然也可以选择不填充。

Android中,Aes加密的填充方式有三种:

ISO10126Padding

最后一个字节填充的值为总共填充的字节数,其他字节随机填充。

假设使用128位密钥加密,最后一个数据块是126位,需要填充两个字节:

蓝色部分为原始数据,红色部分为填充数据,由于需要填充两个字节,所以最后一个字节的值为2,其他字节随机填充。

PKCS5Padding

每个填充的字节的值均为要填充的字节数。

由于需要填充两个字节,所以填充的字节的值均为2。

NoPadding

这个不用说了吧,哈哈...

4. Android中的使用

产生一把AES密钥

    /*
        产生一把AES密钥

        @param keySize
        密钥大小,只能是128位(16字节)、192位(24字节)、256位(32字节)

        @return 字节数组形式的AES密钥
     */
   public static byte[] generateKey(int keySize){

        //保存AES密钥的字节数组
        byte[] keyBytes = null;

        try {
            //获取密钥生成器
            KeyGenerator keyGenerator = KeyGenerator.getInstance("AES");
            //设置密钥长度,如果不调用该方法,默认生成256位的密钥
            keyGenerator.init(keySize);
            //获得密钥对象
            SecretKey secretKey = keyGenerator.generateKey();
            //转成字节数组
            keyBytes = secretKey.getEncoded();

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

        return keyBytes;

    }

加密操作

ECB模式不需要初始化向量IV(也称为偏移量):

        //待加密数据
        String clearText = "大家好,我是朱志强!";
        //产生密钥
        byte[] keyBytes = generateAESKey(256);
        //构建SecretKeySpec,初始化Cipher对象时需要该参数
        SecretKeySpec secretKeySpec = new SecretKeySpec(keyBytes, "AES");

        try {
            //构建Cipher对象,需要传入一个字符串,格式必须为"algorithm/mode/padding"或者"algorithm/",意为"算法/加密模式/填充方式"
            Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
            //初始化Cipher对象
            cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec);

            //加密数据
            byte[] resultBytes = cipher.doFinal(clearText.getBytes());

            //结果用Base64转码
            String finalResult = Base64.encodeToString(resultBytes,Base64.DEFAULT);
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        } catch (NoSuchPaddingException e) {
            e.printStackTrace();
        } catch (InvalidKeyException e) {
            e.printStackTrace();
        } catch (BadPaddingException e) {
            e.printStackTrace();
        } catch (IllegalBlockSizeException e) {
            e.printStackTrace();
        }

CBC,CFB,OFB,CTR需要初始化向量IV,以CBC为例:

        //待加密数据
        String clearText = "大家好,我是朱志强!";
        //产生密钥
        byte[] keyBytes = generateAESKey(256);
        //构建SecretKeySpec,初始化Cipher对象时需要该参数
        SecretKeySpec secretKeySpec = new SecretKeySpec(keyBytes, "AES");

        try {
            //构建Cipher对象,需要传入一个字符串,格式必须为"algorithm/mode/padding"或者"algorithm/",意为"算法/加密模式/填充方式"
            Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
            //初始化Cipher对象
            byte[] ivBytes = generatorIvBytes(keyBytes.length);
            IvParameterSpec ivParameterSpec = new IvParameterSpec(ivBytes);
            cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec,ivParameterSpec);

            //加密数据
            byte[] resultBytes = cipher.doFinal(clearText.getBytes());

            //结果用Base64转码
            String finalResult = Base64.encodeToString(resultBytes,Base64.DEFAULT);
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        } catch (NoSuchPaddingException e) {
            e.printStackTrace();
        } catch (InvalidKeyException e) {
            e.printStackTrace();
        } catch (BadPaddingException e) {
            e.printStackTrace();
        } catch (IllegalBlockSizeException e) {
            e.printStackTrace();
        } catch (InvalidAlgorithmParameterException e) {
            e.printStackTrace();
        }

  private byte[] generatorIvBytes(int blockSize) {
        Random random = new Random();
        byte[] ivParam = new byte[blockSize];
        for (int index = 0; index < blockSize; index++) {
            ivParam[index] = (byte) random.nextInt(256);
        }

        return ivParam;
    }

说明:

①ECB模式和其他模式不同,仅仅在于初始化Cipher对象时,是否需要提供初始化向量IV,这个字节数组是任意的,只要大小和数据块大小(也就是密钥大小)一致即可。

②获取Cipher实例时,传入的字符串并不是任意的,例如Cipher.getInstance("AES/ECB/PKCS5Padding"),首先,格式要固定,必须为"algorithm/mode/padding"或者"algorithm",意为"算法/加密模式/填充方式"。其次,需要Android支持该组合模式。如何知道Android是否支持呢,Cipher的Api文档给出了答案,下面列出关于AES的所有合法形式:

上图是对官方Api文档的截图。

③因为CFB、OFB模式可以以小于数据块的单元进行加密,那么在指定假面模式时可以指定加密单元,如:"AES/CFB8/PKCS5Padding",表示以8位为加密单元,不同的加密单元加密后的结果也是不一致的,如果不指定,默认以数据块为加密单元。

解密操作

解密操作的代码和加密操作类似,只不过把Cipher的操作模式改为解密即可。

ECB模式解密

  //data为待解密的数据,keyBytes为加密时所使用的密钥,transform为加密时所采取的加密模式和填充模式
    public byte[] decryptData(String data,byte[] keyBytes,String transform){
        byte[] clearData = null;

        SecretKeySpec secretKeySpec = new SecretKeySpec(keyBytes,"AES");

        try {

            //根据格式获取Cipher实例,需与加密时一致
            Cipher cipher = Cipher.getInstance(transform);
            //初始化Cipher,注意这里变为解密模式
            cipher.init(Cipher.DECRYPT_MODE,secretKeySpec);
            //先Base64解码
            byte[] temp = Base64.decode(data,Base64.DEFAULT);
            
            //解密数据
            clearData = cipher.doFinal(temp);

        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        } catch (NoSuchPaddingException e) {
            e.printStackTrace();
        } catch (InvalidKeyException e) {
            e.printStackTrace();
        } catch (BadPaddingException e) {
            e.printStackTrace();
        } catch (IllegalBlockSizeException e) {
            e.printStackTrace();
        }

        return clearData;

    }

 其他需要IV解密的,以CBC为例:    

    //data为待解密的数据,keyBytes为加密时所使用的密钥,ivBytes为初始化向量,transform为加密时所采取的加密模式和填充模式
    public byte[] decryptData(String data,byte[] keyBytes,byte[] ivBytes,String transform){
        byte[] clearData = null;

        SecretKeySpec secretKeySpec = new SecretKeySpec(keyBytes,"AES");

        try {

            //根据格式获取Cipher实例,需与加密时一致
            Cipher cipher = Cipher.getInstance(transform);
            //初始化Cipher,注意这里变为解密模式
            IvParameterSpec ivParameterSpec = new IvParameterSpec(ivBytes);
            cipher.init(Cipher.DECRYPT_MODE,secretKeySpec,ivParameterSpec);
            //先Base64解码
            byte[] temp = Base64.decode(data,Base64.DEFAULT);

            //解密数据
            clearData = cipher.doFinal(temp);

        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        } catch (NoSuchPaddingException e) {
            e.printStackTrace();
        } catch (InvalidKeyException e) {
            e.printStackTrace();
        } catch (BadPaddingException e) {
            e.printStackTrace();
        } catch (IllegalBlockSizeException e) {
            e.printStackTrace();
        } catch (InvalidAlgorithmParameterException e) {
            e.printStackTrace();
        }

        return clearData;

    }

  • 4
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论
好的,这里是一个简单的示例,展示了如何在 Android 中使用 AES 加密和解密文件: ```java import javax.crypto.Cipher; import javax.crypto.spec.IvParameterSpec; import javax.crypto.spec.SecretKeySpec; import java.io.FileInputStream; import java.io.FileOutputStream; import java.security.MessageDigest; import java.util.Arrays; public class AesUtil { private static final String ALGORITHM = "AES/CBC/PKCS5Padding"; private static final String SECRET_KEY = "my_secret_key"; private static final String IV = "my_initialization_vector"; public static void encrypt(String inputFile, String outputFile) throws Exception { SecretKeySpec secretKeySpec = generateKey(SECRET_KEY); IvParameterSpec ivParameterSpec = new IvParameterSpec(IV.getBytes("UTF-8")); Cipher cipher = Cipher.getInstance(ALGORITHM); cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec, ivParameterSpec); FileInputStream inputStream = new FileInputStream(inputFile); FileOutputStream outputStream = new FileOutputStream(outputFile); byte[] buffer = new byte[1024]; int bytesRead; while ((bytesRead = inputStream.read(buffer)) != -1) { byte[] output = cipher.update(buffer, 0, bytesRead); if (output != null) { outputStream.write(output); } } byte[] output = cipher.doFinal(); if (output != null) { outputStream.write(output); } inputStream.close(); outputStream.close(); } public static void decrypt(String inputFile, String outputFile) throws Exception { SecretKeySpec secretKeySpec = generateKey(SECRET_KEY); IvParameterSpec ivParameterSpec = new IvParameterSpec(IV.getBytes("UTF-8")); Cipher cipher = Cipher.getInstance(ALGORITHM); cipher.init(Cipher.DECRYPT_MODE, secretKeySpec, ivParameterSpec); FileInputStream inputStream = new FileInputStream(inputFile); FileOutputStream outputStream = new FileOutputStream(outputFile); byte[] buffer = new byte[1024]; int bytesRead; while ((bytesRead = inputStream.read(buffer)) != -1) { byte[] output = cipher.update(buffer, 0, bytesRead); if (output != null) { outputStream.write(output); } } byte[] output = cipher.doFinal(); if (output != null) { outputStream.write(output); } inputStream.close(); outputStream.close(); } private static SecretKeySpec generateKey(String key) throws Exception { byte[] keyBytes = key.getBytes("UTF-8"); MessageDigest sha = MessageDigest.getInstance("SHA-256"); keyBytes = sha.digest(keyBytes); keyBytes = Arrays.copyOf(keyBytes, 16); return new SecretKeySpec(keyBytes, "AES"); } } ``` 在此示例中,我们使用 AES/CBC/PKCS5Padding 加密模式和 SHA-256 哈希算法生成密钥。我们将密钥和初始向量 IV 保存为常量,但您可以根据需要进行更改。 要使用此示例,请在您的代码中调用 `AesUtil.encrypt(inputFile, outputFile)` 或 `AesUtil.decrypt(inputFile, outputFile)` 方法,其中 `inputFile` 是要加密或解密的文件路径,而 `outputFile` 是加密或解密后的文件路径。 请注意,此示例中的加密和解密操作是阻塞的,并且使用了相对较小的缓冲区。对于大文件和需要异步处理的情况,您需要进行适当的优化。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Vincent(朱志强)

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值