Android逆向中常见密码学算法入门

1、为什么要学密码学

1、客户端发送给服务器的数据包中,有些参数不知道来源,可能是随机生成、标准算法加密的、自写算法加密的

2、安卓中,标准算法加密通常会出现在Java、so(C/C++)、JS中

3、 Java有现成的系统API调用,开发者想要使用这些API,必须使用固定的方法名去访问。这些API也就是我们需要学习的内容。

4、C/C++没有现成的系统API调用,开发者要么自己去实现算法,要么调用别人写好的模块,算法的运行不依赖系统API,因此方法名可以混淆。要做的就是根据各种标准算法的特征,去识别是否标准算法。这些算法的特征,就是我们需要学习的内容。

5、JS也没有现成的系统API调用,开发者一般使用CryptoJS、jsencrypt等第三方加密库来加密

2、加密算法

消息摘要算法(散列函数、哈希函数)

MD5、SHA、MAC

对称加密算法

DES、3DES、AES、Blowfish、Rc4

非对称加密算法

RSA

数字签名算法

MD5withRSA、SHA1withRSA、SHA256withRSA

3、H5的app逆向

3.1、H5的核心代码在JS文件中

解决方法:远程调试、修改JS代码注入代码

3.2、WebView远程调试的要求

2.1 、手机端的WebView版本要比电脑端的chrome版本低

2.2 、手机端的WebView需要开启可调试

2.3 、需要有一个科学上网的vpn,因为点击inspect时要去下载一些东西,不然打开是白屏

2.4 、通常app中的WebView是不开启可调试的,需要通过Hook来开启

3.3、H5的app分类

3.1、 纯JS发包,这时可以在远程调试工具上抓到包,也有相应JS代码

3.2 、部分JS发包,部分Java发包,这时有些包可以在调试工具上抓到,有些不行,需要做额外的分析

比如:JS和Java如何相互调用,从这个角度入手,找Java里面的接口

加密可能部分在JS文件中,部分在Java中,说白了有些代码可以在调试工具中看到,有些代码是Java只能逆向app去找

3.3 、纯Java发包,典型是uni-app。但奇怪的是uni-app核心代码都在JS中

3.4、调试h5的app

首先将手机连上电脑,然后在Chrome浏览器输入chrome://inspect

我们以手机上自带的google为例,google的WebView默认开启可调试

点击inspect就可以开始调试了,我们以一个app实战进行学习。

首先我们需要开启app的WebView可调试权限

Java.perform(function(){
    var WebView = Java.use('android.webkit.WebView');
    WebView.$init.overload('android.content.Context').implementation = function(a){
        console.log("WebView.$init is called!");
        var retval = this.$init(a);
        this.setWebContentsDebuggingEnabled(true);
        return retval;
    }
    WebView.$init.overload('android.content.Context', 'android.util.AttributeSet').implementation = function(a, b){
        console.log("WebView.$init is called!");
        var retval = this.$init(a, b);
        this.setWebContentsDebuggingEnabled(true);
        return retval;
    }
    WebView.$init.overload('android.content.Context', 'android.util.AttributeSet', 'int').implementation = function(a, b, c){
        console.log("WebView.$init is called!");
        var retval = this.$init(a, b, c);
        this.setWebContentsDebuggingEnabled(true);
        return retval;
    }
    WebView.$init.overload('android.content.Context', 'android.util.AttributeSet', 'int', 'boolean').implementation = function(a, b, c, d){
        console.log("WebView.$init is called!");
        var retval = this.$init(a, b, c, d);
        this.setWebContentsDebuggingEnabled(true);
        return retval;
    }
    WebView.$init.overload('android.content.Context', 'android.util.AttributeSet', 'int', 'int').implementation = function(a, b, c, d){
        console.log("WebView.$init is called!");
        var retval = this.$init(a, b, c, d);
        this.setWebContentsDebuggingEnabled(true);
        return retval;
    }
    WebView.$init.overload('android.content.Context', 'android.util.AttributeSet', 'int', 'java.util.Map', 'boolean').implementation = function(a, b, c, d, e){
        console.log("WebView.$init is called!");
        var retval = this.$init(a, b, c, d, e);
        this.setWebContentsDebuggingEnabled(true);
        return retval;
    }
    WebView.$init.overload('android.content.Context', 'android.util.AttributeSet', 'int', 'int', 'java.util.Map', 'boolean').implementation = function(a, b, c, d, e, f){
        console.log("WebView.$init is called!");
        var retval = this.$init(a, b, c, d, e, f);
        this.setWebContentsDebuggingEnabled(true);
        return retval;
    }
    
    WebView.setWebContentsDebuggingEnabled.implementation = function(){
        this.setWebContentsDebuggingEnabled(true);
        console.log("setWebContentsDebuggingEnabled is called!");
    }

});

通过frida去hook webview的初始化函数,得到webview对象,然后通过setWebContentsDebuggingEnabled函数并设置为true打开可调试权限

点击inspect开启调试,点击一下登录,监控网络请求

这里还有对应js代码的位置

可以下断点进行调试

4、Hex编码

4.1、什么是Hex编码

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

是一种编码,而非加密

字符编码 ASCII、UTF-8、GBK、USC2编码、URL编码,其实与Hex编码差不多

4.2、Hex编码的代码实现和码表

static final char[] HEX_DIGITS =
      { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' };  
public String hex() {
    char[] result = new char[data.length * 2];
    int c = 0;
    for (byte b : data) {
      result[c++] = HEX_DIGITS[(b >> 4) & 0xf];
      result[c++] = HEX_DIGITS[b & 0xf];
    }
    return new String(result);
  }

要在build.gradle文件中添加依赖

dependencies {
  api 'com.squareup.okhttp3:okhttp:3.10.0'
}

使用方法

ByteString byteString = ByteString.of("whitebird".getBytes());
byteString.hex();

4.3、Hex编码特点

a) 用0-9 a-f 16个字符表示。

b) 每个十六进制字符代表4bit, 也就是2个十六进制字符代表一个字节。

c) 在实际应用中,比如密钥初始化,一定要分清楚传进去的密钥是哪种编码的,采用对应方式解析,才能得到正确的结果

d) 编程中很多问题,需要从字节甚至二进制位的角度去考虑,才能明白

5、base64编码

5.1、什么是Base64

Base64是一种用64个字符表示任意二进制数据的方法,是一种编码,而非加密

码表:A-Z a-z 0-9 + / =

5.2、Base64的应用

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

5.3、Base64的代码实现和码表

//java.util.Base64 码表 Android8.0以上可用
Base64.getEncoder().encodeToString("whitebird".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

5.4、Base64码表的妙用

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

5.5、Base64编码细节

每个Base64字符代表原数据中的6bit

Base64编码后的字符数,是4的倍数

编码的字节数是3的倍数时,不需要填充

5.6、Base64编码的特点

a) Base64编码是编码,不是压缩,编码后只会增加字节数

b) 算法可逆, 解码很方便, 不用于私密信息通信

c) 标准的Base64每行为76个字符,行末添加换行符

d) 加密后的字符串只有65种字符, 不可打印字符也可传输

e) 在Java层可以通过hook对应方法名来快速定位关键代码

f) 在so层可以通过输入输出的数据和码表来确定算法

6、消息摘要算法

6.1、算法特点

a) 消息摘要算法/单向散列函数/哈希函数

b) 不同长度的输入,产生固定长度的输出

c) 散列后的密文不可逆

d) 散列后的结果唯一

e) 哈希碰撞

f) 一般用于校验数据完整性、签名sign

由于密文不可逆,所以服务端也无法解密。想要验证,就需要跟前端一样的方式去重新签名一遍,签名算法一般会把源数据和签名后的值一起提交到服务端,要保证在签名时候的数据和提交上去的源数据一致。

6.2、常见算法

MD5、SHA1、SHA256、SHA512、HmacMD5、HmacSHA1、HmacSHA256、HmacSHA512

RIPEMD160、HmacRIPEMD160、PBKDF2、EvpKDF

6.3、MD5

  1. MD5的Java实现
MessageDigest md5 = MessageDigest.getInstance("MD5");
md5.update("whitebird".getBytes());
byte[] md5result = md5.digest();
System.out.println(HexBin.encode(md5result));
System.out.println(new String(Base64.getEncoder().encode(md5result)));
//  byte[] md5result1 = MessageDigest.getInstance("MD5").digest("whitebird".getBytes());
  1. 加密后的字节数组可以编码成Hex、Base64
  2. 没有任何输入,也能计算hash值
  3. 碰到加salt的MD5,可以直接输入空的值,得到结果去CMD5查询一下,有可能就得到salt
String str="whitebird";
String salt="a123456";
MessageDigest md5 = MessageDigest.getInstance("MD5");
md5.update((str+salt).getBytes());
byte[] md5result = md5.digest();
System.out.println(HexBin.encode(md5result));
System.out.println(new String(Base64.getEncoder().encode(md5result)));

比如我们想对whitebird字符串进行摘要,在这个时候我们会对字符串进行拼接上另外一个字符串salt,这个就是加盐,如果我们把str设置为空,再把摘要后的结果放到CMD5进行查询,就有可能获取这个salt

这个原理就是撞库,所以不是百分百能得到结果

6.4、sha算法

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

所有的操作和MD5一样,只不过getInstance的参数改成了SHA-1

6.5、利用hook代码获取加密数据流

由于MD5和SHA算法都是调用的java库函数进行操作,所以我们可以利用frida编写通杀脚本,以后可以对app中使用了系统库中的MD5或者SHA加密算法进行hook,从而得到加密前的数据。

Java.perform(function(){
    console.log(Java.enumerateLoadedClassesSync().join("\n"))
});

先遍历找到所有已经加载的类

这个类里有一些方法可以用来将输出转为base64、hex、UTF8打印

Java.perform(function(){
    var ByteString = Java.use("com.android.okhttp.okio.ByteString");
    function toBase64(tag,data){
        console.log(tag+"Base64:"+ByteString.of(data).base64());
    }
    function toHex(tag,data){
        console.log(tag+"Hex:"+ByteString.of(data).hex());
    }
    function toUtf8(tag,data){
        console.log(tag+"Utf8:"+ByteString.of(data).utf8());
    }
    var data=[48,49,50,51,52];
    toHex("whitebird ",data);
    toBase64("whitebird ",data);
    toUtf8("whitebird ",data);

});

对于MD5和SHA都是用的MessageDigest下面的getInstance、update和digest,直接hook就完事了

Java.perform(function(){

    function showStacks() {
        console.log(
            Java.use("android.util.Log")
                .getStackTraceString(
                    Java.use("java.lang.Throwable").$new()
                )
        );
    }

    var ByteString = Java.use("com.android.okhttp.okio.ByteString");
    function toBase64(tag,data){
        console.log(tag+"Base64:"+ByteString.of(data).base64());
    }
    function toHex(tag,data){
        console.log(tag+"Hex:"+ByteString.of(data).hex());
    }
    function toUtf8(tag,data){
        console.log(tag+"Utf8:"+ByteString.of(data).utf8());
    }
    var data=[48,49,50,51,52];
    toHex("whitebird ",data);
    toBase64("whitebird ",data);
    toUtf8("whitebird ",data);


    var messageDigest = Java.use("java.security.MessageDigest");
    messageDigest.update.overload('byte').implementation=function (data) {
        console.log("MessageDigest.update('byte') is called!");
        showStacks();
        return this.update(data);
    }
    messageDigest.update.overload('java.nio.ByteBuffer').implementation=function (data) {
        console.log("MessageDigest.update('ByteBuffer') is called!");
        showStacks();
        return this.update(data);
    }
    messageDigest.update.overload('[B').implementation = function (data) {
        console.log("MessageDigest.update('[B') is called!");
        showStacks();
        var algorithm = this.getAlgorithm();
        var tag = algorithm + " update data";
        toUtf8(tag, data);
        toHex(tag, data);
        toBase64(tag, data);
        console.log("=======================================================");
        return this.update(data);
    }
    messageDigest.update.overload('[B', 'int', 'int').implementation = function (data, start, length) {
        console.log("MessageDigest.update('[B', 'int', 'int') is called!");
        showStacks();
        var algorithm = this.getAlgorithm();
        var tag = algorithm + " update data";
        toUtf8(tag, data);
        toHex(tag, data);
        toBase64(tag, data);
        console.log("=======================================================", start, length);
        return this.update(data, start, length);
    }

    messageDigest.digest.overload().implementation = function () {
        console.log("MessageDigest.digest() is called!");
        showStacks();
        var result = this.digest();
        var algorithm = this.getAlgorithm();
        var tag = algorithm + " digest result";
        toUtf8(tag, result);
        toHex(tag, result);
        toBase64(tag, result);
        console.log("=======================================================");
        return result;
    }
    messageDigest.digest.overload('[B').implementation = function (data) {
        console.log("MessageDigest.digest('[B') is called!");
        showStacks();
        var algorithm = this.getAlgorithm();
        var tag = algorithm + " digest data";
        toUtf8(tag, data);
        toHex(tag, data);
        toBase64(tag, data);
        var result = this.digest(data);
        var tags = algorithm + " digest result";
        toUtf8(tag, result);
        toHex(tags, result);
        toBase64(tags, result);
        console.log("=======================================================");
        return result;
    }
    messageDigest.digest.overload('[B', 'int', 'int').implementation = function (data, start, length) {
        console.log("MessageDigest.digest('[B', 'int', 'int') is called!");
        showStacks();
        var algorithm = this.getAlgorithm();
        var tag = algorithm + " digest data";
        toUtf8(tag, data);
        toHex(tag, data);
        toBase64(tag, data);
        var result = this.digest(data, start, length);
        var tags = algorithm + " digest result";
        toUtf8(tag, result);
        toHex(tags, result);
        toBase64(tags, result);
        console.log("=======================================================", start, length);
        return result;
    }
});

6.6、MAC

MAC算法与MD5和SHA的区别是多了一个密钥,密钥可以随机给

SecretKeySpec secretKeySpec =
        new SecretKeySpec("a12345678".getBytes(),"HmacSHA1");
Mac mac = Mac.getInstance(secretKeySpec.getAlgorithm());
//Mac mac = Mac.getInstance("HmacSHA1")
mac.init(secretKeySpec);
mac.update("whitebird".getBytes());
byte[] result =   mac.doFinal();
System.out.println(HexBin.encode(result));
System.out.println(new String(Base64.getEncoder().encode(result)));

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

对于mac算法的hook和md5、sha1类似

Java.perform(function(){

    function showStacks() {
        console.log(
            Java.use("android.util.Log")
                .getStackTraceString(
                    Java.use("java.lang.Throwable").$new()
                )
        );
    }

    var ByteString = Java.use("com.android.okhttp.okio.ByteString");
    function toBase64(tag,data){
        console.log(tag+"Base64:"+ByteString.of(data).base64());
    }
    function toHex(tag,data){
        console.log(tag+"Hex:"+ByteString.of(data).hex());
    }
    function toUtf8(tag,data){
        console.log(tag+"Utf8:"+ByteString.of(data).utf8());
    }
    var data=[48,49,50,51,52];
    toHex("whitebird ",data);
    toBase64("whitebird ",data);
    toUtf8("whitebird ",data);

    var mac = Java.use("javax.crypto.Mac");
    mac.init.overload('java.security.Key', 'java.security.spec.AlgorithmParameterSpec').implementation = function (key, AlgorithmParameterSpec) {
        console.log("Mac.init('java.security.Key', 'java.security.spec.AlgorithmParameterSpec') is called!");
        return this.init(key, AlgorithmParameterSpec);
    }
    mac.init.overload('java.security.Key').implementation = function (key) {
        console.log("Mac.init('java.security.Key') is called!");
        var algorithm = this.getAlgorithm();
        var tag = algorithm + " init Key";
        var keyBytes = key.getEncoded();
        toUtf8(tag, keyBytes);
        toHex(tag, keyBytes);
        toBase64(tag, keyBytes);
        console.log("=======================================================");
        return this.init(key);
    }
    mac.update.overload('byte').implementation = function (data) {
        console.log("Mac.update('byte') is called!");
        return this.update(data);
    }
    mac.update.overload('java.nio.ByteBuffer').implementation = function (data) {
        console.log("Mac.update('java.nio.ByteBuffer') is called!");
        return this.update(data);
    }
    mac.update.overload('[B').implementation = function (data) {
        console.log("Mac.update('[B') is called!");
        var algorithm = this.getAlgorithm();
        var tag = algorithm + " update data";
        toUtf8(tag, data);
        toHex(tag, data);
        toBase64(tag, data);
        console.log("=======================================================");
        return this.update(data);
    }
    mac.update.overload('[B', 'int', 'int').implementation = function (data, start, length) {
        console.log("Mac.update('[B', 'int', 'int') is called!");
        var algorithm = this.getAlgorithm();
        var tag = algorithm + " update data";
        toUtf8(tag, data);
        toHex(tag, data);
        toBase64(tag, data);
        console.log("=======================================================", start, length);
        return this.update(data, start, length);
    }
    mac.doFinal.overload().implementation = function () {
        console.log("Mac.doFinal() is called!");
        var result = this.doFinal();
        var algorithm = this.getAlgorithm();
        var tag = algorithm + " doFinal result";
        toUtf8(tag, result);
        toHex(tag, result);
        toBase64(tag, result);
        console.log("=======================================================");
        return result;
    }
    mac.doFinal.overload('[B').implementation = function (data) {
        console.log("Mac.doFinal.overload('[B') is called!");
        return this.doFinal(data);
    }
    mac.doFinal.overload('[B', 'int').implementation = function (output, outOffset) {
        console.log("Mac.doFinal.overload('[B', 'int') is called!");
        return this.doFinal(output, outOffset);
    }
});

hook结果如下

whitebird Hex:3031323334
whitebird Base64:MDEyMzQ=
whitebird Utf8:01234
Mac.init('java.security.Key') is called!
HmacSHA1 init KeyUtf8:12345678
HmacSHA1 init KeyHex:3132333435363738
HmacSHA1 init KeyBase64:MTIzNDU2Nzg=
=======================================================
Mac.update('[B') is called!
HmacSHA1 update dataUtf8:xiaojianbang小肩膀
HmacSHA1 update dataHex:7869616f6a69616e62616e67e5b08fe882a9e88680
HmacSHA1 update dataBase64:eGlhb2ppYW5iYW5n5bCP6IKp6IaA
=======================================================
Mac.doFinal.overload('[B') is called!
Mac.update('[B') is called!
HmacSHA1 update dataUtf8:saltstr
HmacSHA1 update dataHex:73616c74737472
HmacSHA1 update dataBase64:c2FsdHN0cg==
=======================================================
Mac.doFinal() is called!
HmacSHA1 doFinal resultUtf8:�BհE��e*��?���ze
HmacSHA1 doFinal resultHex:e81742d5b045bcea88652af0048c3fbbf6eb7a65
HmacSHA1 doFinal resultBase64:6BdC1bBFvOqIZSrwBIw/u/bremU=
=======================================================

可以得到key是12345678,然后加入了数据xiaojianbang小肩膀,调用了Mac.update(‘[B’),又往里加了saltstr,完成了字符串拼接为

xiaojianbang小肩膀saltstr

7、对称加密算法

7.1、基础

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

7.2、DES

DES-ECB/CBC

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

import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.Base64;

public class Main {
    public static void main(String[] args) throws NoSuchAlgorithmException, InvalidKeyException {
    try{
        SecretKeySpec desKey = new SecretKeySpec("12345678".getBytes(), "DES");
        // IvParameterSpec ivParameterSpec = new IvParameterSpec("12345678".getBytes());
        Cipher des = Cipher.getInstance("DES/ECB/PKCS5Padding");
        des.init(Cipher.ENCRYPT_MODE, desKey);
        byte[] result = des.doFinal("whitbird".getBytes());
        System.out.println(HexBin.encode(result));
        System.out.println(new String(Base64.getEncoder().encode(result)));
    }catch (Exception e){
        e.printStackTrace();
    }

    }
}

再用网站加密一下

结果是一样的。用java解密试试

try{
       SecretKeySpec desKey = new SecretKeySpec("12345678".getBytes(), "DES");
      // IvParameterSpec ivParameterSpec = new IvParameterSpec("12345678".getBytes());
       Cipher des = Cipher.getInstance("DES/ECB/PKCS5Padding");
       des.init(Cipher.ENCRYPT_MODE, desKey);
       byte[] result = des.doFinal("whitbird".getBytes());
       System.out.println(HexBin.encode(result));
       System.out.println(new String(Base64.getEncoder().encode(result)));

       des.init(Cipher.DECRYPT_MODE,desKey);
       byte[] result1= des.doFinal(result);
       System.out.println(new String(result1));
   }catch (Exception e){
       e.printStackTrace();
   }

需要修改init的第一个参数,改为DECRYPT_MODE,也就是解密模式

还有一种带iv的,属于cbc模式

public static void main(String[] args) throws NoSuchAlgorithmException, InvalidKeyException {
try{
    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);
    byte[] result = des.doFinal("whitbird".getBytes());
    System.out.println(HexBin.encode(result));
    System.out.println(new String(Base64.getEncoder().encode(result)));
}catch (Exception e){
    e.printStackTrace();
}

}

多了一步生成iv,把iv加入到des.init中

修改了模式为CBC,用网站加密试试

再用java解密

try{
    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);
    byte[] result = des.doFinal("whitbird".getBytes());
    System.out.println(HexBin.encode(result));
    System.out.println(new String(Base64.getEncoder().encode(result)));

    des.init(Cipher.DECRYPT_MODE,desKey,ivParameterSpec);
    byte[] result1= des.doFinal(result);
    System.out.println(new String(result1));
}catch (Exception e){
    e.printStackTrace();
}

特点

1、对称加密算法里,使用NOPadding,加密的明文必须等于分组长度倍数,否则报错。针对java会报错,c语言会自动在结尾补充字节0

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位进行分组加密,也就是8个字节

10、如果明文中有两个分组的内容相同,ECB会得到完全一样的密文,CBC不会。

11、加密算法的结果通常与明文等长或者更长,如果变短了,那可能是gzip、protobuf、消息摘要算法

12、DES其实key有效值只有56位,因为每个字节的最后一位不会参与运算

00110001 00110010 00110011 00110100 00110101 00110110 00110111 00111000
把每个字节最后一位取反
00110000 00110011 00110010 00110101 00110100 00110111 00110110 00111001

也就是03254769,现在拿这两个key分别加密试试,结果是一样的

DESede-ECB/CBC

其实就是3des

try{
           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, secretKeySpec, ivParameterSpec);
           byte[] data = desede.doFinal("whitbird".getBytes());
           System.out.println(HexBin.encode(data));
           System.out.println(new String(Base64.getEncoder().encode(data)));
       }catch (Exception e){
           e.printStackTrace();
       }

3DES的密钥就是DES的3倍,也就是24个字节,192位,但是我们之前也说了,每个字节的前七位参与运算,最后一位不参与,所以真正参与加密的key是192-24=168位

特点

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

2、DESede的密钥24个字节,第1组8字节密钥用于DES加密,第2组8个字节密钥用于DES解密,第3组8个字节用于DES加密

3、如果DESede的3个8字节密钥相同,则加密结果与DES一致,因为第一组和第二组加密解密相抵消了

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

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

7.3、AES

public class Main {
    public static void main(String[] args) throws NoSuchPaddingException, NoSuchAlgorithmException {
       try {
           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);
           byte[] result4 = aes.doFinal("Whitebird1234Whitebird".getBytes());
           String hexStr = HexBin.encode(result4);
           System.out.println(hexStr);
           System.out.println(Base64.getEncoder().encodeToString(result4));
       }catch (Exception e){
           e.printStackTrace();
       }
    }
}

通过网站再加密一次验证

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

2、AES算法明文按128位进行分组加密,其余特征与DES一致

7.4、DES/DESede/AES算法通杀hook

其实也就是对init、dofinal进行hook

Java.perform(function () {

    var ByteString = Java.use("com.android.okhttp.okio.ByteString");
    function toBase64(tag, data) {
        console.log(tag + " Base64: " + ByteString.of(data).base64());
    }
    function toHex(tag, data) {
        console.log(tag + " Hex: " + ByteString.of(data).hex());
    }
    function toUtf8(tag, data) {
        console.log(tag + " Utf8: " + ByteString.of(data).utf8());
    }

    var Cipher=Java.use("javax.crypto.Cipher");
    Cipher.init.overload('int', 'java.security.Key').implementation=function (a,b){
        console.log("Cipher.init.overload('int', 'java.security.Key')is called!");
        console.log("=======================================================");
        return this.init(a,b);
    }
    Cipher.init.overload('int', 'java.security.cert.Certificate') .implementation=function (a,b){
        console.log("Cipher.init('int', 'java.security.cert.Certificate') is called!");
        console.log("=======================================================");
        return this.init(a,b);
    }
    Cipher.init.overload('int', 'java.security.Key', 'java.security.AlgorithmParameters') .implementation=function (a,b,c){
        console.log("=======================================================");
        return this.init(a,b,c);
    }
    Cipher.init.overload('int', 'java.security.Key', 'java.security.SecureRandom') .implementation=function (a,b,c){
        console.log("Cipher.init('int', 'java.security.Key', 'java.security.SecureRandom') is called!");
        console.log("=======================================================");
        return this.init(a,b,c);
    }
    Cipher.init.overload('int', 'java.security.Key', 'java.security.spec.AlgorithmParameterSpec').implementation=function (a,b,c){
        console.log("Cipher.init('int', 'java.security.Key', 'java.security.AlgorithmParameters') is called!");
        console.log("=======================================================");
        var algorithm = this.getAlgorithm();
        var tag = algorithm + " init Key";
        var keyBytes = b.getEncoded();
        if (a==1){
            console.log("ENCRYPT_MODE")
        }else if (a==2){
            console.log("DECRYPT_MODE")
        }
        toUtf8(tag, keyBytes);
        toHex(tag, keyBytes);
        toBase64(tag, keyBytes);
        var tags = algorithm + " init iv";
        var iv = Java.cast(c, Java.use("javax.crypto.spec.IvParameterSpec"));
        var ivBytes = iv.getIV();
        toUtf8(tags, ivBytes);
        toHex(tags, ivBytes);
        toBase64(tags, ivBytes)
        return this.init(a,b,c);
    }
    Cipher.init.overload('int', 'java.security.cert.Certificate', 'java.security.SecureRandom') .implementation=function (a,b,c){
        console.log("Cipher.init('int', 'java.security.cert.Certificate', 'java.security.SecureRandom') is called!");
        console.log("=======================================================");
        return this.init(a,b,c);
    }
    Cipher.init.overload('int', 'java.security.Key', 'java.security.AlgorithmParameters', 'java.security.SecureRandom') .implementation=function (a,b,c,d){
        console.log("Cipher.init('int', 'java.security.Key', 'java.security.AlgorithmParameters', 'java.security.SecureRandom') is called!");
        console.log("=======================================================");
        return this.init(a,b,c,d);
    }
    Cipher.init.overload('int', 'java.security.Key', 'java.security.spec.AlgorithmParameterSpec', 'java.security.SecureRandom').implementation=function (a,b,c,d){
        console.log("Cipher.init('int', 'java.security.Key', 'java.security.spec.AlgorithmParameterSpec', 'java.security.SecureRandom') is called!");
        console.log("=======================================================");
        return this.init(a,b,c,d);
    }

    Cipher.doFinal.overload().implementation=function (){
        console.log("Cipher.doFinal() is called!");
        console.log("=======================================================");
        return this.doFinal();
    }
    Cipher.doFinal.overload('[B').implementation=function (a){
        console.log("Cipher.doFinal('[B') is called!");
        var algorithm = this.getAlgorithm();
        var tag = algorithm + " doFinal data";
        toUtf8(tag, a);
        toHex(tag, a);
        toBase64(tag, a);
        var result = this.doFinal(a);
        var tags = algorithm + " doFinal result";
        toUtf8(tags, result);
        toHex(tags, result);
        toBase64(tags, result);
        console.log("=======================================================");
        return result;
    }
    Cipher.doFinal.overload('java.nio.ByteBuffer', 'java.nio.ByteBuffer').implementation=function (a,b){
        console.log("Cipher.doFinal('java.nio.ByteBuffer', 'java.nio.ByteBuffer') is called!");
        console.log("=======================================================");
        return this.doFinal(a,b);
    }
    Cipher.doFinal.overload('[B', 'int').implementation=function (a,b){
        console.log("Cipher.doFinal('[B', 'int') is called!");
        console.log("=======================================================");
        return this.doFinal(a,b);
    }
    Cipher.doFinal.overload('[B', 'int', 'int').implementation=function (a,b,c){
        console.log("Cipher.doFinal('[B', 'int', 'int') is called!");
        var algorithm = this.getAlgorithm();
        var tag = algorithm + " doFinal data";
        toUtf8(tag, a);
        toHex(tag, a);
        toBase64(tag, a);
        var result = this.doFinal(a,b,c);
        var tags = algorithm + " doFinal result";
        toUtf8(tags, result);
        toHex(tags, result);
        toBase64(tags, result);
        console.log("=======================================================", b, c);
        return result;
    }
    Cipher.doFinal.overload('[B', 'int', 'int', '[B').implementation=function (a,b,c,d){
        console.log("Cipher.doFinal('[B', 'int', 'int', '[B') is called!");
        console.log("=======================================================");
        return this.doFinal(a,b,c,d);
    }
    Cipher.doFinal.overload('[B', 'int', 'int', '[B', 'int').implementation=function (a,b,c,d,e){
        console.log("Cipher.doFinal('[B', 'int', 'int', '[B', 'int') is called!");
        console.log("=======================================================");
        return this.doFinal(a,b,c,d,e);
    }
})

分别是DES的加密与解密,密文是xiaojianbang小肩膀,key的十六进制是0102030405060708,iv的utf是12345678

由于DES和3DES的api是一样的,所以上面的脚本也适用于3DES

AES也同理

8、非对称加密算法

8.1、RSA介绍

1、需要生成一个密钥对,包含公钥和私钥,密钥不是随便写的

密钥对生成 http://web.chacuo.net/netrsakeypair

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

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

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

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

8.2、RSA密钥的解析

String publicKeyBase64="MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA9HJaaMA52tJdP8bCRRlt" +
        "NpmkvZ+Fi5aUTdDU4NA4HkhdrXcC7DN8nVp7xS7jcCQFfyF2m/7untZHX8S8dZzF" +
        "vnVUQwEIfEKSHwV0AtCeFSMCQdLAg6okHzFeprI+bVWRgJqA0Ev9hKXUNYxoaSw2" +
        "kKHRw1Lj4kAwwz88iLmgUlKmM+JYOnvAzOd/T2r8mAjdSiWDQtKZZDl/NjWsZzfm" +
        "VT/bs3giSfpdiSiYhI/Bd05GWvw4FHuvY0l+BEIwVRLxtIFV/ourE7eDZx+m5TOZ" +
        "JU+gPwW+BxDvtc9GHuBjKBk/LEe5pIEZUWfOEcaKRDwixCTSqU589ZwY0Y3xHwCh" +
        "EQIDAQAB";

byte[] publicKeyBytes = Base64.getDecoder().decode(publicKeyBase64.getBytes());
X509EncodedKeySpec publickeySpec = new X509EncodedKeySpec(publicKeyBytes);
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
PublicKey publicKey = keyFactory.generatePublic(publickeySpec);

String privateKeyBase64="MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQD0" +
        "clpowDna0l0/xsJFGW02maS9n4WLlpRN0NTg0DgeSF2tdwLsM3ydWnvFLuNwJAV/IXab" +
        "/u6e1kdfxLx1nMW+dVRDAQh8QpIfBXQC0J4VIwJB0sCDqiQfMV6msj5tVZGAmoDQS/2E" +
        "pdQ1jGhpLDaQodHDUuPiQDDDPzyIuaBSUqYz4lg6e8DM539PavyYCN1KJYNC0plkOX82" +
        "NaxnN+ZVP9uzeCJJ+l2JKJiEj8F3TkZa/DgUe69jSX4EQjBVEvG0gVX+i6sTt4NnH6bl" +
        "M5klT6A/Bb4HEO+1z0Ye4GMoGT8sR7mkgRlRZ84RxopEPCLEJNKpTnz1nBjRjfEfAKER" +
        "AgMBAAECggEAG0IVKtEkom4Kz+3OA/6N2yTO5gY/0gz8hKDg3CDb4SSYrZszzDsBjlpk" +
        "qtxLqFmb8007+LE40qV6WAeMLGFK+64Bs2cOFy0lNihZg2bX+etpeQWTShtS49eRgm0l" +
        "4PNFwl14N8IBhyUmwMHjcrbnbsFcrrcziis2RM9sZKKuaf9tlkHffgSRlwDFnfuLvFfH" +
        "dhb8FoUtRY7808aWOSXqxWmMdGO0LE4vWcwzQThFyNNJS0ohIykRLCbKRa09K8UfxZ/h" +
        "RLK8pVegHvGGJ79X1/EuiUQNTwdMstayDomZvMDFJ8mlcmQzccHpMHPCBtCqdqjmOcbn" +
        "5KEPhTT7y+2XQQKBgQD8QiUK2QZ4xpcOVu5M+wZzoVVRuh1I4eHNk5EVbE7xRUan4Ze0" +
        "8wy4oTb7V1NhyoCUwOvikwsu3b36DDjmIx+tc2/IdLAj/bixym0yuNZ7qfP8xhkWtkYx" +
        "TwCSVP3JgVZkewU55PtK/Ip9to8A/vHVzNrPI5WAagZCFmuEoCwf7QKBgQD4EovrKPzK" +
        "ocPg+HJBw0d/u6TR608Iq/MA/J7qOI7c2FWd0rY7LRb94hMQ3c3ahawhd8EmqQQtYzd/" +
        "m9qriyBmxIOCcuCqQNzbwDrkgV1FBVc1QIFfC7D4neVe1RtW3yRPrGXEICXmOTabhKjO" +
        "ikZ4/MawnhBc9uJW3JsIRwN5NQKBgDdkcuvh4ijtOshcUHG5AZ6sg6axnrKdX08cI1S3" +
        "9tHGK5C72etTH6z/QaqJb3be18NSZbnYHjGJYakhu/DVL6SP37D95pvQoBjyYW4s5nQ7" +
        "Sqdr/KBfICS9u1tmrF/uNcFUvj4LSR8bO3IFf3txgDs5g6bOooqTPZKPh8aUwXu1AoGB" +
        "AK6wMMNqW4k9nLghfCP/Jaw3MPt53m/W1gpN2oJJTaEK3k+DADxvpg8mUhLAfqNxGiN/" +
        "Nw7IWl6zzXMZB0VhdXGo1IrZ5oc9oclFdrNOvaVK+3xObkvF6dtJJSpAmz3zaJiKaCdS" +
        "FB4pD+E367918+Zi6m+Y/yDX4c8VDj8n8rQ5AoGBANLq1q7phUWijJ8fn8cL9aPef924" +
        "YzyvXtalnarLPyyKC1u7UbeGfulDV4faFWpVTJJFWLKepA2cMP8Viaa2HBJdXz8sFXYt" +
        "BIP6655o4ofzae1IPJjFmAcHpVmsF0GeAptsCVGnV5lxgnOhWAEuIpLWMBM5f2d4P8LC" +
        "6YUncp6X";

byte[] privateKeyBytes = Base64.getDecoder().decode(privateKeyBase64.getBytes());
PKCS8EncodedKeySpec privateSpec = new PKCS8EncodedKeySpec(privateKeyBytes);
PrivateKey privateKey = keyFactory.generatePrivate(privateSpec);

8.3、RSA加解密

Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
cipher.init(Cipher.ENCRYPT_MODE, publicKey);
byte[] bt_encrypted = cipher.doFinal("whitebird".getBytes());
System.out.println(Base64.getEncoder().encodeToString(bt_encrypted));

cipher.init(Cipher.DECRYPT_MODE, privateKey);
byte[] bt_original = cipher.doFinal(bt_encrypted);
System.out.println(new String(bt_original));

加密结果如上图,因为PKCS1Padding每次填充的值是随机的,所以每次运行得到的结果也不同

8.4、RSA的加密模式和填充方式

1、None模式与ECB模式是一致的

2、NOPadding

明文最多字节数为密钥字节数

填充字节0, 加密后的密文不变

密文与密钥等长

由于我们的密钥是128字节,所以我们能够加密的明文长度也是128

129个字节就会报错,我们再尝试一下长度小于128

我设置的长度是120,它会自动在前面补充8个0字节,由于我是16进制打印的,所以会显示16个00

3、PKCS1Padding

明文最大字节数为密钥字节数-11

我填充了120个字节,显示数量过大

每一次的填充不一样,都是0,2开头,0结尾,但是中间的数据不一样,使得加密后的密文会变,密文与密钥等长。

这里我用的RSA/ECB/PKCS1Padding加密,用的RSA/ECB/NOPadding解密的,如果用PKCS1Padding解密,就自动去除了前面填充的数据,演示效果不明显

String publicKeyBase64="MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDaoIXgGy3b0ggImcTp7LGItyvxDfm8BtW+nkZSaGtnyeN1/P2CothujWOINseMKc1/mCbwbEjBdl5rJEak/iXLKMXrRKTyLr5S9YasdsTX7cDHBjuN5b4FiaHWYdWZ+SLS6IADq5KDeFBDwHCGMCcDkp4iOeX6wrNbzE0Vy14x8QIDAQAB";

          byte[] publicKeyBytes = Base64.getDecoder().decode(publicKeyBase64.getBytes());
          X509EncodedKeySpec publickeySpec = new X509EncodedKeySpec(publicKeyBytes);
          KeyFactory keyFactory = KeyFactory.getInstance("RSA");
          PublicKey publicKey = keyFactory.generatePublic(publickeySpec);

          String privateKeyBase64="MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBANqgheAbLdvSCAiZxOnssYi3K/EN+bwG1b6eRlJoa2fJ43X8/YKi2G6NY4g2x4wpzX+YJvBsSMF2XmskRqT+JcsoxetEpPIuvlL1hqx2xNftwMcGO43lvgWJodZh1Zn5ItLogAOrkoN4UEPAcIYwJwOSniI55frCs1vMTRXLXjHxAgMBAAECgYAsEi3KhlARPt6FtVGfgiorkBkYh6OvBoWx3j/4vrnf+y+SCkSkP+V3K6NDwx8onwigzvASiRGFhjH0USS4f87oLJbWVEruUg+mUBG3UQv+Pn8Q5HGYEUP1qOCkNfYoWPnrK2FJx/gQatD8FvSMtB/hxPuoFhm3XMc6TqUx8K6qTQJBAP5zvlJSMItnVkXYjs063ge9xOYGS1BsgnGaKABwI9rGPjoq4kDz/P3chTuoUDU8EYOS7tMcB6SyiSW2HMLPuIcCQQDb9P1GUinGQleroS7zrCIx3qrblPUERm3vvNyfoXL1GeYjseiEr3Q6DUuv4qEgUy5Ep2JqsAhYfkraiCgkqXfHAkAtNmnB/OKtUo5M2N7+7o/XNkwPR10IjE9dWjPzZ/mm/QS/lzEes3vnq9BsukqOOv8truUK0/czt5mQ/tv7badvAkEArCCeC2fE1SeZn+/05C9yWwBbSJba3suWwKNVK36WjYQE+y4APcVn+P3qmuuJF9jAPm2WsNKs1UOPpFAkyfvshQJAF+mZ4vmY9l+swa9fUcWJLQfC2eow7ZWtlib8adr/zZhUiDJanA5P4x5OwH9do1FKgkuYOx+JMwaLS8WCPWt+kA==";

          byte[] privateKeyBytes = Base64.getDecoder().decode(privateKeyBase64.getBytes());
          PKCS8EncodedKeySpec privateSpec = new PKCS8EncodedKeySpec(privateKeyBytes);
          PrivateKey privateKey = keyFactory.generatePrivate(privateSpec);

          StringBuffer stringBuffer=new StringBuffer();
          for (int i = 0; i < 117; i++) {
              stringBuffer.append('a');
          }
          System.out.println("stringBuffer length:"+stringBuffer.length());

          Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
          cipher.init(Cipher.ENCRYPT_MODE, publicKey);
          byte[] bt_encrypted = cipher.doFinal(stringBuffer.toString().getBytes());
          System.out.println("bt_encrypted length:"+bt_encrypted.length);
          System.out.println(Base64.getEncoder().encodeToString(bt_encrypted));

          Cipher cipher1 = Cipher.getInstance("RSA/ECB/NOPadding");
          cipher1.init(Cipher.DECRYPT_MODE, privateKey);
          byte[] bt_original = cipher1.doFinal(bt_encrypted);
          System.out.println("bt_original length:"+bt_original.length);
          System.out.println(Arrays.toString(bt_original));
          System.out.println(new String(bt_original));

没有指明加密模式和填充方式,表示使用默认的RSA/ECB/PKCS1Padding

8.5、RSA密钥的转换

RSA密码由三个整数组成,我们分别称之为n、e、d

(n、e):公钥,可以对外公布。

(n、d):私钥,这个我们要私密保存。
n:模数(Modulus),私钥和公钥都包含有这个数。
e:公钥指数(publicExponent),一般是固定值65537。
d:私钥指数(privateExponent)

我们先创建一个文件private_pkcs1.pem,写入我们的私钥

-----BEGIN PRIVATE KEY-----
MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBAMmOwfMyWE+r0PEJ
2Iuevdrk1SxDjjDINnT6+L6o0kBmh/VMlCgf52PRXltDu6O/eGVeK/s5oF8mPenM
o7FiOnfX2OxgKuEldyyvpEAVoj0iB4KZpA64f0ab/Q6JSIXG4noUEVpjgntv5qT3
1pu9sMeM/XaPafvDbsFlHXWXAbstAgMBAAECgYA67eMk0I5jVM+0+XT3dUT677an
x2SWPKM3Vn1NeRxnudJ8VJRomtNTKs6UjlwZBw0TThI71GBgEVU+uxcM8LQ4+ut8
q97YYc+OD1wPWXsxJ3Dahj5EgNTBr3g5gV2gBvOyDK8xwL1XvCiwYpk3W50n6lpQ
YHTKJj+diA+1++lf4QJBAOXaVklLDdfv8pLfZR69y0e+UM/Jey4uBlJfuKMCWYIA
QpZ2xT/i63xFtrXlQV9zfXHE2IiLonmygyZJN+1TOJkCQQDgfGuxGced9YhkfIMj
iSu96H3qSE8sZzV2adg5GAwdtubvbopl8YJUpY877Ctyy1gkMkLl6139iy+6Fuwg
/8+1AkBm/jynvt/HIJd7RGqVKcpKrXachNWTTcA7bPTY5hahb+hmb3XXOKD4IGzM
eJugX6mYLvIvzRHSojCuGSTcTOwRAkBaNaWRJarx7xlASEjqntTVG/h9jVFDprX1
P9OEHgXAax+iV+mFzedx3JYSnnVXTB+/a/M2Mb8zhBtJzKxXFnWBAkEAhUjXHwfo
bocFQgWdnO9RWJcE/IuhbOEc9BeMOGIJuERHKpRN5ZFsKmtalF6sIRa0hJgD0HBz
ldIDGKSyYrz4Vg==
-----END PRIVATE KEY-----

然后输入指令

openssl rsa -in private_pkcs1.pem -out public_pkcs1.der -pubout -RSAPublicKey_out -outform DER

产生一个der文件,public_pkcs1.der是个二进制文件,我们用010看一下

public_pkcs1.pem 如下

-----BEGIN PUBLIC KEY-----
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDJjsHzMlhPq9DxCdiLnr3a5NUs
Q44wyDZ0+vi+qNJAZof1TJQoH+dj0V5bQ7ujv3hlXiv7OaBfJj3pzKOxYjp319js
YCrhJXcsr6RAFaI9IgeCmaQOuH9Gm/0OiUiFxuJ6FBFaY4J7b+ak99abvbDHjP12
j2n7w27BZR11lwG7LQIDAQAB
-----END PUBLIC KEY-----

public_pkcs1.pem 实际与 public_pkcs1.der 包含了完全相同的信息。我们发现这个数据有三部分:“—–BEGIN RSA PUBLIC KEY—–”、中间的数据、“—–END RSA PUBLIC KEY—–”。下面我们把数据用base解码:

可以看到,public_pkcs1.der 里面的数据是解码后的数据的一部分

PKCS#1是有结构的,也就是说der是有结构的

RSAPublicKey ::= SEQUENCE {
   modulus           INTEGER,  -- n
   publicExponent    INTEGER   -- e
}

PKCS#1形式的RSA公钥定义很简单,只包含n、e值。上面的定义用的是ASN.1抽象语法标记(Abstract Syntax Notation One,ASN.1用来描述一段二级制数据的结构信息,就像我们用XML来描述文本文件的结构信息一样。我们不能把n、e两个值直接拼接在一起形成二进制公钥,因为这样做公钥的使用者在解析这段公钥的时候就会懵逼。这是什么类型的二级制数据?如果是是一个公钥,那么这个公钥是长度是多少?那段数据是n值,那一段是e值?所以实际上,DER格式公钥除了包含n、e信息以外,还包含了一些结构信息。

30 //ASN.1 DER 编码的开始
81 //代表接下来 1 个字节是表达长度
89 //排除掉前面 308189 的长度
02 INTEGER 
81 //代表接下来 1 个字节是表达长度
81 Length
//00 代表模数的开始
00 c9 8e c1 f3 32 58 4f ab d0 f1 09 d8 8b 9e bd da e4 d5 2c 43 8e 30 c8 36 74 fa f8 be a8 d2 40 66 87 f5 4c 94 28 1f e7 63 d1 5e 5b 43 bb a3 bf 78 65 5e 2b fb 39 a0 5f 26 3d e9 cc a3 b1 62 3a 77 d7 d8 ec 60 2a e1 25 77 2c af a4 40 15 a2 3d 22 07 82 99 a4 0e b8 7f 46 9b fd 0e 89 48 85 c6 e2 7a 14 11 5a 63 82 7b 6f e6 a4 f7 d6 9b bd b0 c7 8c fd 76 8f 69 fb c3 6e c1 65 1d 75 97 01 bb 2d 

02 INTEGER  
03 Length
Value 01 00 01               

用openssl查看n、e、d

openssl rsa -in private_pkcs1.pem -text -noout ////以文本格式输出私钥内容

String stringN ="00c98ec1f332584fabd0f109d88b9ebddae4d52c438e30c83674faf8bea8d2406687f54c94281fe763d15e5b43bba3bf78655e2bfb39a05f263de9cca3b1623a77d7d8ec602ae125772cafa44015a23d22078299a40eb87f469bfd0e894885c6e27a14115a63827b6fe6a4f7d69bbdb0c78cfd768f69fbc36ec1651d759701bb2d";
String stringE="010001";
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);
byte[] pubkeyBytesss = publicKey.getEncoded();
System.out.println(Base64.getEncoder().encodeToString(pubkeyBytesss));
Cipher cipher = Cipher.getInstance("RSA");
cipher.init(Cipher.ENCRYPT_MODE, publicKey);
byte[] bt_encrypted = cipher.doFinal("whitebird".getBytes());
System.out.println(Base64.getEncoder().encodeToString(bt_encrypted));

我们把证书的字节流用base64加密回去了

MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDJjsHzMlhPq9DxCdiLnr3a5NUs
Q44wyDZ0+vi+qNJAZof1TJQoH+dj0V5bQ7ujv3hlXiv7OaBfJj3pzKOxYjp319js
YCrhJXcsr6RAFaI9IgeCmaQOuH9Gm/0OiUiFxuJ6FBFaY4J7b+ak99abvbDHjP12
j2n7w27BZR11lwG7LQIDAQAB

这个结果和之前的是一样的,我们自己手动加上“—–BEGIN RSA PUBLIC KEY—–”、“—–END RSA PUBLIC KEY—–”就行了

下面的是私钥

现在我们需要解密,要生成私钥

String stringN ="00c98ec1f332584fabd0f109d88b9ebddae4d52c438e30c83674faf8bea8d2406687f54c94281fe763d15e5b43bba3bf78655e2bfb39a05f263de9cca3b1623a77d7d8ec602ae125772cafa44015a23d22078299a40eb87f469bfd0e894885c6e27a14115a63827b6fe6a4f7d69bbdb0c78cfd768f69fbc36ec1651d759701bb2d";
String stringE="010001";
String privateD ="3aede324d08e6354cfb4f974f77544faefb6a7c764963ca337567d4d791c67b9d27c5494689ad3532ace948e5c19070d134e123bd4606011553ebb170cf0b438faeb7cabded861cf8e0f5c0f597b312770da863e4480d4c1af7839815da006f3b20caf31c0bd57bc28b06299375b9d27ea5a506074ca263f9d880fb5fbe95fe1";

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);
byte[] pubkeyBytesss = publicKey.getEncoded();
System.out.println(Base64.getEncoder().encodeToString(pubkeyBytesss));
Cipher cipher = Cipher.getInstance("RSA");
cipher.init(Cipher.ENCRYPT_MODE, publicKey);
byte[] bt_encrypted = cipher.doFinal("whitebird".getBytes());
System.out.println(Base64.getEncoder().encodeToString(bt_encrypted));


BigInteger D = new BigInteger(privateD, 16); //16改10就可以把十进制转成大数
RSAPrivateKeySpec spec1 = new RSAPrivateKeySpec(N, D);
PrivateKey privateKey =keyFactory.generatePrivate(spec1);
byte[] privatekeyBytes = privateKey.getEncoded();
System.out.println(Base64.getEncoder().encodeToString(privatekeyBytes));
cipher.init(Cipher.DECRYPT_MODE, privateKey);
byte[] bt_original = cipher.doFinal(bt_encrypted);
System.out.println(new String(bt_original));

用到了N和D,N是之前的,E是openssl解析出来的私钥

解密成功

8.6、多种加密算法的常见结合套路

随机生成AES密钥AESKey

AESKey密钥用于AES加密数据,得到数据密文cipherText

使用RSA对AESKey加密,得到密钥密文cipherKey

提交密钥密文cipherKey和数据密文cipherText给服务器

9、数字签名算法

代码实现

Signature sig = Signature.getInstance("SHA256withRSA");
sig.initSign(privateKey);
sig.update("whitebird".getBytes());
byte[] signResult = sig.sign();
System.out.println(Base64.getEncoder().encodeToString(signResult));

这个加密流程是对明文先进行SHA256加密,得到的结果进行hex,然后对这个结果再进行RSA加密

对签名进行验证

sig.initVerify(publicKey);
sig.update("whitebird".getBytes());
Boolean result = sig.verify(signResult);
System.out.println(result);

打印了true

我们进行反向验证

MessageDigest SHA256 = MessageDigest.getInstance("SHA-256");
        SHA256.update("whitebird".getBytes());
        byte[] sha256result = SHA256.digest();
        System.out.println(HexBin.encode(sha256result));

       Cipher cipher1 = Cipher.getInstance("RSA/ECB/NOPadding");
       cipher.init(Cipher.DECRYPT_MODE, publicKey);
       byte[] before_rsa = cipher.doFinal(signResult);
       System.out.println(HexBin.encode(before_rsa));

我们自己进行sha256加密,然后得到的结果进行hex

对之前加密完的结果用publicKey手动解密,解密完也进行hex

D267D6D3BC0FCBBC3D1A789FC9F0D0374487A204908C248171306D8E5C4625F2
3031300D060960864801650304020105000420D267D6D3BC0FCBBC3D1A789FC9F0D0374487A204908C248171306D8E5C4625F2

可以看出,在进行RSA加密之前还对密文进行了填充,填充的值为3031300D060960864801650304020105000420

我们换一个明文重新加密,看看填充的值是否会发生改变

Signature sig = Signature.getInstance("SHA256withRSA");
    sig.initSign(privateKey);
    sig.update("abc".getBytes());
    byte[] signResult = sig.sign();
    System.out.println(Base64.getEncoder().encodeToString(signResult));


    sig.initVerify(publicKey);
    sig.update("abc".getBytes());
    Boolean result = sig.verify(signResult);
    System.out.println(result);


     MessageDigest SHA256 = MessageDigest.getInstance("SHA-256");
     SHA256.update("abc".getBytes());
     byte[] sha256result = SHA256.digest();
     System.out.println(HexBin.encode(sha256result));

    Cipher cipher1 = Cipher.getInstance("RSA/ECB/NOPadding");
    cipher.init(Cipher.DECRYPT_MODE, publicKey);
    byte[] before_rsa = cipher.doFinal(signResult);
    System.out.println(HexBin.encode(before_rsa));

结果如下

BA7816BF8F01CFEA414140DE5DAE2223B00361A396177A9CB410FF61F20015AD
3031300D060960864801650304020105000420BA7816BF8F01CFEA414140DE5DAE2223B00361A396177A9CB410FF61F20015AD

填充值还是3031300D060960864801650304020105000420,是固定的

通杀hook

如果想拿明文,就是hook Signature的update,想拿私钥,就hook Signature的initSign

var signature =Java.use("java.security.Signature");
signature.update .overload('byte') .implementation=function (a) {
    console.log("Signature.update('byte') is called!");
    console.log("=======================================================");
    return this.update(a);
}
var signature =Java.use("java.security.Signature");
signature.update .overload('java.nio.ByteBuffer') .implementation=function (a) {
    console.log("Signature.update('java.nio.ByteBuffer') is called!");
    console.log("=======================================================");
    return this.update(a);
}
var signature =Java.use("java.security.Signature");
signature.update .overload('[B') .implementation=function (a) {
    console.log("Signature.update('[B') is called!");
    toUtf8("signature.update", a);
    toHex("signature.update", a);
    toBase64("signature.update", a);
    console.log("=======================================================");
    return this.update(a);
}
var signature =Java.use("java.security.Signature");
signature.update .overload('[B', 'int', 'int') .implementation=function (a,b,c) {
    console.log("Signature.update('[B', 'int', 'int') is called!");
    toUtf8("signature.update", a);
    toHex("signature.update", a);
    toBase64("signature.update", a);
    console.log("=======================================================",b,c);
    return this.update(a,b,c);
}


signature.initVerify.overload('java.security.PublicKey').implementation=function (a){
    console.log("Signature.initVerify('java.security.PublicKey') is called!");
    var opensslRsaPk=Java.use("com.android.org.conscrypt.OpenSSLRSAPublicKey");
    var key =Java.cast(a,opensslRsaPk).getEncoded();
    toUtf8("rsa private key:", key);
    toHex("rsa private key:", key);
    toBase64("rsa private key:", key);
    console.log("=======================================================");
    return this.initVerify(a);
}
signature.initVerify.overload('java.security.cert.Certificate').implementation=function (a){
    console.log("Signature.initVerify('java.security.cert.Certificate') is called!");
    console.log("=======================================================");
    return this.initVerify(a);
}

10、CryptoJS

消息摘要算法

var CryptoJS = module.exports;

console.log(CryptoJS.MD5("whitebird") + '');
console.log(CryptoJS.SHA256("whitebird").toString());
console.log(CryptoJS.HmacSHA1("whitebird", "12345678").toString());

消息摘要算法的其他调用形式

模板

SHA256
var hasher = CryptoJS.algo.SHA256.create();
hasher.reset();
hasher.update('message');
var hash = hasher.finalize();
var hash = hasher.finalize('message');

举例

var hasher = CryptoJS.algo.SHA256.create();
hasher.update('whitebird');
var hash = hasher.finalize('saltstr');
console.log(hash + '');

reset会清空,update和finalize都会添加数据

模板

HmacSHA256
var hmacHasher = CryptoJS.algo.HMAC.create(CryptoJS.algo.SHA256, key);
hmacHasher.reset();
hmacHasher.update('message');
var hmac = hmacHasher.finalize();
var hmac = hmacHasher.finalize('message');

举例

var hmacHasher = CryptoJS.algo.HMAC.create(CryptoJS.algo.SHA1, "12345678");
hmacHasher.reset();
hmacHasher.update('whitebird');
var hmac = hmacHasher.finalize();
console.log(hmac + '');

字符串解析

string转wordArray

CryptoJS.enc.Utf8.parse(utf8String);
CryptoJS.enc.Hex.parse(hexString);
CryptoJS.enc.Base64.parse(base64String);

分别以UTF8、Hex、Base64进行解码

测试

var utf8String="whitebird"
console.log(CryptoJS.enc.Utf8.parse(utf8String).toString())
var hexString = "776869746562697264";
console.log(CryptoJS.enc.Hex.parse(hexString).toString())
var base64String="d2hpdGViaXJk"
console.log(CryptoJS.enc.Base64.parse(base64String).toString())

默认以16进制打印

wordArray形式

wordArray转string

wordArray + '';
wordArray.toString();
wordArray.toString(CryptoJS.enc.Utf8);//以UTF-8形式打印
wordArray.toString(CryptoJS.enc.Hex);//以Hex形式打印
wordArray.toString(CryptoJS.enc.Base64);//以Base64形式打印
CryptoJS.enc.Utf8.stringify(wordArray);
CryptoJS.enc.Hex.stringify(wordArray);
CryptoJS.enc.Base64.stringify(wordArray);

测试

var utf8String="whitebird"

console.log(CryptoJS.enc.Utf8.parse(utf8String).toString(CryptoJS.enc.Utf8))
console.log(CryptoJS.enc.Utf8.parse(utf8String).toString(CryptoJS.enc.Hex))
console.log(CryptoJS.enc.Utf8.parse(utf8String).toString(CryptoJS.enc.Base64))

对称加密算法

var  message =CryptoJS.enc.Utf8.parse("whitebird")
var key=CryptoJS.enc.Hex.parse("0102030405060708")
var Utf8IVBytes=CryptoJS.enc.Utf8.parse("12345678");
var cfg = {
    iv: Utf8IVBytes,
    mode: CryptoJS.mode.CBC,
    padding: CryptoJS.pad.Pkcs7,
    format: CryptoJS.format.Hex //指定格式化输出为16进制,默认是base64
};


var ciphertext = CryptoJS.DES.encrypt(message, key, cfg);
console.log(ciphertext)
console.log("key hex:"+ciphertext.key+"")
console.log("iv utf-8:"+CryptoJS.enc.Hex.parse(ciphertext.iv+"").toString(CryptoJS.enc.Utf8))
console.log("ciphertext1:"+ciphertext+"")
console.log("ciphertext2 :"+ciphertext.ciphertext+"")
var plaintext  = CryptoJS.DES.decrypt(ciphertext, key, cfg);
console.log("plaintext:"+CryptoJS.enc.Hex.parse(plaintext+"").toString(CryptoJS.enc.Utf8))



//var ciphertext = CryptoJS.TripleDES.encrypt(message, key, cfg);
// var plaintext  = CryptoJS.TripleDES.decrypt(ciphertext, key, cfg);
//
// var ciphertext = CryptoJS.AES.encrypt(message, key, cfg);
// var plaintext  = CryptoJS.AES.decrypt(ciphertext, key, cfg);
//
// var ciphertext = CryptoJS.RC4.encrypt(message, key, cfg);
// var plaintext  = CryptoJS.RC4.decrypt(ciphertext, key, cfg);

1、cfg中没有传mode和padding,默认使用CBC的加密模式,Pkcs7的填充方式

2、加密结果是wordArray对象,调用toString默认转Base64编码的密文,转hex可以使用

var hexString = wordArray.ciphertext.toString();

3、CryptoJS中提供的加密模式:CBC ECB CFB OFB CTRGladman CTR

4、CryptoJS中提供的填充方式:NoPadding ZeroPadding Pkcs7(Pkcs5) Iso10126 Iso97971 AnsiX923

5、密文/明文的自定义输出/输入(cfg中format的指定)

format: {
    stringify: function (data){
        let e = {
            ct: data.ciphertext.toString(),
            miaoshu: "这是我们的自定义输出内容"
        };
        return JSON.stringify(e)
    },
    parse: function (data){
        let json = JSON.parse(data);
        let newVar = CryptoJS.lib.CipherParams.create({ciphertext: CryptoJS.enc.Hex.parse(json.ct)});
        return newVar
    }
}

其他算法

let ripemd160 = CryptoJS.RIPEMD160("whitebird");
console.log(ripemd160 + '');
let hmacRIPEMD160 = CryptoJS.HmacRIPEMD160("whitebird", "12345678");
console.log(hmacRIPEMD160 + '');

类似MD5算法

let pbkdf2 = CryptoJS.PBKDF2("whitebird", "saltstr", {keySize: 8, iterations: 10000});
console.log(pbkdf2 + '');

let evpKDF = CryptoJS.EvpKDF("whitebird", "saltstr", {keySize: 8, iterations: 10000});
console.log(evpKDF + '');

区块链中常用的加密算法


转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 767778848@qq.com