Android 最全的AES加密算法及其实现(前所未有的震撼)

原文地址:https://blog.csdn.net/whb20081815/article/details/73997645


一:什么是AES加密
AES高级加密标准(英语:Advanced Encryption Standard,缩写:AES),在密码学中又称Rijndael加密法,是美国联邦政府采用的一种区块加密标准。
这个标准用来替代原先的DES,已经被多方分析且广为全世界所使用。

AES 是一个迭代的、 对称密钥分组 的密码, AES算法加密强度大,执行效率高,使用简单,实际开发中建议选择AES 算法。

  • 二:AES加密安卓中的实现
    安卓中AES加密方案如下:
    安卓里面提供了2个API:SecretKeySpec和Cipher
    明文和密文,由Ciper提供加密和解密的函数

    加密的具体过程:

    String filecontent = CXAESUtil.encrypt(AESKEY, inputString);//加密后的

    /**
     * 加密
     * 
     * @param key
     *            密钥
     * @param src
     *            加密文本
     * @return
     * @throws Exception
     */
    public static String encrypt(String key, String src) throws Exception {
        // /src = Base64.encodeToString(src.getBytes(), Base64.DEFAULT);
        byte[] rawKey = toMakekey(key, keyLenght, defaultV).getBytes();// key.getBytes();
        byte[] result = encrypt(rawKey, src.getBytes("utf-8"));
        // result = Base64.encode(result, Base64.DEFAULT);
        return toHex(result);
    }
    /**
     * 真正的加密过程
     * 1.通过密钥得到一个密钥专用的对象SecretKeySpec
     * 2.Cipher 加密算法,加密模式和填充方式三部分或指定加密算 (可以只用写算法然后用默认的其他方式)Cipher.getInstance("AES");
     * @param key
     * @param src
     * @return
     * @throws Exception
     */
    private static byte[] encrypt(byte[] key, byte[] src) throws Exception {
        SecretKeySpec skeySpec = new SecretKeySpec(key, "AES");
        Cipher cipher = Cipher.getInstance("AES");
        cipher.init(Cipher.ENCRYPT_MODE, skeySpec, new IvParameterSpec(new byte[cipher.getBlockSize()]));
        byte[] encrypted = cipher.doFinal(src);
        return encrypted;
    }
    /**
     * 二进制转字符,转成了16进制
     * 0123456789abcdefg
     * @param buf
     * @return
     */
    public static String toHex(byte[] buf) {
        if (buf == null)
            return "";
        StringBuffer result = new StringBuffer(2 * buf.length);
        for (int i = 0; i < buf.length; i++) {
            appendHex(result, buf[i]);
        }
        return result.toString();
    }
    
    private static void appendHex(StringBuffer sb, byte b) {
        sb.append(HEX.charAt((b >> 4) & 0x0f)).append(HEX.charAt(b & 0x0f));
    }

    注意的问题:先前一直失败,其重点是对 “填充模式” 的应用,我最终使用了AES/CFB/NoPadding,当不满16字节时,加密后数据长度不变。

    Cipher cipher = Cipher.getInstance("AES");
    cipher.init(Cipher.ENCRYPT_MODE, skeySpec, new IvParameterSpec(new byte[cipher.getBlockSize()]));

    1. 算法/模式/填充                16字节加密后数据长度        不满16字节加密后长度    
    2. AES/CBC/NoPadding             16                          不支持    
    3. AES/CBC/PKCS5Padding          32                          16    
    4. AES/CBC/ISO10126Padding       32                          16    
    5. AES/CFB/NoPadding             16                          原始数据长度    
    6. AES/CFB/PKCS5Padding          32                          16    
    7. AES/CFB/ISO10126Padding       32                          16    
    8. AES/ECB/NoPadding             16                          不支持    
    9. AES/ECB/PKCS5Padding          32                          16    
    10. AES/ECB/ISO10126Padding       32                          16    
    11. AES/OFB/NoPadding             16                          原始数据长度    
    12. AES/OFB/PKCS5Padding          32                          16    
    13. AES/OFB/ISO10126Padding       32                          16    
    14. AES/PCBC/NoPadding            16                          不支持    
    15. AES/PCBC/PKCS5Padding         32                          16    
    16. AES/PCBC/ISO10126Padding      32                          16    


    当原始数据长度为16的整数倍时,假如原始数据长度等于16*n,则使用NoPadding时加密后数据长度等于16*n,其它情况下加密数据长度等于16*(n+1)。在不足16的整数倍的情况下,假如原始数据长度等于16*n+m[其中m小于16],除了NoPadding填充之外的任何方 式,加密数据长度都等于16*(n+1);NoPadding填充情况下,CBC、ECB和PCBC三种模式是不支持的,CFB、OFB两种模式下则加密数据长度等于原始数据长度。

    三:AES的算法分析
    AES的加密算法解析:

       根据使用的密码长度,AES最常见的有3种方案,用以适应不同的场景要求,分别是AES-128、AES-192和AES-256。本文主要对AES-128进行介绍,另外两种的思路基本一样,只是轮数会适当增加。
    算法中16字节的明文、密文和轮密钥都以一个4x4的矩阵表示。AES加密有很多轮的重复和变换。大致步骤如下:1、密钥扩展(KeyExpansion),2、初始轮(Initial Round),3、重复轮(Rounds),每一轮又包括:SubBytes、ShiftRows、MixColumns、AddRoundKey,4、最终轮(Final Round),最终轮没有MixColumns。






  • Rijnadel(State, CipherKey) {  
  •   
  • //初始化  
  •   
  • KeyExpansion( CipherKey, ExpandedKey );//生成子密钥  
  •   
  • AddRoundKey( State, ExpandedKey );//与子密钥位与  
  •   
  • // 前Nr-1轮  
  •   
  • for(i =1; i < Nr; i++) {  
  •   
  • ByteSub(State);// S-盒  
  •   
  • ShiftRow(State);// 行被移位  
  •   
  • MixColumn(State);// 列被混叠  
  •   
  • AddRoundKey (State, ExpandedKey ); //与子密钥位与  
  •   
  • }  
  •   
  • //最后一轮  
  •   
  • ByteSub(State);  
  •   
  • ShiftRow(State);  
  •   
  • AddRoundKey (State, ExpandedKey );  
  •   
  • }  

从图上可以看出AES加解密包括10轮,前面9轮包含 S盒变换、行移位、列混淆、轮密钥加4个阶段 ,最后一轮则少了列混淆这个阶段。

解密过程:
/**
 * 解密
 * 
 * @param key
 *            密钥
 * @param encrypted
 *            待揭秘文本
 * @return
 * @throws Exception
 */
public static String decrypt(String key, String encrypted) throws Exception {
    byte[] rawKey = toMakekey(key, keyLenght, defaultV).getBytes();// key.getBytes();
    byte[] enc = toByte(encrypted);
    // enc = Base64.decode(enc, Base64.DEFAULT);
    byte[] result = decrypt(rawKey, enc);
    // /result = Base64.decode(result, Base64.DEFAULT);
    return new String(result, "utf-8");
}
/**
 * 真正的解密过程
 * 
 * @param key
 * @param encrypted
 * @return
 * @throws Exception
 */
private static byte[] decrypt(byte[] key, byte[] encrypted) throws Exception {
    SecretKeySpec skeySpec = new SecretKeySpec(key, "AES");
    Cipher cipher = Cipher.getInstance("AES");
    cipher.init(Cipher.DECRYPT_MODE, skeySpec, new IvParameterSpec(new byte[cipher.getBlockSize()]));
    byte[] decrypted = cipher.doFinal(encrypted);
    return decrypted;
}
加密总结:
1.把秘钥写在代码里有一定风险,当别人反编译代码的时候,可能会看到秘钥, Android  开发里建议用JNI 把秘钥值写到C 代码里,甚至拆分成几份,最后再组合成真正的秘钥
2如果先把一个文件转换成字节数组,然后再加密,最后生成文件,这样很大机率很会产生OOM,所以这里利用了FileChannel,一次读取一定的字节数,而后再进行加密解密,最后再通过Channel生成新文件
3.一个加密的密钥和加密的字符串,然后通过AES算法密码把字节数组加密,然后转换成了16进制,得到数字和字母
4.加密和解密都一样,就是在设置AES模式(加密和解密)不一样,还有一个就是对内容的处理,字节和16进制的相互转换
5. 同样的代码,Android和java的运行结果是不同的, 这应该是2端的编码不同导致的。

四:为什么采用对称加密的方式?
非对称加密一般不会单独拿来使用,他并不是为了取代对称加密而出现的,非对称加密速度比对称加密慢很多,
极端情况下会慢1000 倍,所以一般不会用来加密大量数据,通常我们经常会将对称加密和非对称加密两种技术联合起来使用,例如用非对称加密来给称加密里的秘钥进行加密(即秘钥交换)。

五:AES加密在安卓中的运用
实际 android  开发中可以用对称加密(例如选择AES 算法)来解决很多问题,例如:
  • 做一个管理密码的app,我们在不同的网站里使用不同账号密码,很难记住,想做个app 统一管理,但是账号密码保存在手机里,一旦丢失了容易造成安全隐患,所以需要一种加密算法,将账号密码信息加密起来保管,这时候如果使用对称加密算法,将数据进行加密,秘钥我们自己记在心里,只需要记住一个密码。需要的时候可以还原信息。
  • android 里需要把一些敏感数据保存到SharedPrefrence 里的时候,也可以使用对称加密,这样可以在需要的时候还原。
  • 请求网络接口的时候,我们需要上传一些敏感数据,同样也可以使用对称加密,服务端使用同样的算法就可以解密。或者服务端需要给客户端传递数据,同样也可以先加密,然后客户端使用同样算法解密。

六;安卓中用的到加密
1.Https编程  :应该是使用带安全的网络协议处理。除非你本地需要加密
2.数据签名:混淆代码和防二次打包的APK加密技术
3.对称加密:可以先将数据通过某种加密方式加密发送到服务器端,然后服务器端再解密 ,项目中除了登陆,支付等接口采用rsa非对称加密,之外的采用aes对称加密
4.非对称加密====支付宝


final EditText editText=(EditText)findViewById(R.id.edit_query);
final TextView tv_result=(TextView)findViewById(R.id.tv_result);
final TextView tv_before=(TextView)findViewById(R.id.tv_before);

Button btn_entry=(Button)findViewById(R.id.btn_encry);


btn_entry.setOnClickListener(new View.OnClickListener() {

    @Override
    public void onClick(View v) {
        try {

            String inputString=editText.getText().toString();

            Log.i(TAG,"输入的内容="+inputString);

            String filecontent = CXAESUtil.encrypt(AESKEY, inputString);//加密后的

            tv_before.setText("加密后的内容:"+filecontent);

            Log.i(TAG,"加密后的内容="+filecontent);

            String decryptString  = CXAESUtil.decrypt(AESKEY, filecontent);//加密后的

            Log.i(TAG,"解密后的内容="+decryptString);

            tv_result.setText("解密后的内容:"+decryptString);

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

Button btn_reset=(Button)findViewById(R.id.btn_reset);

btn_reset.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        editText.getText().clear();
    }
});


package password.aes.peng.cx.com.aesdemo;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;

import javax.crypto.Cipher;
import javax.crypto.CipherInputStream;
import javax.crypto.CipherOutputStream;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;

public class CXAESUtil {

    private final static String HEX = "0123456789ABCDEF";
    private static final int keyLenght = 16;
    private static final String defaultV = "0";

    /**
     * 加密
     * 
     * @param key
     *            密钥
     * @param src
     *            加密文本
     * @return
     * @throws Exception
     */
    public static String encrypt(String key, String src) throws Exception {
        // /src = Base64.encodeToString(src.getBytes(), Base64.DEFAULT);
        byte[] rawKey = toMakekey(key, keyLenght, defaultV).getBytes();// key.getBytes();
        byte[] result = encrypt(rawKey, src.getBytes("utf-8"));
        // result = Base64.encode(result, Base64.DEFAULT);
        return toHex(result);
    }

    /**
     * 加密
     * 
     * @param key
     *            密钥
     * @param src
     *            加密文本
     * @return
     * @throws Exception
     */
    public static String encrypt2Java(String key, String src) throws Exception {
        // /src = Base64.encodeToString(src.getBytes(), Base64.DEFAULT);
        byte[] rawKey = toMakekey(key, keyLenght, defaultV).getBytes();// key.getBytes();
        byte[] result = encrypt2Java(rawKey, src.getBytes("utf-8"));
        // result = Base64.encode(result, Base64.DEFAULT);
        return toHex(result);
    }
    
    /**
     * 解密
     * 
     * @param key
     *            密钥
     * @param encrypted
     *            待揭秘文本
     * @return
     * @throws Exception
     */
    public static String decrypt(String key, String encrypted) throws Exception {
        byte[] rawKey = toMakekey(key, keyLenght, defaultV).getBytes();// key.getBytes();
        byte[] enc = toByte(encrypted);
        // enc = Base64.decode(enc, Base64.DEFAULT);
        byte[] result = decrypt(rawKey, enc);
        // /result = Base64.decode(result, Base64.DEFAULT);
        return new String(result, "utf-8");
    }

    /**
     * 密钥key ,默认补的数字,补全16位数,以保证安全补全至少16位长度,androidios对接通过
     * @param str
     * @param strLength
     * @param val
     * @return
     */
    private static String toMakekey(String str, int strLength, String val) {

        int strLen = str.length();
        if (strLen < strLength) {
            while (strLen < strLength) {
                StringBuffer buffer = new StringBuffer();
                buffer.append(str).append(val);
                str = buffer.toString();
                strLen = str.length();
            }
        }
        return str;
    }

    /**
     * 真正的加密过程
     * 1.通过密钥得到一个密钥专用的对象SecretKeySpec
     * 2.Cipher 加密算法,加密模式和填充方式三部分或指定加密算 (可以只用写算法然后用默认的其他方式)Cipher.getInstance("AES");
     * @param key
     * @param src
     * @return
     * @throws Exception
     */
    private static byte[] encrypt(byte[] key, byte[] src) throws Exception {
        SecretKeySpec skeySpec = new SecretKeySpec(key, "AES");
        Cipher cipher = Cipher.getInstance("AES");
        cipher.init(Cipher.ENCRYPT_MODE, skeySpec, new IvParameterSpec(new byte[cipher.getBlockSize()]));
        byte[] encrypted = cipher.doFinal(src);
        return encrypted;
    }
    
    /**
     * 真正的加密过程
     * 
     * @param key
     * @param src
     * @return
     * @throws Exception
     */
    private static byte[] encrypt2Java(byte[] key, byte[] src) throws Exception {
        SecretKeySpec skeySpec = new SecretKeySpec(key, "AES");
        Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
        cipher.init(Cipher.ENCRYPT_MODE, skeySpec, new IvParameterSpec(new byte[cipher.getBlockSize()]));
        byte[] encrypted = cipher.doFinal(src);
        return encrypted;
    }

    /**
     * 真正的解密过程
     * 
     * @param key
     * @param encrypted
     * @return
     * @throws Exception
     */
    private static byte[] decrypt(byte[] key, byte[] encrypted) throws Exception {
        SecretKeySpec skeySpec = new SecretKeySpec(key, "AES");
        Cipher cipher = Cipher.getInstance("AES");
        cipher.init(Cipher.DECRYPT_MODE, skeySpec, new IvParameterSpec(new byte[cipher.getBlockSize()]));
        byte[] decrypted = cipher.doFinal(encrypted);
        return decrypted;
    }

    public static String toHex(String txt) {
        return toHex(txt.getBytes());
    }

    public static String fromHex(String hex) {
        return new String(toByte(hex));
    }


    /**
     * 16进制转化为字节数组
     * @param hexString
     * @return
     */
    public static byte[] toByte(String hexString) {
        int len = hexString.length() / 2;
        byte[] result = new byte[len];
        for (int i = 0; i < len; i++)
            result[i] = Integer.valueOf(hexString.substring(2 * i, 2 * i + 2), 16).byteValue();
        return result;
    }


    /**
     * 二进制转字符,转成了16进制
     * 0123456789abcdefg
     * @param buf
     * @return
     */
    public static String toHex(byte[] buf) {
        if (buf == null)
            return "";
        StringBuffer result = new StringBuffer(2 * buf.length);
        for (int i = 0; i < buf.length; i++) {
            appendHex(result, buf[i]);
        }
        return result.toString();
    }

    private static void appendHex(StringBuffer sb, byte b) {
        sb.append(HEX.charAt((b >> 4) & 0x0f)).append(HEX.charAt(b & 0x0f));
    }

    /**
     * 初始化 AES Cipher
     * @param sKey
     * @param cipherMode
     * @return
     */
    public static Cipher initAESCipher(String sKey, int cipherMode) {
        // 创建Key gen
        // KeyGenerator keyGenerator = null;
        Cipher cipher = null;
        try {
            /*
             * keyGenerator = KeyGenerator.getInstance("AES");
             * keyGenerator.init(128, new SecureRandom(sKey.getBytes()));
             * SecretKey secretKey = keyGenerator.generateKey(); byte[]
             * codeFormat = secretKey.getEncoded(); SecretKeySpec key = new
             * SecretKeySpec(codeFormat, "AES"); cipher =
             * Cipher.getInstance("AES"); //初始化 cipher.init(cipherMode, key);
             */
            byte[] rawKey = toMakekey(sKey, keyLenght, defaultV).getBytes();
            SecretKeySpec skeySpec = new SecretKeySpec(rawKey, "AES");
            cipher = Cipher.getInstance("AES");
            cipher.init(cipherMode, skeySpec, new IvParameterSpec(new byte[cipher.getBlockSize()]));
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();  // To change body of catch statement use File |
                                 // Settings | File Templates.
        } catch (NoSuchPaddingException e) {
            e.printStackTrace();  // To change body of catch statement use File |
                                 // Settings | File Templates.
        } catch (InvalidKeyException e) {
            e.printStackTrace();  // To change body of catch statement use File |
                                 // Settings | File Templates.
        } catch (InvalidAlgorithmParameterException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        return cipher;
    }

    /**
     * 对文件进行AES加密
     * @param sourceFile
     * @param fileType
     * @param sKey
     * @return
     */
    public static File encryptFile(File sourceFile, String toFile, String dir, String sKey) {
        // 新建临时加密文件
        File encrypfile = null;
        InputStream inputStream = null;
        OutputStream outputStream = null;
        try {
            inputStream = new FileInputStream(sourceFile);
            encrypfile = new File(dir + toFile);
            outputStream = new FileOutputStream(encrypfile);
            Cipher cipher = initAESCipher(sKey, Cipher.ENCRYPT_MODE);
            // 以加密流写入文件
            CipherInputStream cipherInputStream = new CipherInputStream(inputStream, cipher);
            byte[] cache = new byte[1024];
            int nRead = 0;
            while ((nRead = cipherInputStream.read(cache)) != -1) {
                outputStream.write(cache, 0, nRead);
                outputStream.flush();
            }
            cipherInputStream.close();
        } catch (FileNotFoundException e) {
            e.printStackTrace();  // To change body of catch statement use File |
                                 // Settings | File Templates.
        } catch (IOException e) {
            e.printStackTrace();  // To change body of catch statement use File |
                                 // Settings | File Templates.
        } finally {
            try {
                inputStream.close();
            } catch (IOException e) {
                e.printStackTrace();  // To change body of catch statement use
                                     // File | Settings | File Templates.
            }
            try {
                outputStream.close();
            } catch (IOException e) {
                e.printStackTrace();  // To change body of catch statement use
                                     // File | Settings | File Templates.
            }
        }
        return encrypfile;
    }

    /**
     * AES方式解密文件
     * @param sourceFile
     * @return
     */
    public static File decryptFile(File sourceFile, String toFile, String dir, String sKey) {
        File decryptFile = null;
        InputStream inputStream = null;
        OutputStream outputStream = null;
        try {
            decryptFile = new File(dir + toFile);
            Cipher cipher = initAESCipher(sKey, Cipher.DECRYPT_MODE);
            inputStream = new FileInputStream(sourceFile);
            outputStream = new FileOutputStream(decryptFile);
            CipherOutputStream cipherOutputStream = new CipherOutputStream(outputStream, cipher);
            byte[] buffer = new byte[1024];
            int r;
            while ((r = inputStream.read(buffer)) >= 0) {
                cipherOutputStream.write(buffer, 0, r);
            }
            cipherOutputStream.close();
        } catch (IOException e) {
            e.printStackTrace();  // To change body of catch statement use File |
                                 // Settings | File Templates.
        } finally {
            try {
                inputStream.close();
            } catch (IOException e) {
                e.printStackTrace();  // To change body of catch statement use
                                     // File | Settings | File Templates.
            }
            try {
                outputStream.close();
            } catch (IOException e) {
                e.printStackTrace();  // To change body of catch statement use
                                     // File | Settings | File Templates.
            }
        }
        return decryptFile;
    }

}


DEMO地址:不知道为什么上传不了。


猜你喜欢

转载自blog.csdn.net/chenhuakang/article/details/80223202