Android 安全

MD5

加密与摘要是不一样的,加密的数据是完整的,通过解密可以获取完整的数据。

摘要得到的消息是不完整的,通过摘要的数据不能获取到原数据。

MD5长度默认是128bit,这样表达不好,所以将二级制转换成16进制,4bit代表一个16进制,所有128/4=32 ,所以为32位16进制。 MD5 16位与32位区别是将32位后面的16位去掉,得到的16位

MD5作用

  • 一致性检验
  • 数字签名
  • 安全访问,就是对数据加密存到数据库或服务器中,只有对应的密钥才能访问

MD5是不可逆的,没有对应的算法,无法从生产MD5值逆向获取到原数据,除非采用暴力破解,彩虹表法。

MD5不可逆原因

由于它是一种散列数,也叫哈希数,它是一种单向密码体制,即明文到密文不可逆映射,即只有加密过程,没有解密过程。哈希函数可以将任意长度的输入变化成固定长度的输入,针对不同的输入得到不同的输出,如果两个不同的消息得到相同的哈希值,就称为碰撞,它具有抗碰撞性,需要大量的时间才能够找到不同的输入得到相同的输出结果。也是不可逆的。使用hash计算原文存在丢失,一个MD5可以对应多个原文,即有限的MD5与无限的原文,一个MD5有128bit二进制,有2^128中可能。

彩虹表

可以根据彩虹表破解,相当于已知到一堆字符串的md5值是什么,然后将这些存储起来反向查询。但是多次md5,也是不安全的,因为我们在彩虹表会存一堆字符串2次md5值,3次md5值等。 这里就有人会问到什么是彩虹表,摘抄维基百科: 它是一个用于加密散列函数逆运算预先计算好的表。常用于破解加密后的密码散列。查找表包含有限字符固定长度的纯文本密码,是一种空间换时间实践,在暴力破解中,使用更多的存储空间与较少的计算能力,但比每一次输入散列查找表使用更少的储存空间与更多的计算能力

可以通过以下方法增加破解难度:

  • 由于sha1,sha256,sha512也是类似于md5,所以可以通过,md5后再sha1等增加“一点”安全性,减少彩虹表破解可能性
  • 真正公认的方法是md5或sha1加“盐”,就是要进行md5的字符串,加上个随机字符串,再进行md5加密,这个随机字符串存储在该用户的字段中

这里就涉及一个新的名词,盐。那什么是盐呢。

  • 在密码学中,是指在散列之前,将散列内容任意固定位置插入特定的字符串,这种插入字符串的方式称为加盐,在大部分情况,盐不需要保密,盐可以是随机字符串,也可以是随机位置,这样安全性就大大提高。 加盐好处:
  • 通常情况,当字段通过MD5加密,散列后的值是无法通过算法获取原始值,但是在一个大型的彩虹表中,通过在表中搜多该MD5值,有可能短时间获取散列值。但是加盐后的散列值,即使通过彩虹表获取散列后的数值对应的原始内容,但是加盐后插入的字符串扰乱了真正的密码,是的获取真正密码的概率大大降低。

MD5算法特点

  • 压缩性,任意长度的数据,算出MD5的值都是固定的
  • 容易计算,从原始数据计算出MD5值很容易
  • 抗修改性,对原数据进行任何改动,哪怕只修改1个字节,最后得到的MD5值区别都很大
  • 强抗碰撞:想找到两个不同数据,使它们MD5值相同非常困难

MD5用途

  • 文件校验,对文件进行MD5校验,就能得到文件在传输过程中有没有被篡改
  • 密码加密

MD5加密方法

  1. 初始化MessageDigest对象
  2. 传入需要计算的字符串,先使用getBytes方法生成字符串数据,在调用update方法进行更新摘要
  3. 计算摘要值,调用MessageDisgest对象的disgest方法完成计算,计算结果通过字节类型返回
  4. 处理计算结果

普遍使用加密方式有:位运算,格式化字符串,使用算法将加密后的数据转换成16进制

代码:

public class MD5Util {  
    public final static String getMD5String(String s) {  
        char hexDigits[] = { '0', '1', '2', '3', '4',  
                '5', '6', '7', '8', '9',  
                'A', 'B', 'C', 'D', 'E', 'F' };  
        try {  
            byte[] btInput = s.getBytes();  
            //获得MD5摘要算法的 MessageDigest 对象  
            MessageDigest mdInst = MessageDigest.getInstance("MD5");  
            //使用指定的字节更新摘要  
            mdInst.update(btInput);  
            //获得密文  
            byte[] md = mdInst.digest();  
            //把密文转换成十六进制的字符串形式  
            int j = md.length;  
            char str[] = new char[j * 2];  
            int k = 0;  
            for (int i = 0; i < j; i++) {  
                byte byte0 = md[i];  
                str[k++] = hexDigits[byte0 >>> 4 & 0xf];  
                str[k++] = hexDigits[byte0 & 0xf];  
            }  
            return new String(str);  
        }  
        catch (Exception e) {  
            e.printStackTrace();  
            return null;  
        }  
    }  
}

MD5与Base64结合使用

base64是将任意序列的8位字节描述一种不易为人识别的形式,该算法是用64个字符来表示任意二进制数据的方法。通常用于邮件,http加密,登录用户名密码加密,可以进行加密与解密,建议它只是一种编码格式,并不是一种加密算法,不要用来加密数据

MD5加密后还要使用Base64编码原因:

  • 使用Base64算法编码后得到32位字符串长度值,有利于在数据库中进行存储

后起之秀

MD5SHA-1是最常用的摘要算法,一个生成16字节一个生成20位字节长度,但是安全强度比较低,都被TLS(传输层安全,一种安全通信协议)禁用。

目前推荐的是SHA-2。SHA-2 实际上是一系列摘要算法的统称,总共有 6 种,常用的有 SHA224、SHA256、SHA384,分别能够生成 28 字节、32 字节、48 字节的摘要。 摘要算法保证了“数字摘要”和原文是完全等价的。所以,我们只要在原文后附上它的摘要,就能够保证数据的完整性。

对称式加密

DES与AES

  • DES默认是56位加密密钥,已经不安全
  • AES加密模式不要使用ECB模式,它不安全,所以推荐使用CBCCFB模式,并且使用PKCS5Padding进行填充。 使用CBC模式,需要一个IV参量,就是之前随机生成的指定长度字符串,来增强加密。

最早有 ECB、CBC、CFB、OFB 等几种分组模式,但都陆续被发现有安全漏洞,所以现在基本都不怎么用了。最新的分组模式被称为 AEAD(Authenticated Encryption with Associated Data),在加密的同时增加了认证的功能,常用的是 GCM、CCMPoly1305。 把上面这些组合起来,就可以得到 TLS 密码套件中定义的对称加密算法。比如:

AES128-GCM,意思是密钥长度为 128 位的 AES 算法,使用的分组模式是 GCM;ChaCha20-Poly1305 的意思是 ChaCha20 算法,使用的分组模式是 Poly1305

PKCS5Padding中,明确定义Block的大小是8位,而在PKCS7Padding定义中,对于块的大小是不确定的,可以在1-255之间,填充值的算法都是一样的value=k - (l mod k) ,K=块大小,l=数据长度,如果l=8, 则需要填充额外的8个byte的8 而使用NoPadding模式,要求输入的长度必须为16字节的倍数,又设置了CBC模式,还需附带一个IV参量,增加加密算法强度,而CFB不需要IV参量。其他模式加密数据长度为16*(n+1)的倍数。

代码:

    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 强随机种子算法, 要区别4.2以上版本的调用方法
    /*
     * 生成随机数,可以当做动态的密钥 加密和解密的密钥必须一致,不然将不能解密
     */
    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 = null;
        // 在4.2以上版本中,SecureRandom获取方式发生了改变
        if (android.os.Build.VERSION.SDK_INT >= 17) {
            sr = SecureRandom.getInstance(SHA1PRNG, "Crypto");
        } else {
            sr = SecureRandom.getInstance(SHA1PRNG);
        }
        // for Java
        // secureRandom = 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;
    }/*
     * 加密
     */
    public static String encrypt(String key, String cleartext) {
        if (TextUtils.isEmpty(cleartext)) {
            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);
        cipher.init(Cipher.ENCRYPT_MODE, skeySpec, new IvParameterSpec(new byte[cipher.getBlockSize()]));
        byte[] encrypted = cipher.doFinal(clear);
        return encrypted;
    }
    /*
     * 解密
     */
    public static String decrypt(String key, String encrypted) {
        if (TextUtils.isEmpty(encrypted)) {
            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);
        cipher.init(Cipher.DECRYPT_MODE, skeySpec, new IvParameterSpec(new byte[cipher.getBlockSize()]));
        byte[] decrypted = cipher.doFinal(encrypted);
        return decrypted;
    }

RSA

非对称式加密。用私钥加密必须通过公钥解密,用公钥加密必须通过私钥解密 密钥不要低于512位,512位与1024位都已经被成功破解,所以建议使用2048位密钥长度,进行数字签名

android系统的RSA实现是"RSA/None/NoPadding",而标准JDK实现是"RSA/None/PKCS1Padding" ,这样会造成了在android机上加密后无法在服务器上解密的原因,所以得统一成JDK标准实现

RSA非对称加密内容长度有限制,1024位key的最多只能加密127位数据,否则就会报错(javax.crypto.IllegalBlockSizeException: Data must not be longer than 117 bytes) , RSA 是常用的非对称加密算法。研究发现是由于待加密的数据超长所致。 RSA 算法规定:待加密的字节数不能超过密钥的长度值除以 8 再减去 11(即:KeySize / 8 - 11

私钥的加解密都很耗时,所以可以根据不同的需求采用不能方案来进行加解密。个人觉得服务器要求解密效率高,客户端私钥加密,服务器公钥解密比较好

RSA算法是最流行的公钥密码算法,使用长度可以变化的密钥。RSA是第一个既能用于数据加密也能用于数字签名的算法。

RSA算法原理如下:

  1. 随机选择两个大质数p和q,p不等于q,计算N=pq
  2. 选择一个大于1小于N的自然数e,e必须与(p-1)(q-1)互素
  3. 用公式计算出d:d×e = 1 (mod (p-1)(q-1))
  4. 销毁p和q

最终得到的N和e就是“公钥”,d就是“私钥”,发送方使用N去加密数据,接收方只有使用d才能解开数据内容。

RSA的安全性依赖于大数分解,小于1024位的N已经被证明是不安全的,而且由于RSA算法进行的都是大数计算,使得RSA最快的情况也比DES慢上倍,这是RSA最大的缺陷,因此通常只能用于加密少量数据或者加密密钥,但RSA仍然不失为一种高强度的算法。

后起之秀

ECC(Elliptic Curve Cryptography)是非对称加密里的“后起之秀”,它基于“椭圆曲线离散对数”的数学难题,使用特定的曲线方程和基点生成公钥和私钥,子算法 ECDHE 用于密钥交换,ECDSA 用于数字签名。 目前比较常用的两个曲线是 P-256secp256r1,在 OpenSSL 称为 prime256v1)和 x25519P-256 是 NIST(美国国家标准技术研究所)和 NSA(美国国家安全局)推荐使用的曲线,而 x25519 被认为是最安全、最快速的曲线。 比起 RSAECC 在安全强度和性能上都有明显的优势。160 位的 ECC 相当于 1024 位的 RSA,而 224 位的 ECC 则相当于 2048 位的 RSA。因为密钥短,所以相应的计算量、消耗的内存和带宽也就少,加密解密的性能就上去了。

SQL 注入

SQL注入攻击指的是未将数据与代码进行严格的隔离,导致用户在读取数据时,错误将代码当做数据执行,导致一些安全问题,典型的例子是当对SQL语句进行拼接操作时,直接将未加转义的用户输入内容作为变量。

预防注入:

  • 过滤用户输入的参数中的特殊字符,降低注入风险
  • 禁止通过字符串拼接SQL语句,严格使用参数绑定传入的SQL参数
  • 合理使用数据库访问框架提供的防注入机制

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • Gradle之Project Api 使用

    在根工程下自定义config.gradle可以直接在根project引用apply from:'config.gradle' 如果需要在app project...

    Yif
  • Gradle 之 Task 使用

    在根工程下自定义config.gradle可以直接在根project引用apply from:'config.gradle' 如果需要在app project...

    Yif
  • Groovy高级用法

    在根工程下自定义config.gradle可以直接在根project引用apply from:'config.gradle' 如果需要在app project...

    Yif
  • C++ OpenCV图像分割之GrabCut分割

    在OpenCV中的图像分割中GrabCut分割算法,该算法可以方便的分割出前景图像,操作简单,而且分割的效果很好。在前我们刚用学了OpenCV中的鼠标回调函数,...

    Vaccae
  • Groovy 基本类型与闭包

    在根工程下自定义config.gradle可以直接在根project引用apply from:'config.gradle' 如果需要在app project...

    Yif
  • Gradle之Project Api 使用

    在根工程下自定义config.gradle可以直接在根project引用apply from:'config.gradle' 如果需要在app project...

    Yif
  • Gradle 之 Task 使用

    在根工程下自定义config.gradle可以直接在根project引用apply from:'config.gradle' 如果需要在app project...

    Yif
  • 现代IM系统中聊天消息的同步和存储方案探讨

    IM全称是『Instant Messaging』,中文名是即时通讯。在这个高度信息化的移动互联网时代,生活中IM类产品已经成为必备品,比较有名的如钉钉、微信、Q...

    JackJiang
  • Groovy高级用法

    在根工程下自定义config.gradle可以直接在根project引用apply from:'config.gradle' 如果需要在app project...

    Yif
  • BZOJ 2456: mode(新生必做的水题)

    2456: mode Time Limit: 1 Sec  Memory Limit: 1 MB Submit: 4868  Solved: 2039 Des...

    Angel_Kitty

扫码关注云+社区

领取腾讯云代金券