Android加密算法之对称加密AES

作为一名developer,一些重要文件存取,网络数据传输安全不可忽视,促使我们使用加密算法手段保证信息数据的安全。加密并不意味着绝对的安全,总有破解的时候,为了提高破解难度,在算法要求和品位上也越来越高,常见的有SHA-256、MD5等的Hash算法,HMAC-SHA256等的信息认证算法,RSA(玩过支付宝支付的朋友应该知道其使用的公钥私钥就是该算法)等的非对称加密算法,以及今天我要说的主角对称加密算法如AES,其他算法这里就不提了。

因需求的不同,对于算法的选择也因需而异。这里强调Base64算法不叫加密,只是一种编码方式,可以用来打乱一些不太敏感又不想让人直接看到的内容,但请不要当加密算法来用。

对称加密算法,又称秘钥加密:用一个秘钥来管理信息的加密解密,其优点是算法公开、计算量小、加密速度快、加密效率高。正如上述,其加密的安全性不在于技术是有多精湛,对秘钥的保管才是最重要的安全性问题,一旦泄露就没有安全可言了(将秘钥投入到算法中,分分钟破解密文),可以说是该算法最大的缺点。常用的对称算法有:DES、3DES、TDEA、Blowfish、RC2、RC4、RC5、IDEA、SKIPJACK、AES等。DES等算法已不再安全,推荐使用AES算法,而AES算法中建议使用CBC模式,秘钥长度则推荐使用256位,但是需要另行下载JCE(Java Cryptography Extension ,下文会有说明)。

Android中AES加密沿用java里的API,默认是ECB模式(该模式已不安全),需要显示指定CBC模式,另外由于jdk版本的限制,使用PKCS5Padding填充方式即可,PKCS7Padding其实和PKCS5Padding差别不大,不必太纠结。简单实现请看下面贴出的AESUtils.java代码:

package cn.icheny.security;

import android.util.Base64;
import java.io.UnsupportedEncodingException;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.KeyGenerator;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.SecretKey;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;

/**
 * AES加密,解密工具
 * 
 * @author Cheny
 */

public class AESUtils {
    private static final String AES_CBC_PKCS5_PADDING = "AES/CBC/PKCS5Padding";
    private static final String AES = "AES";

    /**
     * 生成秘钥
     * @return Base64编码的秘钥
     */
    public static String generateSecretKey() {
        try {
            // 获取Key生成器实例,一般一个实例可以多次用来生成秘钥
            KeyGenerator keyGenerator = KeyGenerator.getInstance(AES);
            // 256位
            keyGenerator.init(256);
            // 生成密钥
            SecretKey secretKey = keyGenerator.generateKey();
            // 获取密钥
            byte[] keyBytes = secretKey.getEncoded();
            return Base64.encodeToString(keyBytes, Base64.DEFAULT);// 生成的秘钥转换成Base64编码,加、解密时需要用Base64还原秘钥
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 加密
     * @param plaintext 明文
     * @param key 秘钥
     * @return Base64编码的密文
     */
    public static String encrypt(String plaintext, String key) {
        try {
            // Base64还原秘钥
            byte[] keyBytes = Base64.decode(key.getBytes(), Base64.DEFAULT);
            // 还原密钥对象
            SecretKey secretKey = new SecretKeySpec(keyBytes, AES);
            // 加密初始化实例
            Cipher cipher = Cipher.getInstance(AES_CBC_PKCS5_PADDING);
            // CBC模式需要添加一个参数IvParameterSpec,ECB模式则不需要
            cipher.init(Cipher.ENCRYPT_MODE, secretKey, new IvParameterSpec(new byte[cipher.getBlockSize()]));
            byte[] result = cipher.doFinal(plaintext.getBytes("UTF-8"));
            return Base64.encodeToString(result, Base64.DEFAULT);// 生成的密文转换成Base64编码出文本,解密时需要用Base64还原出密文
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        } catch (NoSuchPaddingException e) {
            e.printStackTrace();
        } catch (IllegalBlockSizeException e) {
            e.printStackTrace();
        } catch (BadPaddingException e) {
            e.printStackTrace();
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        } catch (InvalidAlgorithmParameterException e) {
            e.printStackTrace();
        } catch (InvalidKeyException e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 解密
     * @param ciphertext 密文
     * @param key  秘钥
     * @return 明文
     */
    public static String decrypt(String ciphertext, String key) {

        try {
            byte[] keyBytes = Base64.decode(key.getBytes(), Base64.DEFAULT);// Base64还原秘钥
            // 还原密钥对象
            SecretKey secretKey = new SecretKeySpec(keyBytes, AES);
            // 加密初始化实例
            Cipher cipher = Cipher.getInstance(AES_CBC_PKCS5_PADDING);
            cipher.init(Cipher.DECRYPT_MODE, secretKey, new IvParameterSpec(new byte[cipher.getBlockSize()]));
            // Base64还原密文
            byte[] cipherBytes = Base64.decode(ciphertext, Base64.DEFAULT);
            byte[] result = cipher.doFinal(cipherBytes);
            return new String(result, "UTF-8");
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        } catch (NoSuchPaddingException e) {
            e.printStackTrace();
        } catch (InvalidKeyException e) {
            e.printStackTrace();
        } catch (IllegalBlockSizeException e) {
            e.printStackTrace();
        } catch (BadPaddingException e) {
            e.printStackTrace();
        } catch (InvalidAlgorithmParameterException e) {
            e.printStackTrace();
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
        return null;
    }
}

代码中有注释,在加密中使用了android里Base64工具类,对秘钥,密文进行Base64转码修饰了一番,java中没有该工具类,若需Base64转码需自行代码实现,当然也可以转为16进制字符串,这里不再赘述。

由于因为美国对软件出口的限制,我们日常开发使用的jdk(主要是说jre)版本加密能力受到限制,上述工具类实际运行时会抛出:java.security.InvalidKeyException: Illegal key size 异常,当秘钥长度超过128位时会报出此异常,128位及以下可以正常运行。好了,文章就唠到这里,下次再见,白白!

一本正经说了那么多,突然放了一个大坑,说好的JCE说明呢?坑不解决了?怎么可以这么坑?OK,继续一本正经说下去:

下面引用某度来的说明:

        Java几乎各种常用加密算法都能找到对应的实现。
因为美国的出口限制,Sun通过权限文件(local_policy.jar、US_export_policy.jar)做了相应限制。
因此存在一些问题:
密钥长度上不能满足需求(如:java.security.InvalidKeyException: Illegal key size or default parameters);
部分算法未能支持,如MD4、SHA-224等算法;

        API使用起来还不是很方便;一些常用的进制转换辅助工具未能提供,如Base64编码转换、十六进制编码转换等工具。    Oracle在其官方网站上提供了无政策限制权限文件        (Unlimited Strength Jurisdiction Policy Files),我们只需要将其部署在JRE环境中,就可以解决限制问题。

对于java便是Java Cryptography Extension (JCE) Unlimited Strength Jurisdiction Policy Files.

这里提供当前几个主流jdk版本对应的JCE文件下载:

JDK6的下载地址:http://www.oracle.com/technetwork/java/javase/downloads/jce-6-download-429243.html

JDK7的下载地址:http://www.oracle.com/technetwork/java/javase/downloads/jce-7-download-432124.html

JDK8的下载地址:http://www.oracle.com/technetwork/java/javase/downloads/jce8-download-2133166.html

下面继续引用某度来的说明:

       下载的压缩包中仅有一个目录,也就是jce目录。该目录中包含了4个文件:README.txt、COPYRIGHT.html、local_policy.jar和US_export_policy.jar。其中包含的两个jar文件正是此次配置中用到的文件。
我们可以查看上述README.txt文件,你需要在JDK的JRE环境中,或者是JRE环境中配置上述两个jar文件。
切换到%JDK_Home%\jre\lib\security目录下,对应覆盖local_policy.jar和US_export_policy.jar两个文件。同时,你可能有必要在%JRE_Home%\lib\security目录下,也需要对应覆盖这两个文件。
配置权限文件的最终目的是为了使应用在运行环境中获得相应的权限,可以加强应用的安全性。通常,我们在应用服务器上安装的是JRE,而不是JDK。因此,这就很有必要在应用服务器的%JRE_Home%\lib\security目录下,对应覆盖这两个权限文件。很多开发人员往往忽略了这一点,导致事故发生。

OK,由于引用的说明写得很详细很彻底,我就不跟着扯了,文章到此就告一段落了,以后会继续更新其他算法,敬请关注!

 

© 版权声明
THE END
喜欢就支持以下吧
点赞2 分享
评论 共1条
头像
欢迎您留下宝贵的见解!
提交
头像

昵称

取消
昵称表情代码图片