Android 加密算法归纳

专业术语(摘自百度百科)

密钥:分为加密密钥和解密密钥。
明文:没有进行加密,能够直接代表原文含义的信息。
密文:经过加密处理处理之后,隐藏原文含义的信息。
加密:将明文转换成密文的实施过程。
解密:将密文转换成明文的实施过程。

基本简介

密码是通信双方按约定的法则进行信息特殊变换的一种重要保密手段。依照这些法则,变明文为密文,称为加密变换;变密文为明文,称为脱密变换。密码在早期仅对文字或数码进行加、脱密变换,随着通信技术的发展,对语音、图像、数据等都可实施加、脱密变换。

Android中的加密接口

  1. 为安全框架提供类和接口,如解析和管理证书、密钥生成、算法参数。
    java.security
    java.security.acl
    java.security.cert
    java.security.interfaces
    java.security.spec

2.为加密操作提供类和接口,如加密操作包括加密,密钥生成和密钥协议以及消息认证码(MAC)生成。
支持加密包括对称,非对称,块和流密码。
javax.crypto
javax.crypto.interfaces
javax.crypto.spec

可参考谷歌开发者文档:https://developer.android.com/reference/packages

Base64

Base64只是一种编码方式,将二进制数据转化为字符(ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/),不要使用Base64来进行加密数据。

Android中Base64接口为
android.util.Base64
image.png

SHA

安全散列算法:能计算出一个数字消息所对应到的,长度固定的字符串(又称消息摘要)的算法,且若输入的消息不同,它们对应到不同字符串的机率很高。

建议使用SHA-256算法对message字符串做哈希。
不建议使用MD2、MD4、MD5、SHA-1、RIPEMD算法来加密用户密码等敏感信息。这一类算法已经有很多破解办法。
请注意在多个字符串拼接后做SHA加密要注意添加间隔符,以区分多个字符串,否则可能会造成加密后结果相同:例如sha(AB+CD) = sha(ABC+D)。

/**
 * @author SamLeung
 * @Emial [email protected]
 */
public class SHA {
    private SHA() {
    }

    public static String encrypt1(String data) {
        return encrypt(data, "SHA-1");
    }

    public static String encrypt224(String data) {
        return encrypt(data, "SHA-224");
    }

    public static String encrypt256(String data) {
        return encrypt(data, "SHA-256");
    }

    public static String encrypt384(String data) {
        return encrypt(data, "SHA-384");
    }

    public static String encrypt512(String data) {
        return encrypt(data, "SHA-512");
    }

    /**
     * 通过SHA加密
     *
     * @param data 原始数据
     * @param algorithm 算法(SHA-1,SHA-224,SHA-256,SHA-384,和SHA-512)
     */
    public static String encrypt(String data, String algorithm) {
       String result = null;

        try {
            byte[] dataBytes = data.getBytes();
            MessageDigest md5 = MessageDigest.getInstance(algorithm);
            md5.update(dataBytes);
            byte[] bytes = md5.digest();

            StringBuilder sb = new StringBuilder();
            for (byte b : bytes) {
                if(Integer.toHexString(0xFF & b).length() == 1) {
                    sb.append("0").append(Integer.toHexString(0xFF & b));
                } else {
                    sb.append(Integer.toHexString(0xFF & b));
                }
            }

            return sb.toString();
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        }
        return result;
    }
}
HMAC

HMAC是密钥相关的哈希运算消息认证码,HMAC运算利用哈希算法,以一个密钥和一个消息为输入,生成一个消息摘要作为输出。即在SHA算法上增加了一个密钥作为数据认证,主要用于身份验证中。

/**
 * @author SamLeung
 * @Emial [email protected]
 */
public class HMAC {
    private HMAC(){}

    public static String encrypt1(byte[] data, byte[] key){
        return encrypt(data, key, "HmacSHA1");
    }

    public static String encrypt224(byte[] data, byte[] key){
        return encrypt(data, key, "HmacSHA224");
    }

    public static String encrypt256(byte[] data, byte[] key){
        return encrypt(data, key, "HmacSHA256");
    }

    public static String encrypt384(byte[] data, byte[] key){
        return encrypt(data, key, "HmacSHA384");
    }

    public static String encrypt512(byte[] data, byte[] key){
       return encrypt(data, key, "HmacSHA512");
    }

    public static String encryptMD5(byte[] data, byte[] key){
        return encrypt(data, key, "HmacMD5");
    }

    /**
     * 通过HMAC加密
     *
     * @param data 原始数据
     * @param algorithm 算法(SHA-1,SHA-224,SHA-256,SHA-384,和SHA-512)
     */
    public static String encrypt(byte[] data, byte[] key, String algorithm) {
        try {
            SecretKey secretKey = new SecretKeySpec(key, algorithm);
            Mac mac = Mac.getInstance(algorithm);
            mac.init(secretKey);
            mac.update(data);
            byte[] bytes = mac.doFinal();


            return Base64.encodeToString(bytes, Base64.NO_PADDING);
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        } catch (InvalidKeyException e) {
            e.printStackTrace();
        }

        return null;
    }
}
对称加密(DES、AES)

在对称加密算法中,数据发信方将明文(原始数据)和加密密钥一起经过特殊加密算法处理后,使其变成复杂的加密密文发送出去。收信方收到密文后,若想解读原文,则需要使用加密用过的密钥及相同算法的逆算法对密文进行解密,才能使其恢复成可读明文。在对称加密算法中,使用的密钥只有一个,发收信双方都使用这个密钥对数据进行加密和解密,这就要求解密方事先必须知道加密密钥。

建议使用AES算法,DES默认的是56位的加密密钥,已经不安全,不建议使用。

DES

DES算法为密码体制中的对称密码体制,又被称为美国数据加密标准,是1972年美国IBM公司研制的对称密码体制加密算法。 明文按64位进行分组,密钥长64位,密钥事实上是56位参与DES运算(第8、16、24、32、40、48、56、64位是校验位, 使得每个密钥都有奇数个1)分组后的明文组和56位的密钥按位替代或交换的方法形成密文组的加密方法。

/**
 * @author SamLeung
 * @Emial [email protected]
 */
public class DES {

    private DES() {}

    /**
     * 生成秘钥
     *
     * @return
     */
    public static byte[] generateKey() {
        try {
            KeyGenerator keyGen = KeyGenerator.getInstance("DES"); // 秘钥生成器
            keyGen.init(56); // 初始秘钥生成器,DES是一个基于56位密钥的对称的加密算法
            SecretKey secretKey = keyGen.generateKey(); // 生成秘钥

            return secretKey.getEncoded(); // 获取秘钥字节数组
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        }

        return null;
    }

    /**
     * 加密
     *
     * @return
     */
    public static byte[] encrypt(byte[] data, byte[] key) {
        return doCipher(data, key, Cipher.ENCRYPT_MODE);
    }

    /**
     * 解密
     *
     * @return
     */
    public static byte[] decrypt(byte[] data, byte[] key) {
        return doCipher(data, key, Cipher.DECRYPT_MODE);
    }

    /**
     * 进行加密/解密
     *
     * @param data 原始数据
     * @param key 密钥
     * @param opmode 加密/解密{@link Cipher#ENCRYPT_MODE},{@link Cipher#DECRYPT_MODE}
     * */
    private static byte[] doCipher(byte[] data, byte[] key, int opmode){
        byte[] bytes = null;
        try {
            //这里首先将密钥字节流转为secret key
            SecretKey secretKey = new SecretKeySpec(key, "DES");

            //算法参数,增加加密算法的强度
            IvParameterSpec ivParameterSpec = new IvParameterSpec(key);

            Cipher cipher = Cipher.getInstance("DES");
            cipher.init(opmode, secretKey,ivParameterSpec);
            bytes = cipher.doFinal(data);
        } 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 bytes;
    }
}
DES3

基于DES,对一块数据用三个不同的密钥进行三次加密,强度更高。

/**
 * @author SamLeung
 * @Emial [email protected]
 */
public class DES3 {
    private DES3() {
    }


    public static byte[] generateKey(){
        byte[] key = null;
        try {
            KeyGenerator keyGenerator = KeyGenerator.getInstance("DESede");
            keyGenerator.init(56 * 3); //DES是一个基于56位密钥的对称的加密算法,而DES3其实是进行3次DES。
            key = keyGenerator.generateKey().getEncoded();
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        }

        return key;
    }

    /**
     * 加密
     *
     * @return
     */
    public static byte[] encrypt(byte[] data, byte[] key) {
        return doCipher(data, key, Cipher.ENCRYPT_MODE);
    }

    /**
     * 解密
     *
     * @return
     */
    public static byte[] decrypt(byte[] data, byte[] key) {
        return doCipher(data, key, Cipher.DECRYPT_MODE);
    }

    /**
     * 进行加密/解密
     *
     * @param data 原始数据
     * @param key 密钥
     * @param opmode 加密/解密{@link Cipher#ENCRYPT_MODE},{@link Cipher#DECRYPT_MODE}
     * */
    private static byte[] doCipher(byte[] data, byte[] key, int opmode){
        byte[] bytes = null;

        try {
            SecretKey secretKey = new SecretKeySpec(key, "DESede");
            IvParameterSpec ivParameterSpec = new IvParameterSpec(key);

            Cipher cipher = Cipher.getInstance("DESede");
            cipher.init(opmode, secretKey, ivParameterSpec);
            bytes = cipher.doFinal(data);
        } catch (InvalidKeyException e) {
            e.printStackTrace();
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        } catch (BadPaddingException e) {
            e.printStackTrace();
        } catch (IllegalBlockSizeException e) {
            e.printStackTrace();
        } catch (NoSuchPaddingException e) {
            e.printStackTrace();
        } catch (InvalidAlgorithmParameterException e) {
            e.printStackTrace();
        }

        return bytes;
    }
}
AES

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

AES有五种工作模式:ECB、CBC、CTR、CFB、OCF。

不推荐使用ECB模式,推荐且使用CBC/CFB模式,可使用PKCS5Padding填充。

工作模式的加密方法可参考https://www.cnblogs.com/starwolf/p/3365834.html

/**
 * @author SamLeung
 * @Emial [email protected]
 */
public class AES {
    private static final String CBC_PKCS1_PADDING = "AES/CBC/PKCS5Padding";//注意加密模式不要使用ECB模式。ECB模式不安全
    private static final String IPS = "c^Y!mrz7AYbQRriB"; //使用密码生成器生成

    private AES() {
    }

    public static byte[] generateKey(){
        byte[] key = null;
        try {
            KeyGenerator keyGenerator = KeyGenerator.getInstance("AES");
            keyGenerator.init(256);
            return keyGenerator.generateKey().getEncoded();
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        }

        return key;
    }

    public static byte[] encrypt(byte[] data, byte[] key){
        return doCipher(data, key, Cipher.ENCRYPT_MODE);
    }

    public static byte[] decrypt(byte[] data, byte[] key){
        return doCipher(data, key, Cipher.DECRYPT_MODE);
    }

    private static byte[] doCipher(byte[] data, byte[] key, int opmode){
        byte[] bytes = null;

        try {
            SecretKey secretKey = new SecretKeySpec(key, "AES");
            IvParameterSpec ivParameterSpec = new IvParameterSpec(IPS.getBytes()); //使用CBC模式必须指定IvParameterSpec,且expected IV length of 16,即长度限制为16
            Cipher cipher = Cipher.getInstance(CBC_PKCS1_PADDING);
            cipher.init(opmode, secretKey, ivParameterSpec);

            bytes = cipher.doFinal(data);
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        } catch (NoSuchPaddingException e) {
            e.printStackTrace();
        } catch (InvalidAlgorithmParameterException e) {
            e.printStackTrace();
        } catch (InvalidKeyException e) {
            e.printStackTrace();
        } catch (BadPaddingException e) {
            e.printStackTrace();
        } catch (IllegalBlockSizeException e) {
            e.printStackTrace();
        }

        return bytes;
    }
}
AES/CBC/PKCS5Padding说明:

我们经常可以看到

Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding")

这种写法,意思是指定一个字符串,用于描述要在给定输入执行的操作(或一组操作),以生成一些输出。

API接口
image.png

transformation参数总是包括密码算法的名称(例如AES),并且可以跟随反馈模式(谷歌开发者文档中为feedback mode)和填充方案。形式一般为:

“algorithm/mode/padding” === 算法/模式/填充
或者
“algorithm” === 算法

在只指定算法的情况下,会使用默认的模式和填充方案,例如:

Cipher c = Cipher.getInstance("DES"); 

默认为

Cipher c = Cipher.getInstance("DES/CBC/PKCS5Padding"); 

在某些情况下,我们还需指定IvParameterSpec参数

IvParameterSpec ivParameterSpec = new IvParameterSpec(IPS.getBytes()); 
Cipher cipher = Cipher.getInstance(CBC_PKCS1_PADDING);
cipher.init(opmode, secretKey, ivParameterSpec);

IvParameterSpec继承自AlgorithmParameterSpec,该类的作用是指定了一个初始化向量,在密码学的领域里,初始化向量(英语:initialization vector,缩写为IV),或译初向量,又称初始变量(starting variable,缩写为SV),是一个固定长度的输入值。一般的使用上会要求它是随机数或拟随机数(pseudorandom)。使用随机数产生的初始化向量才能达到语义安全(散列函数与消息验证码也有相同要求),并让攻击者难以对同一把密钥的密文进行破解。在区块加密中,使用了初始化向量的加密模式被称为区块加密模式。

可参考:https://developer.android.com/reference/javax/crypto/Cipher

非对称加密

非对称加密算法需要两个密钥来进行加密和解密,这两个秘钥是公开密钥(public key,简称公钥)和私有密钥(private key,简称私钥)。
非对称加密与对称加密相比,其安全性更好:对称加密的通信双方使用相同的秘钥,如果一方的秘钥遭泄露,那么整个通信就会被破解。而非对称加密使用一对秘钥,一个用来加密,一个用来解密,而且公钥是公开的,秘钥是自己保存的,不需要像对称加密那样在通信之前要先同步秘钥。
非对称加密的缺点是加密和解密花费时间长、速度慢,只适合对少量数据进行加密。

RSA

非对称加密算法
1999年512位密钥的RSA被成功破解,2009年1024位密钥的RSA也被成功破解,因此建议使用2048位的密钥长度。

RSA非对称加密内容长度有限制,无论是公钥加密还是私钥加密,1024位的key最多只能加密127位数据,
否则会抛出异常:javax.crypto.IllegalBlockSizeException: input must be under 128 bytes。
这是由于RSA算法规定:待加密的字节数不能超过密钥的长度值除以8再减去11,(即:KeySize / 8 - 11),
而加密后得到密文的字节数,是密钥的长度值除以 8(即:KeySize / 8),因此在加密和解密的时候需要分进行分块加密和解密。

建议使用RSA/ECB/OAEPWithSHA256AndMGF1Padding加密算法。
但是使用OAEPWithSHA256AndMGF1Padding作为填充方式的话,对输入即data的长度有限制,数据长度不能超过191,若超过时,会抛出too much data for RSA block异常。

/**
 * @author SamLeung
 * @Emial [email protected]
 */
public class RSA {

    public static final String RSA = "RSA";// 非对称加密密钥算法
    public static final String ECB_PADDING = "RSA/ECB/PKCS1Padding";//加密填充方式
    //public static final String ECB_PADDING = "RSA/ECB/OAEPWithSHA256AndMGF1Padding";//加密填充方式
    /**
     * RSA算法规定:待加密的字节数不能超过密钥的长度值除以8再减去11。
     * 而加密后得到密文的字节数,正好是密钥的长度值除以 8。
     * */
    private static int KEYSIZE = 2048;// 密钥位数
    private static int RESERVE_BYTES = 11;
    private static int DECRYPT_BLOCK = KEYSIZE / 8; 
    private static int ENCRYPT_BLOCK = DECRYPT_BLOCK - RESERVE_BYTES; 

    /**
     * 随机生成RSA密钥对
     *
     * @param keysize 密钥长度,范围:512-2048,一般2048
     */
    public static KeyPair generateKeyPair(int keysize) {
        try {
            KeyPairGenerator kpg = KeyPairGenerator.getInstance(RSA);
            kpg.initialize(keysize);
            return kpg.genKeyPair();
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
            return null;
        }
    }

    /**
     * 用公钥对字符串进行加密
     *
     * @param data 原文
     */
    public static byte[] encryptWithPublicKey(byte[] data, byte[] key)
            throws Exception {
        Cipher cp = Cipher.getInstance(ECB_PADDING);
        cp.init(Cipher.ENCRYPT_MODE, getPublicKey(key));
        return cp.doFinal(data);
    }

    /**
     * 公钥解密
     *
     * @param data 待解密数据
     * @param key 密钥
     */
    public static byte[] decryptWithPublicKey(byte[] data, byte[] key)
            throws Exception {
        Cipher cipher = Cipher.getInstance(ECB_PADDING);
        cipher.init(Cipher.DECRYPT_MODE, getPublicKey(key));
        return cipher.doFinal(data);
    }

    /**
     * 私钥加密
     *
     * @param data 待加密数据
     * @param key 密钥
     */
    public static byte[] encryptWithPrivateKey(byte[] data, byte[] key)
            throws Exception {
        Cipher cipher = Cipher.getInstance(ECB_PADDING);
        cipher.init(Cipher.ENCRYPT_MODE, getPrivateKey(key));
        return cipher.doFinal(data);
    }


    /**
     * 私钥解密
     *
     * @param data 待解密数据
     * @param key 密钥
     */
    public static byte[] decryptWithPrivateKey(byte[] data, byte[] key)
            throws Exception {
        Cipher cp = Cipher.getInstance(ECB_PADDING);
        cp.init(Cipher.DECRYPT_MODE, getPrivateKey(key));
        byte[] arr = cp.doFinal(data);
        return arr;
    }

    public static PublicKey getPublicKey(byte[] key) throws Exception{
        X509EncodedKeySpec keySpec = new X509EncodedKeySpec(key);
        KeyFactory keyFactory = KeyFactory.getInstance(RSA);
        return keyFactory.generatePublic(keySpec);
    }

    public static PrivateKey getPrivateKey(byte[] key) throws Exception{
        PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(key);
        KeyFactory keyFactory = KeyFactory.getInstance(RSA);
        return  keyFactory.generatePrivate(keySpec);
    }

    /**
     * 分块加密
     *
     * @param data
     * @param key
     * */
    public static byte[] encryptWithPublicKeyBlock(byte[] data, byte[] key) throws Exception{
        int blockCount = (data.length / ENCRYPT_BLOCK);

        if ((data.length % ENCRYPT_BLOCK) != 0) {
            blockCount += 1;
        }

        ByteArrayOutputStream bos = new ByteArrayOutputStream(blockCount * ENCRYPT_BLOCK);
        Cipher cipher = Cipher.getInstance(ECB_PADDING);
        cipher.init(Cipher.ENCRYPT_MODE, getPublicKey(key));

        for (int offset = 0; offset < data.length; offset += ENCRYPT_BLOCK) {
            int inputLen = (data.length - offset);
            if (inputLen > ENCRYPT_BLOCK) {
                inputLen = ENCRYPT_BLOCK;
            }
            byte[] encryptedBlock = cipher.doFinal(data, offset, inputLen);
            bos.write(encryptedBlock);
        }

        bos.close();
        return bos.toByteArray();
    }

    /**
     * 分块加密
     *
     * @param data
     * @param key
     * */
    public static byte[] encryptWithPrivateKeyBlock(byte[] data, byte[] key) throws Exception{
        int blockCount = (data.length / ENCRYPT_BLOCK);

        if ((data.length % ENCRYPT_BLOCK) != 0) {
            blockCount += 1;
        }
        ByteArrayOutputStream bos = new ByteArrayOutputStream(blockCount * ENCRYPT_BLOCK);
        Cipher cipher = Cipher.getInstance(ECB_PADDING);
        cipher.init(Cipher.ENCRYPT_MODE, getPrivateKey(key));

        for (int offset = 0; offset < data.length; offset += ENCRYPT_BLOCK) {
            int inputLen = (data.length - offset);
            if (inputLen > ENCRYPT_BLOCK) {
                inputLen = ENCRYPT_BLOCK;
            }
            byte[] encryptedBlock = cipher.doFinal(data, offset, inputLen);
            bos.write(encryptedBlock);
        }

        bos.close();
        return bos.toByteArray();
    }

    /**
     * 分块解密
     *
     * @param data
     * @param key
     * */
    public static byte[] decryptWithPublicKeyBlock(byte[] data, byte[] key) throws Exception{
        int blockCount = (data.length / DECRYPT_BLOCK);
        if ((data.length % DECRYPT_BLOCK) != 0) {
            blockCount += 1;
        }
        ByteArrayOutputStream bos = new ByteArrayOutputStream(blockCount * DECRYPT_BLOCK);
        Cipher cipher = Cipher.getInstance(ECB_PADDING);
        cipher.init(Cipher.DECRYPT_MODE, getPublicKey(key));
        for (int offset = 0; offset < data.length; offset += DECRYPT_BLOCK) {
            int inputLen = (data.length - offset);
            if (inputLen > DECRYPT_BLOCK) {
                inputLen = DECRYPT_BLOCK;
            }
            byte[] decryptedBlock = cipher.doFinal(data, offset, inputLen);
            bos.write(decryptedBlock);
        }

        bos.close();
        return bos.toByteArray();
    }

    /**
     * 分块解密
     *
     * @param data
     * @param key
     * */
    public static byte[] decryptWithPrivateKeyBlock(byte[] data, byte[] key) throws Exception{
        int blockCount = (data.length / DECRYPT_BLOCK);
        if ((data.length % DECRYPT_BLOCK) != 0) {
            blockCount += 1;
        }
        ByteArrayOutputStream bos = new ByteArrayOutputStream(blockCount * DECRYPT_BLOCK);
        Cipher cipher = Cipher.getInstance(ECB_PADDING);
        cipher.init(Cipher.DECRYPT_MODE, getPrivateKey(key));
        for (int offset = 0; offset < data.length; offset += DECRYPT_BLOCK) {
            int inputLen = (data.length - offset);

            if (inputLen > DECRYPT_BLOCK) {
                inputLen = DECRYPT_BLOCK;
            }

            byte[] decryptedBlock = cipher.doFinal(data, offset, inputLen);
            bos.write(decryptedBlock);
        }

        bos.close();
        return bos.toByteArray();
    }
}

XOR

异或加密:
某个字符或者数值 x 与一个数值 m 进行异或运算得到 y ,则再用 y 与 m 进行异或运算就可还原为 x。

使用场景:
1. 两个变量的互换(不借助第三个变量);
2. 数据的简单加密解密;

调用一次可对数据加密,将结果数据作为参数传入再调用一次即对数据解密。



public class XOR {
    private XOR(){}

    public static byte[] execute(byte[] data, int key) {
        if (data == null || data.length == 0){
            return null;
        }

        int length = data.length;

        for (int i = 0; i < length; i++) {
            data[i] ^= key;
        }

        return data;
    }
}

本人本文章简书链接:https://www.jianshu.com/p/44b3f5f03c78

源码地址:https://github.com/samlss/Security

猜你喜欢

转载自blog.csdn.net/samlss/article/details/80781678
今日推荐