05 密码学

1,243 阅读10分钟

为什么要去看密码学

  • 客户端发送给服务器的数据包中,有些参数不知道来源,可能是随机生成、标准算法加密的、自写算法加密的
  • 安卓中,标准算法加密通常会出现在Java、so(C/C++)、JS中
  • Java有现成的系统API调用,开发者想要使用这些API,必须使用固定的方法名去访问。这些API也就是我们需要学习的内容。
  • C/C++没有现成的系统API调用,开发者要么自己去实现算法,要么调用别人写好的模块,算法的运行不依赖系统API,因此方法名可以混淆。你要做的就是根据各种标准算法的特征,去识别是否标准算法。这些算法的特征,就是我们需要学习的内容。
  • JS也没有现成的系统API调用,开发者一般使用CryptoJS、jsencrypt等第三方加密库来加密。

密码学里面要看哪些内容

  • 消息摘要算法(散列函数、哈希函数):MD5、SHA、MAC

  • 对称加密算法:DES、3DES、AES

  • 非对称加密算法:RSA

  • 数字签名算法:MD5withRSA、SHA1withRSA、SHA256withRSA

加密方式

1 Hex编码

什么是hex编码

hex编码是一种用16个字符表示任意二进制数据的方法

Hex编码的特点

a) 用0-9 a-f 16个字符表示。
b) 每个十六进制字符代表4bit, 也就是2个十六进制字符代表一个字节。
c) 在实际应用中,比如密钥初始化,一定要分清楚传进去的密钥是哪种编码的,采用对应的方式解析,才能得到正确的结果
d) 编程中很多问题,需要从字节甚至二进制位的角度去考虑,才能明白

Hex编码Java实现

import com.sun.org.apache.xerces.internal.impl.dv.util.HexBin;

import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;

public class HexEncode {
    public static void main(String[] args) {
        MessageDigest md5 = null;
        try {
//            md5 = MessageDigest.getInstance("MD5");
//            md5.update("a12345678".getBytes());
//            md5.update("a12345678".getBytes(), 1, 3);
//            byte[] result1 = md5.digest();
//            System.out.println(HexBin.encode(result1));
            String data = "Andy";
            String salt = "a12345678";
            byte[] result2 = MessageDigest.getInstance("SHA-256").digest((data + salt).getBytes());
            String hexStr = HexBin.encode(result2);
            System.out.println(hexStr);
            //System.out.println(Base64.getEncoder().encodeToString(hexStr.getBytes()));
            //System.out.println(Base64.getEncoder().encodeToString(result2));

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


    }
}

2 Base64编码

1. 什么是Base64

Base64是一种用64个字符表示任意二进制数据的方法
是一种编码,而非加密
A-Z a-z 0-9 + / =

2. Base64的应用

RSA密钥、加密后的密文、图片等数据中,会有一些不可见字符,
直接转成文本传输的话,会有乱码、数据错误、数据丢失等情况出现,就可以使用Base64编码

3. Base64的代码实现和码表

//java.util.Base64 码表 Android8.0以上可用
Base64.getEncoder().encodeToString("Andy".getBytes())
//android.util.Base64 码表
Base64.encodeToString

//okio.ByteString
build.gradle
dependencies {
    api 'com.squareup.okhttp3:okhttp:3.10.0'
}
ByteString byteString = ByteString.of("100".getBytes());
byteString.base64();    //码表 okio.Base64 encode

4. Base64码表的妙用

为了传输数据安全,通常会对Base64数据进行URL编码,或者会把+和/替换成-和_ 

5. Base64编码细节

每个Base64字符代表原数据中的6bit
Base64编码后的字符数,是4的倍数
编码的字节数是3的倍数时,不需要填充

6. Base64编码的特点

a) Base64编码是编码,不是压缩,编码后只会增加字节数
b) 算法可逆, 解码很方便, 不用于私密信息通信
c) 标准的Base64每行为76个字符,行末添加换行符
d) 加密后的字符串只有65种字符, 不可打印字符也可传输
e) 在Java层可以通过hook对应方法名来快速定位关键代码
f) 在so层可以通过输入输出的数据和码表来确定算法

image.png

image.png

消息摘要算法

1. 算法特点

a) 消息摘要算法/单向散列函数/哈希函数
b) 不同长度的输入,产生固定长度的输出
c) 散列后的密文不可逆
d) 散列后的结果唯一
e) 哈希碰撞
f) 一般用于校验数据完整性、签名sign
由于密文不可逆,所以服务端也无法解密
想要验证,就需要跟前端一样的方式去重新签名一遍
签名算法一般会把源数据和签名后的值一起提交到服务端
要保证在签名时候的数据和提交上去的源数据一致

2. 常见算法

MD5、SHA1、SHA256、SHA512、HmacMD5、HmacSHA1、HmacSHA256、HmacSHA512
RIPEMD160、HmacRIPEMD160、PBKDF2、EvpKDF

image.png

MD5

image.png

1. MD5的Java实现

MessageDigest md5 = MessageDigest.getInstance("MD5");
md5.update("Andy".getBytes());
md5.digest();

2 算法特点

1. 加密后的字节数组可以编码成Hex、Base64
2. 没有任何输入,也能计算hash值
3. 碰到加salt的MD5,可以直接输入空的值,得到结果去CMD5查询一下,有可能就得到salt

SHA

image.png

1. SHA的Java实现

MessageDigest sha1 = MessageDigest.getInstance("SHA-1");
sha1.update("Andy".getBytes());
sha1.digest();

2 SHA算法的特点

1 加密后的字节数组可以编码成Hex、Base64
2 没有任何输入,也能计算hash

MAC

image.png

1. MAC算法与MD和SHA的区别是多了一个密钥,密钥可以随机给
2. MAC的Java实现
SecretKeySpec secretKeySpec = new SecretKeySpec("a12345678".getBytes(),"HmacSHA1");
Mac mac = Mac.getInstance(secretKeySpec.getAlgorithm());
mac.init(secretKeySpec);
mac.update("Andy".getBytes());
mac.doFinal();
3. 加密后的字节数组可以编码成Hex、Base64
4. 没有任何输入,也能计算hash值

MD5,HEX,MAC通杀算法

note.youdao.com/s/CJT8frzb

对称加密算法

1. 加密/解密的过程可逆的算法,叫做加密算法
2. 加密/解密使用相同的密钥,叫做对称加密算法
3. 对称加密算法的密钥可以随机给,但是有位数要求
4. 对称加密算法的输入数据没有长度要求,加密速度快
5. 各算法的密钥长度
RC4 密钥长度1-256字节
DES 密钥长度8字节
3DES/DESede/TripleDES 密钥长度24字节
AES 密钥长度16、24、32字节
根据密钥长度不同AES又分为AES-128、AES-192、AES-256
6. 对称加密分类
a) 序列加密/流加密: 以字节流的方式,依次加密(解密)明文(密文)中的每一个字节
   RC4 
b) 分组加密: 将明文消息分组(每组有多个字节),逐组进行加密
   DES、3DES、AES

DES

image.png

DES算法实现

import com.sun.org.apache.xerces.internal.impl.dv.util.HexBin;

import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.Base64;

public class DESEncode {
    public static void main(String[] args) throws NoSuchPaddingException, NoSuchAlgorithmException, IllegalBlockSizeException, BadPaddingException, InvalidAlgorithmParameterException, InvalidKeyException {
            // DES加密
            SecretKeySpec desKey = new SecretKeySpec("12345678".getBytes(), "DES");
            IvParameterSpec ivParameterSpec = new IvParameterSpec("12345678".getBytes());  // 长度按照对应算法的分组长度
            Cipher des = Cipher.getInstance("DES/CBC/PKCS5Padding");
            des.init(Cipher.ENCRYPT_MODE, desKey, ivParameterSpec);
            //des.update("1234xiaojianbang1234".getBytes());
            byte[] result3 = des.doFinal("xiaojianxiaojian".getBytes());
            String hexStr = HexBin.encode(result3);
            // 加密结果为hex编码
            System.out.println(hexStr);
            // 加密结果为base64编码
            System.out.println(Base64.getEncoder().encodeToString(result3));
            
            // 将加密结果解码为字符串
            des.init(Cipher.DECRYPT_MODE, desKey, ivParameterSpec);
            byte[] result4 = des.doFinal(result3);
            System.out.println(new String(result4));
    }
}

DES算法特点

1. 对称加密算法里,使用NOPadding,加密的明文必须等于分组长度倍数,否则报错
2. 没有指明加密模式和填充方式,表示使用默认的DES/ECB/PKCS5Padding
3. 加密后的字节数组可以编码成Hex、Base64
4. 要复现一个对称加密算法,需要得到明文、key、iv、mode、padding
5. 明文、key、iv需要注意解析方式,而且不一定是字符串形式
6. 如果加密模式是ECB,则不需要加iv,加了的话会报错

7. ECB模式和CBC模式的区别
8. 如果使用PKCS5Padding,会对加密的明文填充1字节-1个分组的长度
9. DES算法明文按64位进行分组加密
10. 如果明文中有两个分组的内容相同,ECB会得到完全一样的密文,CBC不会
11. 加密算法的结果通常与明文等长或者更长,如果变短了,那可能是gzip、protobuf、消息摘要算法

DESede

image.png

DESede算法实现

DESede加解密的Java实现
//DESedeKeySpec desedeKey = new DESedeKeySpec("123456781234567812345678".getBytes());
//SecretKeyFactory key = SecretKeyFactory.getInstance("DESede");
//SecretKey secretKey = key.generateSecret(desKeySpec);
SecretKeySpec secretKeySpec = new SecretKeySpec("123456781234567812345678".getBytes(), "DESede");
IvParameterSpec ivParameterSpec = new IvParameterSpec("12345678".getBytes());
Cipher desede = Cipher.getInstance("DESede/CBC/PKCS5Padding");
desede.init(Cipher.ENCRYPT_MODE, desedeKey, ivParameterSpec);
desede.doFinal("xiaojianbang".getBytes());

DESede算法特点

1. DESede实际上是先进行DES加密,再进行DES解密,再进行DES加密

2. DESede的密钥24个字节,第1个8字节密钥用于DES加密,之后类推

3. 如果DESede的3个8字节密钥相同,则加密结果与DES一致

4. 为了保证DESede的安全性,一般前2个或者3个8字节密钥都不一致

5. DESede的其他特征与DES一致

AES

image.png

1. 根据密钥长度不同,分为AES128、AES192、AES256

2. AES加解密的Java实现
SecretKeySpec key = new SecretKeySpec("1234567890abcdef".getBytes(),"AES");
AlgorithmParameterSpec iv = new IvParameterSpec("1234567890abcdef".getBytes());
Cipher aes = Cipher.getInstance("AES/CBC/PKCS5Padding");
aes.init(Cipher.ENCRYPT_MODE, key, iv);
aes.doFinal("xiaojianbang1234xiaojianbang1234".getBytes());

3. AES算法明文按128位进行分组加密,其余特征与DES一致
4. DES、3DES、AES算法通杀hook
https://note.youdao.com/s/7EcfTfzx

非对称加密算法

典型算法:RSA

1. 需要生成一个密钥对,包含公钥和私钥,密钥不是随便写的
密钥对生成 http://web.chacuo.net/netrsakeypair

2. 公钥加密的数据,私钥才能解密
   私钥加密的数据,公钥才能解密

3. 一般公钥是公开的,私钥保密,私钥包含公钥,从公钥无法推导出私钥

4. 加密处理安全,但是性能极差,单次加密长度有限制

5. RSA算法既可用于加密解密,也可用于数据签名

RSA_Base64

image.png

1. 私钥的格式

pkcs1格式通常开头是 -----BEGIN RSA PRIVATE KEY-----
pkcs8格式通常开头是 -----BEGIN PRIVATE KEY-----
Java中的私钥必须是pkcs8格式

2. RSA密钥的解析

byte[] keyBytes = Base64Decoder.decodeBuffer(key);
X509EncodedKeySpec keySpec = new X509EncodedKeySpec(keyBytes);
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
PublicKey publicKey = keyFactory.generatePublic(keySpec);

byte[] keyBytes = Base64Decoder.decodeBuffer(key);
PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(keyBytes);
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
PrivateKey privateKey = keyFactory.generatePrivate(keySpec);

3. RSA加解密

Cipher cipher = Cipher.getInstance("RSA/None/NOPadding");
cipher.init(Cipher.ENCRYPT_MODE, publicKey);
byte[] bt_encrypted = cipher.doFinal(bt_plaintext);

Cipher cipher = Cipher.getInstance("RSA/None/NOPadding");
cipher.init(Cipher.DECRYPT_MODE, privateKey);
byte[] bt_original = cipher.doFinal(bt_encrypted);

4. RSA模式和填充细节

a) None模式与ECB模式是一致的
b) NOPadding
   明文最多字节数为密钥字节数
   填充字节0, 加密后的密文不变
   密文与密钥等长
c) PKCS1Padding
   明文最大字节数为密钥字节数-11
   每一次的填充不一样,使得加密后的密文会变
   密文与密钥等长
  • 5. 把PKCS1Padding加密后的密文,用NOPadding去解密,会怎么样呢?
    1. 没有指明加密模式和填充方式,表示使用默认的RSA/ECB/NOPadding
    1. 加密后的字节数组可以编码成Hex、Base64

RSA_hex

1. RSA密钥的转换

www.cnblogs.com/wyzhou/p/97…

openssl rsa -pubin -in public.pem -text //以文本格式输出公钥内容
openssl rsa -in private.pem -text       //以文本格式输出私钥内容
a) 从PEM格式密钥中提取modulus、publicExponent、privateExponent
b) modulus、publicExponent、privateExponent转PEM格式
c) 还有极少数的modulus、publicExponent、privateExponent用十进制表示

2. RSA密钥的解析

BigInteger N = new BigInteger(stringN, 16); //16改10就可以把十进制转成大数
BigInteger E = new BigInteger(stringE, 16); 
RSAPublicKeySpec spec = new RSAPublicKeySpec(N, E);
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
PublicKey publicKey = keyFactory.generatePublic(spec);

3. RSA_Hex加解密的方式与RSA_Base64一致

4. 多种加密算法的常见结合套路

随机生成AES密钥AESKey
AESKey密钥用于AES加密数据,得到数据密文cipherText
使用RSA对AESKey加密,得到密钥密文cipherKey
提交密钥密文cipherKey和数据密文cipherText给服务器

5 关于AES和RSA加密结合

  • 先获取AES随机的秘钥,通过该秘钥将数据进行加密

  • 将AES的秘钥进行rsa加密

  • 客户端收到AES加密后的数据,以及RSA加密后的key,先将key通过rsa解密,之后拿着加密之后的key将数据体进行解密

  1. RSA算法通杀hook note.youdao.com/s/1ZcMKaQk

数字签名算法

image.png

1. 签名
PrivateKey priK = getPrivateKey(str_priK);
Signature sig = Signature.getInstance("SHA256withRSA");
sig.initSign(priK);
sig.update(data);
sig.sign();

2. 验证
PublicKey pubK = getPublicKey(str_pubK);
Signature sig = Signature.getInstance("SHA256withRSA");
sig.initVerify(pubK);
sig.update(data);
sig.verify(sign);

3. 数字签名算法通杀hook