Android的本地密钥的安全性

 在Android开发中,我们有时候可能需要将一些密码或者比较静态字符串放到APP里面,怎样保证这些数据的安全性呢?

通常我们会将这些数据进行加密处理,那加密之后的数据存放到APP的什么地方呢?

1、密钥本地存放

保存加密数据的方式 安全性
直接放到sharedprefence中 安全性最低
直接编码到java文件中 不安全,dex很容易被逆向
密码分成几段,存储在不同地方,例如文件、代码等 只要多花时间也可以被逆向
ndk开发,密钥放在so文件,加密解密操作放到so文件里 提高了安全性,但是有经验的也会逆向
gradle中配置build变量 不安全,dex很容易被逆向,但避免用户的恶意删除
string.xml中 不安全,虽然该string已经对应的数字串,但是逆向之后还是可以被跟踪到

综上,并没有完全的安全性,只是增大了逆向成本。所以为了保证密钥的安全性,可以通过多种方式混合,增加逆向难度。

不推荐使用文件、数据库来保存静态密钥,容易被用户恶意删除。

2、Base64编码算法

64个字符来表示任意二进制数据。只是一种编码方式,通常为了防止传输出错,对加密后的数据进行base64编码。

  private void base64(){
        String base = "Base64编码难道很长编码出来的才长吗";
        byte[] baseByte = base.getBytes();
        String result = Android.util.Base64.encodeToString(baseByte,Base64.DEFAULT);
    }

Base64类里面的几种类型

DEFAULT 默认方式,当字符串过长(一般超过76)会在中间加一个换行符,字符串最后也会增加一个换行符
NO_PADDING 转码时会对字符串长度余4的=部位,该类型就是省略加密字符串最后的=

NO_WRAP

去掉所有的换行符,Android通常建议使用该方式
CRLF

使用CR LF这一对作为一行的结尾而不是Lnuix风格的LF

URL_SAFE 转码时会生成+/=特殊字符,该类型就是不生成URL和文件名有特殊意识的字符来作为加密字符,具体以-_取代+/

针对例子中的字符串,不同的类型编码出来

DEFAULT QmFzZTY057yW56CB6Zq+6YGT5b6I6ZW/57yW56CB5Ye65p2l55qE5omN6ZW/5ZCX 会含有+/,同时前后都有换行符
NO_PADDING    
NO_WRAP QmFzZTY057yW56CB6Zq+6YGT5b6I6ZW/57yW56CB5Ye65p2l55qE5omN6ZW/5ZCX 换行符没有了
CRLF    
URL_SAFE QmFzZTY057yW56CB6Zq-6YGT5b6I6ZW_57yW56CB5Ye65p2l55qE5omN6ZW_5ZCX 用-_代替了+/

3、随机数生成器

在Android开发中使用SecureRandom来获取随机数,不要使用Random。但在使用过程中不要设置种子

    private void random(){
        SecureRandom secureRandom = new SecureRandom();
       Log.d(TAG,"random = "+secureRandom.nextInt()) ;
    }

4、几种常见的加密

不可逆 数字摘要:通过算法将短数据变为长数据,标示数据的唯一性。 md5:用来加密密码
sha1:比md5更长,更安全,效率要低
可逆 对称加密:密钥可以指定,只有一把密钥,如果密钥泄漏数据就会暴漏。 DES/AES 加密速度快、但安全性低,只要密钥暴漏,数据就可以被解密
非对称加密:两把密钥,有程序生成,不能指定 RSA

加密速度慢,但安全性比较高。

公钥加密只能私密解密;私钥加密只能公密解密。

1)MD5加密代码

private String md5(String string) {
        MessageDigest md5 = null;
        try {
            md5 = MessageDigest.getInstance("MD5");
            byte[] bytes = md5.digest(string.getBytes());
            return byteToHex(bytes);
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        }
        return "";
    }

    private String byteToHex(byte[] bytes) {

        String result = "";
        if (bytes == null || bytes.length == 0) {
            return result;
        }
        for (byte b : bytes) {
            //byte的大小为8bits而int的大小为32bits,将bit转换成16进制
            String temp = Integer.toHexString(b & 0xff);
            if (temp.length() == 1) {
                temp = "0" + temp;
            }
            result += temp;
        }
        return result;
    }

2)SHA1加密

    private String sha1(String string) {
        MessageDigest sha1 = null;
        try {
            sha1 = MessageDigest.getInstance("SHA-1");
            byte[] bytes = sha1.digest(string.getBytes());
            return byteToHex(bytes);
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        }
        return "";
    }

对比MD5和SHA1对123456的加密

MD5 e10adc3949ba59abbe56e057f20f883e
SHA1 7c4a8d09ca3762af61e59520943dc26494f8941b

显然,SHA1的生成的字符串要比MD5要长。

3)DES加密

 /**
     * Description 根据键值进行加密,返回字符串进行Base64编码
     *
     * @param data
     * @param key  加密键
     * @return
     * @throws Exception
     */
    public static String encryptToBase64(byte[] data, String key) throws Exception {
        return Base64.encodeToString(encrypt(data, key), Base64.NO_WRAP);
    }

    /**
     * Description 根据键值进行解密
     *
     * @param data 该data被base64编码
     * @param key  加密键byte数组
     * @return
     * @throws IOException
     * @throws Exception
     */
    public static String decryptInBase64(String data, String key) throws Exception {
        return new String(decrypt(Base64.decode(data, Base64.NO_WRAP), key));
    }

    /**
     * Description 根据键值进行加密
     *
     * @param data
     * @param key  加密键
     * @return
     * @throws Exception
     */
    public static byte[] encrypt(byte[] data, String key) throws Exception {
        byte[] bt = encrypt(data, key.getBytes());
        return bt;
    }

    /**
     * Description 根据键值进行解密
     *
     * @param data
     * @param key  加密键byte数组
     * @return
     * @throws IOException
     * @throws Exception
     */
    public static byte[] decrypt(byte[] data, String key) throws IOException,
            Exception {
        if (data == null)
            return null;
        byte[] bt = decrypt(data, key.getBytes());
        return bt;
    }

    /**
     * Description 根据键值进行加密
     *
     * @param data
     * @param key  加密键byte数组
     * @return
     * @throws Exception
     */
    private static byte[] encrypt(byte[] data, byte[] key) throws Exception {
        // 生成一个可信任的随机数源
        SecureRandom sr = new SecureRandom();
        // 从原始密钥数据创建DESKeySpec对象
        DESKeySpec dks = new DESKeySpec(key);

        // 创建一个密钥工厂,然后用它把DESKeySpec转换成SecretKey对象
        SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("DES");
        SecretKey securekey = keyFactory.generateSecret(dks);

        // Cipher对象实际完成加密操作
        Cipher cipher = Cipher.getInstance("DES");

        // 用密钥初始化Cipher对象
        cipher.init(Cipher.ENCRYPT_MODE, securekey, sr);

        return cipher.doFinal(data);
    }

    /**
     * Description 根据键值进行解密
     *
     * @param data
     * @param key  加密键byte数组
     * @return
     * @throws Exception
     */
    private static byte[] decrypt(byte[] data, byte[] key) throws Exception {
        // 生成一个可信任的随机数源
        SecureRandom sr = new SecureRandom();

        // 从原始密钥数据创建DESKeySpec对象
        DESKeySpec dks = new DESKeySpec(key);

        // 创建一个密钥工厂,然后用它把DESKeySpec转换成SecretKey对象
        SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("DES");
        SecretKey securekey = keyFactory.generateSecret(dks);

        // Cipher对象实际完成解密操作
        Cipher cipher = Cipher.getInstance("DES");

        // 用密钥初始化Cipher对象
        cipher.init(Cipher.DECRYPT_MODE, securekey, sr);

        return cipher.doFinal(data);
    }

当然也可以直接用new String(byte[])的方式将加密和解密后的bytes输出

4)AES加密

 /**
     * Description 根据键值进行加密
     *
     * @param data
     * @param key  加密键
     * @return
     * @throws Exception
     */
    public static byte[] encrypt(String data, String key) throws Exception {
        byte[] bt = encrypt(data.getBytes(), key);
        return bt;
    }


    /**
     * Description 根据键值进行加密
     *
     * @param data
     * @param key  加密键byte数组
     * @return
     * @throws Exception
     */
    private static byte[] encrypt(byte[] data, String key) throws Exception {
        //创建密钥
        SecretKeySpec securekey = getKey(key);
        // Cipher对象实际完成加密操作
        Cipher cipher = Cipher.getInstance("AES");

        // 用密钥初始化Cipher对象
        cipher.init(Cipher.ENCRYPT_MODE, securekey);

        return cipher.doFinal(data);
    }

    /**
     * Description 根据键值进行解密
     *
     * @param data
     * @param key  加密键byte数组
     * @return
     * @throws Exception
     */
    public static byte[] decrypt(byte[] data,String key) throws Exception {

        //创建密钥
        SecretKeySpec keySpec = getKey(key);

        // Cipher对象实际完成解密操作
        Cipher cipher = Cipher.getInstance("AES");

        // 用密钥初始化Cipher对象
        cipher.init(Cipher.DECRYPT_MODE, keySpec);
        byte[] result = cipher.doFinal(data);

        return result;
    }

    private static SecretKeySpec getKey(String key) {
        //必须必须要注意的是,这里的password的长度,必须为128或192或256bits.
        // 也就是16或24或32bytebyte[] password = new byte[16/24/32]; "123456781234567812345678"
        return new SecretKeySpec(key.getBytes(), "AES/CBC/PKCS5PADDING");
    }

重点说下获取这个加密和解密的key的时候遇到的问题。

一开始采用的是下面这种方式:

    private static SecretKeySpec getKey() {
        KeyGenerator keyGenerator = null;
        try {
            keyGenerator = KeyGenerator.getInstance("AES");
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        }
        keyGenerator.init(128, new SecureRandom("12345678".getBytes()));
        SecretKey secretKey = keyGenerator.generateKey();
        byte[] enCodeFormat = secretKey.getEncoded();
        SecretKeySpec key = new SecretKeySpec(enCodeFormat, "AES");
        return key;
    }

在Android环境下运行,报错

javax.crypto.BadPaddingException: pad block corrupted

通过查原因是由于加密和解密的两个key不一致引起的,通过加日志,的确在加密和解密的时候,产生的SecretKeySpec是不一样的。

所以采用了示例中生成key的方式,但是该方式还是要注意一个问题,就是在设置密码的时候,,必须为128或192或256bits,也就是16或24或32bytebyte[] password = new byte[16/24/32]; 否则会抛出下面的错误

猜你喜欢

转载自blog.csdn.net/nihaomabmt/article/details/82183003