04 密码学
theme: channing-cyan highlight: a11y-light
为什么要去看密码学
- 客户端发送给服务器的数据包中,有些参数不知道来源,可能是随机生成、标准算法加密的、自写算法加密的
- 安卓中,标准算法加密通常会出现在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编码
txt
hex编码是一种用16个字符表示任意二进制数据的方法
Hex编码的特点
a) 用0-9 a-f 16个字符表示。
b) 每个十六进制字符代表4bit, 也就是2个十六进制字符代表一个字节。
c) 在实际应用中,比如密钥初始化,一定要分清楚传进去的密钥是哪种编码的,采用对应的方式解析,才能得到正确的结果
d) 编程中很多问题,需要从字节甚至二进制位的角度去考虑,才能明白
Hex编码Java实现
``` 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层可以通过输入输出的数据和码表来确定算法
消息摘要算法
1. 算法特点
a) 消息摘要算法/单向散列函数/哈希函数
b) 不同长度的输入,产生固定长度的输出
c) 散列后的密文不可逆
d) 散列后的结果唯一
e) 哈希碰撞
f) 一般用于校验数据完整性、签名sign
由于密文不可逆,所以服务端也无法解密
想要验证,就需要跟前端一样的方式去重新签名一遍
签名算法一般会把源数据和签名后的值一起提交到服务端
要保证在签名时候的数据和提交上去的源数据一致
2. 常见算法
MD5、SHA1、SHA256、SHA512、HmacMD5、HmacSHA1、HmacSHA256、HmacSHA512
RIPEMD160、HmacRIPEMD160、PBKDF2、EvpKDF
MD5
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
1. SHA的Java实现
MessageDigest sha1 = MessageDigest.getInstance("SHA-1");
sha1.update("Andy".getBytes());
sha1.digest();
2 SHA算法的特点
1 加密后的字节数组可以编码成Hex、Base64
2 没有任何输入,也能计算hash值
MAC
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通杀算法
http://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
DES算法实现
```java 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
DESede算法实现
java
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
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
http://note.youdao.com/s/7EcfTfzx
非对称加密算法
典型算法:RSA
1. 需要生成一个密钥对,包含公钥和私钥,密钥不是随便写的
密钥对生成 http://web.chacuo.net/netrsakeypair
2. 公钥加密的数据,私钥才能解密
私钥加密的数据,公钥才能解密
3. 一般公钥是公开的,私钥保密,私钥包含公钥,从公钥无法推导出私钥
4. 加密处理安全,但是性能极差,单次加密长度有限制
5. RSA算法既可用于加密解密,也可用于数据签名
RSA_Base64
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
每一次的填充不一样,使得加密后的密文会变
密文与密钥等长
-
- 把PKCS1Padding加密后的密文,用NOPadding去解密,会怎么样呢?
-
- 没有指明加密模式和填充方式,表示使用默认的RSA/ECB/NOPadding
-
- 加密后的字节数组可以编码成Hex、Base64
RSA_hex
1. RSA密钥的转换
http://www.cnblogs.com/wyzhou/p/9738964.html
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将数据体进行解密
-
RSA算法通杀hook http://note.youdao.com/s/1ZcMKaQk
数字签名算法
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