Android之AES对称加密

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/qq_36347817/article/details/89372644

上篇记录了Android之RSA非对称加密,针对一些支付、登录等接口。今天记录一下AES对称加密。

一、简介

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

二、安卓实现

安卓工具类代码:

import android.util.Base64;
import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;

/**
 * Created by zachary on 18/6/27.
 * 安卓端AES加密
 */
public class AesUtils {

    //加密用的Key 可以用26个字母和数字组成 此处使用AES-128-CBC加密模式,key需要为16位。
    private String sKey;
    //向量
    private static String ivParameter = "1234567890123456";
    private static AesUtils instance = null;

    private AesUtils(String sKey) {
        this.sKey = sKey;
    }

    public static AesUtils getInstance(String sKey) {
        if (instance == null)
            instance = new AesUtils(sKey);
        return instance;
    }

    // 加密
    public String encrypt(String sSrc) throws Exception {
        Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
        byte[] raw = sKey.getBytes();
        SecretKeySpec skeySpec = new SecretKeySpec(raw, "AES");
        IvParameterSpec iv = new IvParameterSpec(ivParameter.getBytes());// 使用CBC模式,需要一个向量iv,可增加加密算法的强度
        cipher.init(Cipher.ENCRYPT_MODE, skeySpec, iv);
        byte[] encrypted = cipher.doFinal(sSrc.getBytes("utf-8"));
        return Base64.encodeToString(encrypted, Base64.DEFAULT);// 此处使用BASE64做转码。
    }

    // 加密
    public static String encrypt(String encData, String secretKey) throws Exception {

        if (secretKey == null) {
            return null;
        }
        if (secretKey.length() != 16) {
            return null;
        }
        Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
        byte[] raw = secretKey.getBytes();
        SecretKeySpec skeySpec = new SecretKeySpec(raw, "AES");
        IvParameterSpec iv = new IvParameterSpec(ivParameter.getBytes());// 使用CBC模式,需要一个向量iv,可增加加密算法的强度
        cipher.init(Cipher.ENCRYPT_MODE, skeySpec, iv);
        byte[] encrypted = cipher.doFinal(encData.getBytes("utf-8"));
        return Base64.encodeToString(encrypted, Base64.DEFAULT);// 此处使用BASE64做转码。(处于android.util包)
    }

    // 解密
    public String decrypt(String sSrc) {
        try {
            byte[] raw = sKey.getBytes("ASCII");
            SecretKeySpec skeySpec = new SecretKeySpec(raw, "AES");
            Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
            IvParameterSpec iv = new IvParameterSpec(ivParameter.getBytes());
            cipher.init(Cipher.DECRYPT_MODE, skeySpec, iv);
            byte[] encrypted1 = Base64.decode(sSrc, Base64.DEFAULT);// 先用base64解密
            byte[] original = cipher.doFinal(encrypted1);
            String originalString = new String(original, "utf-8");
            return originalString;
        } catch (Exception ex) {
            return null;
        }
    }

    // 解密
    public static String decrypt(String sSrc, String key) {
        try {
            byte[] raw = key.getBytes("ASCII");
            SecretKeySpec skeySpec = new SecretKeySpec(raw, "AES");
            Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
            IvParameterSpec iv = new IvParameterSpec(ivParameter.getBytes());
            cipher.init(Cipher.DECRYPT_MODE, skeySpec, iv);
            byte[] encrypted1 = Base64.decode(sSrc, Base64.DEFAULT);// 先用base64解密
            byte[] original = cipher.doFinal(encrypted1);
            String originalString = new String(original, "utf-8");
            return originalString;
        } catch (Exception ex) {
            return null;
        }
    }
}

 使用方法:

import com.zachary.util.Util.AesUtils;
import org.junit.Test;

//测试安卓AES
public class AESTest {

    //明文
    protected String source = "zachary,加油吧!";
    //密文
    protected String encrypted = "";
    //密钥
    protected String key = "1234567890123456";

    @Test
    public void test() throws Exception {
        //加密明文--> 密文
        encrypted = AesUtils.encrypt(source, key);
        source = "";
        //解密密文--> 明文
        source = AesUtils.decrypt(encrypted, key);
    }
}

三、Java实现

随机生成Key值:

// SHA1PRNG 强随机种子算法, 要区别不同版本的调用方法
private static final String SHA1PRNG = "SHA1PRNG";      
 
//生成随机数,可以当做动态的密钥 加密和解密的密钥必须一致,不然将不能解密
public static String generateKey() {
    try {
        SecureRandom localSecureRandom = SecureRandom.getInstance(SHA1PRNG);
        byte[] bytes_key = new byte[20];
        localSecureRandom.nextBytes(bytes_key);
        String str_key = toHex(bytes_key);
        return str_key;
    } catch (Exception e) {
        e.printStackTrace();
    }
    return null;
}

对应的需要对密钥进行处理:

    //AES 加密
    private static final String AES = "AES";               
    // SHA1PRNG 强随机种子算法, 要区别不同版本的调用方法
    private static final String SHA1PRNG = "SHA1PRNG";      
    
    // 对密钥进行处理
    private static byte[] getRawKey(byte[] seed) throws Exception {
        KeyGenerator kgen = KeyGenerator.getInstance(AES);

        //for android
        SecureRandom sr ;
        // 在不同版本中,SecureRandom获取方式发生了改变
//        if(android.os.Build.VERSION.SDK_INT > 23){            // Android  6.0 以上
//            sr = SecureRandom.getInstance(SHA1PRNG,new CryptoProvider());
//        }else if(android.os.Build.VERSION.SDK_INT >= 17){     //4.2及以上
//            sr = SecureRandom.getInstance(SHA1PRNG, "Crypto");
//        }else {
//            sr = SecureRandom.getInstance(SHA1PRNG);
//        }

        // for Java
        sr = SecureRandom.getInstance(SHA1PRNG);

        sr.setSeed(seed);
        kgen.init(128, sr); //256 bits or 128 bits,192bits
        //AES中128位密钥版本有10个加密循环,192比特密钥版本有12个加密循环,256比特密钥版本则有14个加密循环。
        SecretKey skey = kgen.generateKey();
        byte[] raw = skey.getEncoded();
        return raw;
    }

完整Java工具类代码:

import java.security.Provider;
import java.security.SecureRandom;
import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;

/**
 * Created by zachary on 19/4/18.
 * Java中AES对称加密
 */
public class AESUtils {

    private final static String HEX = "0123456789ABCDEF";
    private static final String CBC_PKCS5_PADDING = "AES/CBC/PKCS5Padding"; //AES是加密方式 CBC是工作模式 PKCS5Padding是填充模式
    private static final String AES = "AES";                //AES 加密
    private static final String SHA1PRNG = "SHA1PRNG";      // SHA1PRNG 强随机种子算法, 要区别不同版本的调用方法
    private static String ivParameter = "1234567890123456"; //16位

    /**
     * 生成随机数,可以当做动态的密钥 加密和解密的密钥必须一致,不然将不能解密
     */
    public static String generateKey() {

        try {
            SecureRandom localSecureRandom = SecureRandom.getInstance(SHA1PRNG);
            byte[] bytes_key = new byte[20];
            localSecureRandom.nextBytes(bytes_key);
            String str_key = toHex(bytes_key);
            return str_key;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    // 对密钥进行处理
    private static byte[] getRawKey(byte[] seed) throws Exception {
        KeyGenerator kgen = KeyGenerator.getInstance(AES);

        //for android
        SecureRandom sr ;
        // 在不同版本中,SecureRandom获取方式发生了改变
//        if(android.os.Build.VERSION.SDK_INT > 23){            // Android  6.0 以上
//            sr = SecureRandom.getInstance(SHA1PRNG,new CryptoProvider());
//        }else if(android.os.Build.VERSION.SDK_INT >= 17){     //4.2及以上
//            sr = SecureRandom.getInstance(SHA1PRNG, "Crypto");
//        }else {
//            sr = SecureRandom.getInstance(SHA1PRNG);
//        }

        // for Java
        sr = SecureRandom.getInstance(SHA1PRNG);

        sr.setSeed(seed);
        kgen.init(128, sr); //256 bits or 128 bits,192bits
        //AES中128位密钥版本有10个加密循环,192比特密钥版本有12个加密循环,256比特密钥版本则有14个加密循环。
        SecretKey skey = kgen.generateKey();
        byte[] raw = skey.getEncoded();
        return raw;
    }

    /**
     * 加密
     * @param key       密钥
     * @param cleartext 明文
     */
    public static String encrypt(String key, String cleartext) {
        if (cleartext == null || cleartext.length() == 0) {
            return cleartext;
        }
        try {
            byte[] result = encrypt(key, cleartext.getBytes());
            return Base64Encoder.encode(result);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 加密
     */
    private static byte[] encrypt(String key, byte[] clear) throws Exception {
        byte[] raw = getRawKey(key.getBytes());
        SecretKeySpec skeySpec = new SecretKeySpec(raw, AES);
        Cipher cipher = Cipher.getInstance(CBC_PKCS5_PADDING);

        //iv是16个0(这个称为初始化向量)
        //cipher.init(Cipher.ENCRYPT_MODE, skeySpec, new IvParameterSpec(new byte[cipher.getBlockSize()]));

        cipher.init(Cipher.ENCRYPT_MODE, skeySpec, new IvParameterSpec(ivParameter.getBytes()));
        byte[] encrypted = cipher.doFinal(clear);
        return encrypted;
    }

    /**
     * 解密
     * @param key       密钥
     * @param encrypted 密文
     */
    public static String decrypt(String key, String encrypted) {
        if (encrypted == null || encrypted.length() == 0) {
            return encrypted;
        }
        try {
            byte[] enc = Base64Decoder.decodeToBytes(encrypted);
            byte[] result = decrypt(key, enc);
            return new String(result);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 解密
     */
    private static byte[] decrypt(String key, byte[] encrypted) throws Exception {
        byte[] raw = getRawKey(key.getBytes());
        SecretKeySpec skeySpec = new SecretKeySpec(raw, AES);
        Cipher cipher = Cipher.getInstance(CBC_PKCS5_PADDING);

        //iv是16个0(这个称为初始化向量)
        //cipher.init(Cipher.DECRYPT_MODE, skeySpec, new IvParameterSpec(new byte[cipher.getBlockSize()]));

        cipher.init(Cipher.DECRYPT_MODE, skeySpec, new IvParameterSpec(ivParameter.getBytes()));
        byte[] decrypted = cipher.doFinal(encrypted);
        return decrypted;
    }

    //二进制转字符
    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));
    }

    // 增加  CryptoProvider  类
    public static  class CryptoProvider extends Provider {
        /**
         * Creates a Provider and puts parameters
         */
        public CryptoProvider() {
            super("Crypto", 1.0, "HARMONY (SHA1 digest; SecureRandom; SHA1withDSA signature)");
            put("SecureRandom.SHA1PRNG",
                    "org.apache.harmony.security.provider.crypto.SHA1PRNG_SecureRandomImpl");
            put("SecureRandom.SHA1PRNG ImplementedIn", "Software");
        }
    }
    
}

补充关于Base64Decoder类和Base64Encoder类:

Base64Decoder 类:

import java.io.IOException;
import java.io.InputStream;
import android.text.TextUtils;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.FilterInputStream;

public class Base64Decoder extends FilterInputStream {

    private static final char[] chars = { 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u',
            'v', 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/' };

    // A mapping between char values and six-bit integers
    private static final int[] ints = new int[128];
    static {
        for (int i = 0; i < 64; i++) {
            ints[chars[i]] = i;
        }
    }

    private int charCount;
    private int carryOver;

    /***
     * Constructs a new Base64 decoder that reads input from the given
     * InputStream.
     *
     * @param in
     *            the input stream
     */
    private Base64Decoder(InputStream in) {
        super(in);
    }

    /***
     * Returns the next decoded character from the stream, or -1 if end of
     * stream was reached.
     *
     * @return the decoded character, or -1 if the end of the input stream is
     *         reached
     * @exception IOException
     *                if an I/O error occurs
     */
    public int read() throws IOException {
        // Read the next non-whitespace character
        int x;
        do {
            x = in.read();
            if (x == -1) {
                return -1;
            }
        } while (Character.isWhitespace((char) x));
        charCount++;

        // The '=' sign is just padding
        if (x == '=') {
            return -1; // effective end of stream
        }

        // Convert from raw form to 6-bit form
        x = ints[x];

        // Calculate which character we're decoding now
        int mode = (charCount - 1) % 4;

        // First char save all six bits, go for another
        if (mode == 0) {
            carryOver = x & 63;
            return read();
        }
        // Second char use previous six bits and first two new bits,
        // save last four bits
        else if (mode == 1) {
            int decoded = ((carryOver << 2) + (x >> 4)) & 255;
            carryOver = x & 15;
            return decoded;
        }
        // Third char use previous four bits and first four new bits,
        // save last two bits
        else if (mode == 2) {
            int decoded = ((carryOver << 4) + (x >> 2)) & 255;
            carryOver = x & 3;
            return decoded;
        }
        // Fourth char use previous two bits and all six new bits
        else if (mode == 3) {
            int decoded = ((carryOver << 6) + x) & 255;
            return decoded;
        }
        return -1; // can't actually reach this line
    }

    /***
     * Reads decoded data into an array of bytes and returns the actual number
     * of bytes read, or -1 if end of stream was reached.
     *
     * @param buf
     *            the buffer into which the data is read
     * @param off
     *            the start offset of the data
     * @param len
     *            the maximum number of bytes to read
     * @return the actual number of bytes read, or -1 if the end of the input
     *         stream is reached
     * @exception IOException
     *                if an I/O error occurs
     */
    public int read(byte[] buf, int off, int len) throws IOException {
        if (buf.length < (len + off - 1)) {
            throw new IOException("The input buffer is too small: " + len + " bytes requested starting at offset " + off + " while the buffer " + " is only " + buf.length + " bytes long.");
        }

        // This could of course be optimized
        int i;
        for (i = 0; i < len; i++) {
            int x = read();
            if (x == -1 && i == 0) { // an immediate -1 returns -1
                return -1;
            } else if (x == -1) { // a later -1 returns the chars read so far
                break;
            }
            buf[off + i] = (byte) x;
        }
        return i;
    }

    /***
     * Returns the decoded form of the given encoded string, as a String. Note
     * that not all binary data can be represented as a String, so this method
     * should only be used for encoded String data. Use decodeToBytes()
     * otherwise.
     *
     * @param encoded
     *            the string to decode
     * @return the decoded form of the encoded string
     */
    public static String decode(String encoded) {
        if (TextUtils.isEmpty(encoded)) {
            return "";
        }
        return new String(decodeToBytes(encoded));
    }

    /***
     * Returns the decoded form of the given encoded string, as bytes.
     *
     * @param encoded
     *            the string to decode
     * @return the decoded form of the encoded string
     */
    public static byte[] decodeToBytes(String encoded) {
        byte[] bytes = encoded.getBytes();
        Base64Decoder in = new Base64Decoder(new ByteArrayInputStream(bytes));
        ByteArrayOutputStream out = new ByteArrayOutputStream((int) (bytes.length * 0.75));
        try {
            byte[] buf = new byte[4 * 1024]; // 4K buffer
            int bytesRead;
            while ((bytesRead = in.read(buf)) != -1) {
                out.write(buf, 0, bytesRead);
            }
            return out.toByteArray();
        } catch (IOException e) {
            throw new RuntimeException(e);
        } finally {
            try {
                in.close();
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
            try {
                out.close();
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }
    }
}

Base64Encoder类:

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.FilterOutputStream;
import java.io.OutputStream;

public class Base64Encoder extends FilterOutputStream {

    private static final char[] chars = { 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u',
            'v', 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/' };

    private int charCount;
    private int carryOver;
    // 是否每76字节换行
    private boolean isWrapBreak = true;

    /***
     * Constructs a new Base64 encoder that writes output to the given
     * OutputStream.
     *
     * @param out
     *            the output stream
     */
    private Base64Encoder(OutputStream out) {
        super(out);
    }

    /***
     * Constructs a new Base64 encoder that writes output to the given
     * OutputStream.
     *
     * @param out
     *            the output stream
     */
    private Base64Encoder(OutputStream out, boolean isWrapBreak) {
        this(out);
        this.isWrapBreak = isWrapBreak;
    }

    /***
     * Writes the given byte to the output stream in an encoded form.
     *
     * @exception IOException
     *                if an I/O error occurs
     */
    public void write(int b) throws IOException {
        // Take 24-bits from three octets, translate into four encoded chars
        // Break lines at 76 chars
        // If necessary, pad with 0 bits on the right at the end
        // Use = signs as padding at the end to ensure encodedLength % 4 == 0

        // Remove the sign bit,
        // thanks to Christian Schweingruber <[email protected]>
        if (b < 0) {
            b += 256;
        }

        // First byte use first six bits, save last two bits
        if (charCount % 3 == 0) {
            int lookup = b >> 2;
            carryOver = b & 3; // last two bits
            out.write(chars[lookup]);
        }
        // Second byte use previous two bits and first four new bits,
        // save last four bits
        else if (charCount % 3 == 1) {
            int lookup = ((carryOver << 4) + (b >> 4)) & 63;
            carryOver = b & 15; // last four bits
            out.write(chars[lookup]);
        }
        // Third byte use previous four bits and first two new bits,
        // then use last six new bits
        else if (charCount % 3 == 2) {
            int lookup = ((carryOver << 2) + (b >> 6)) & 63;
            out.write(chars[lookup]);
            lookup = b & 63; // last six bits
            out.write(chars[lookup]);
            carryOver = 0;
        }
        charCount++;

        // Add newline every 76 output chars (that's 57 input chars)
        if (this.isWrapBreak && charCount % 57 == 0) {
            out.write('\n');
        }
    }

    /***
     * Writes the given byte array to the output stream in an encoded form.
     *
     * @param buf
     *            the data to be written
     * @param off
     *            the start offset of the data
     * @param len
     *            the length of the data
     * @exception IOException
     *                if an I/O error occurs
     */
    public void write(byte[] buf, int off, int len) throws IOException {
        // This could of course be optimized
        for (int i = 0; i < len; i++) {
            write(buf[off + i]);
        }
    }

    /***
     * Closes the stream, this MUST be called to ensure proper padding is
     * written to the end of the output stream.
     *
     * @exception IOException
     *                if an I/O error occurs
     */
    public void close() throws IOException {
        // Handle leftover bytes
        if (charCount % 3 == 1) { // one leftover
            int lookup = (carryOver << 4) & 63;
            out.write(chars[lookup]);
            out.write('=');
            out.write('=');
        } else if (charCount % 3 == 2) { // two leftovers
            int lookup = (carryOver << 2) & 63;
            out.write(chars[lookup]);
            out.write('=');
        }
        super.close();
    }

    /***
     * Returns the encoded form of the given unencoded string.<br>
     * 默认是否每76字节换行
     *
     * @param bytes
     *            the bytes to encode
     * @return the encoded form of the unencoded string
     * @throws IOException
     */
    public static String encode(byte[] bytes) {
        return encode(bytes, true);
    }

    /***
     * Returns the encoded form of the given unencoded string.
     *
     * @param bytes
     *            the bytes to encode
     * @param isWrapBreak
     *            是否每76字节换行
     * @return the encoded form of the unencoded string
     * @throws IOException
     */
    public static String encode(byte[] bytes, boolean isWrapBreak) {
        ByteArrayOutputStream out = new ByteArrayOutputStream((int) (bytes.length * 1.4));
        Base64Encoder encodedOut = new Base64Encoder(out, isWrapBreak);
        try {
            encodedOut.write(bytes);
        } catch (IOException e) {
            throw new RuntimeException(e);
        } finally {
            try {
                encodedOut.close();
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }
        return out.toString();
    }

}

 使用方法:

import org.junit.Before;
import org.junit.Test;

//测试安卓AES
public class AESTest {

    //明文
    protected String source = "zachary,加油吧!";
    //密文
    protected String encrypted = "";
    //密钥
    protected String key = "";

    @Before
    public void init(){
        key = AESUtils.generateKey();
        //key = "0123456789123456";
    }

    @Test
    public void test() {
        //加密明文--> 密文
        encrypted = AESUtils.encrypt(key,source);
        source = "";
        //解密密文--> 明文
        source = AESUtils.decrypt(key,encrypted);
    }
}

猜你喜欢

转载自blog.csdn.net/qq_36347817/article/details/89372644