本公众号分享的所有技术仅用于学习交流,请勿用于其他非法活动,如果错漏,欢迎留言指正
《Java加密与解密的艺术》 c语言的实现请参照之前发布的这篇文章:加解密快速入门
java快速入门
一、java基础
.java
->.class
不记得概念,回头看即可
遇到陌生的api,查JDK_API文档即可
Overview (Java SE 11 & JDK 11 ) (runoob.com)
java变量、 数据类型、条件语句、循环语句
import java.util.Scanner; import java.util.Arrays; /** * 对这个类进行注释 * Main.java * 类名:首字母大写且驼峰式命名,例如:Main、UserInfo、PersonApplication * 类修饰符:public、default(不写) * 一个文件中最多只能有一个public类 且文件名必须和public类名一致。 * 如果文件中有多个类,文件名与public类名一致。 * 如果文件中有多个类且无public类,文件名可以是任意类名。 */ public class Main { /** * 对这个方法进行注释 * 主函数必须要是main * 方法名: 首字母小写且驼峰式命名,后面的每一个单词的首字母大写。例如:getName() * 类中成员修饰符:public、private、protected、default(不写) * void表示方法没有返回值。如果有返回则必须在定义方式就要写明他是返回的什么类型。 * @param args 字符数组 */ public static void main(String[] args) { String name = ""; // 单行注释,变量 int age = 24; final char gender = 'M'; // 常量 // 输出 System.out.print("please input name:"); // 尾部不加换行 please input name:cisco // 输入 Scanner input = new Scanner(System.in); name = input.nextLine(); System.out.println(name + ',' + gender + ','+ age); // 尾部自动加换行 cisco,M,24 // 静态成员,无需实例化就可以指定调用。 ControlFlow.switchFun(gender); // man // 非静态成员需实例化才可以指定调用。 ControlFlow obj = new ControlFlow(); obj.ifFun(age); // 大叔 obj.whileFun(); /* 多行注释 0,执行中... 1,执行中... 2,执行中... 3,退出while... */ obj.doWhileFun(); /* 3,执行中... do-while至少执行一次do{}。即使一开始就不满足条件((count = 3) < 3),也会执行一次do{}里面的内容 4,退出do-while... */ obj.forFun(); /* 0 1 2 3 4 continue 6 7 8 9 */ /* 整数类型 byte,字节 【1字节】表示范围:-128 ~ 127 即:-2^7 ~ 2^7 -1 short,短整型 【2字节】表示范围:-32768 ~ 32767 int,整型 【4字节】表示范围:-2147483648 ~ 2147483647 long,长整型 【8字节】表示范围:-9223372036854775808 ~ 9223372036854775807 */ DataType obj2 = new DataType(); obj2.byteToString(); // 字符串是:aid=246387571&auto_play=0&cid=289008441&did=KREhESMUJh8tFCEVaRVpE2taPQk7WChCJg&epid=0&ftime=1627100937&lv=0&mid=0&part=1&sid=0&stime=1627104372&sub_type=0&type=3 /* 字符:'a','b','c' 字符串: "abc",字符串是由多个字符串组成。在Java中,字符串是由码点组成的,而不是由字节组成的。 由于网络传输或存储需要,把unicode编码的字符串压缩成UTF-8、GBK编码的字节数组 */ obj2.stringToByte(); /* [-42, -48, -50, -60, -41, -42, -73, -5] GBK编码 [-28, -72, -83, -26, -106, -121, -27, -83, -105, -25, -84, -90] UTF-8编码 */ // 字符串的定义 String v11 = "中文字符"; System.out.println(v11); // 中文字符 String v12 = new String("中文字符"); System.out.println(v12); // 中文字符 String v13 = new String(new byte[]{-28, -72, -83, -26, -106, -121, -27, -83, -105, -25, -84, -90}); System.out.println(v13); // 中文字符 try { String v14 = new String(new byte[]{-42, -48, -50, -60, -41, -42, -73, -5}, "GBK"); System.out.println(v14); // 中文字符 } catch (Exception e) { } String v15 = new String( new char[]{'中', '文', '字', '符'} ) ; System.out.println(v15); // 中文字符 // 字符串的方法 String origin = "cisco 字符串 使用方法"; char v1 = origin.charAt(6); // 指定字符 System.out.println(v1); // 字 int len = origin.length(); // 长度 System.out.println(len); // 14 for (int i = 0; i < len; i++) { char item = origin.charAt(i); } String v2 = origin.trim(); // 去除空白 System.out.println(v2); // cisco 字符串 使用方法 String v3 = origin.toLowerCase(); // 小写 System.out.println(v3); // cisco 字符串 使用方法 String v4 = origin.toUpperCase(); // 大写 System.out.println(v4); // CISCO 字符串 使用方法 String[] v5 = origin.split("字"); // 分割 System.out.println(v5); // [Ljava.lang.String;@5197848c System.out.println(Arrays.toString(v5)); // [cisco , 符串 使用方法] String v6 = origin.replace("o", "i"); // 替换 System.out.println(v6); // cisci 字符串 使用方法 String v7 = origin.substring(2, 6); // 子字符串=切片 [2:6] System.out.println(v7); // "sco " boolean v8 = origin.equals("cisco 字符串 使用方法"); // "alex是个大DB" "alex是个大SB" System.out.println(v8); // false boolean v9 = origin.contains("co"); System.out.println(v9); // false boolean v10 = origin.startsWith("c"); System.out.println(v10); // false String v100 = origin.concat("哈哈哈"); // 末尾追加 System.out.println(v100); // cisco 字符串 使用方法哈哈哈 // 字符串拼接 StringBuilder sb = new StringBuilder(); // StringBuffer是多线程安全的 sb.append("name"); sb.append("="); sb.append("cisco"); sb.append("&"); sb.append("age"); sb.append("="); sb.append("18"); System.out.println(sb); // name=cisco&age=18 System.out.println(sb.toString()); // name=cisco&age=18 /* 数组,存放固定长度的元素。容器+特定类型+固定长度(数组一旦创建个数就不可调整) */ // [123,1,999] int[] numArray = new int[3]; numArray[0] = 123; numArray[1] = 1; numArray[2] = 99; System.out.println(Arrays.toString(numArray)); // [123, 1, 99 String[] names = new String[]{"cisco", "123", "456"}; // 使用 "new" 关键字来创建数组对象,并在括号中指定数组的大小和初始值。 System.out.println(Arrays.toString(names)); // [cisco, 123, 456] String[] nameArray = {"cisco", "123", "456"}; // 使用 "{" 和 "}" 来创建数组对象,并在括号中指定数组的初始值,不需要使用 "new" 关键字。 System.out.println(Arrays.toString(nameArray)); // [cisco, 123, 456] // 索引 // nameArray[0] // nameArray.length for (int idx = 0; idx < nameArray.length; idx++) { String item = nameArray[idx]; } /* object 在Python中每个类都默认继承Object类(所有的类都是Object的子类)。 在Java所有的类都是默认继承Object类。 */ // 用基类可以泛指他的子类的类型。 String v00 = "cisco"; System.out.println(v00); // cisco System.out.println(v00.getClass()); // class java.lang.String Object v01 = new String("cisco"); System.out.println(v01); // cisco System.out.println(v01.getClass()); // class java.lang.String // 声明数组,数组中元素必须int类型; int[] v02 = new int[3]; // 声明数组,数组中元素必须String类型; String[] v03 = new String[3]; // 想要声明的数组中想要是混合类型,就可以用Object来实现。数组中可以是int/String类型; Object[] v04 = new Object[3]; v04[0] = 123; v04[1] = "cisco"; System.out.println(Arrays.toString(v04)); // [123, cisco, null] // 类型判断 TypeOf.func(123); // 整型 TypeOf.func("123"); // 字符串类型 } }
class ControlFlow{
public void ifFun(int age){
// 条件1:if
if (age < 18) {
System.out.println("少年");
} else if (age < 40) {
System.out.println("大叔");
} else {
System.out.println("老汉");
}
}
// 静态成员,无需实例化就可以指定调用。
public static void switchFun(char gender){
// 条件2:switch
switch (gender) {
case 'F':
System.out.println("female");
break; // 退出{},不再执行后续的代码
case 'M':
System.out.println("man");
break; default:
System.out.println("unknown");
break; }
}
public void whileFun(){
// 循环1:while
int count = 0;
while (count < 3) {
System.out.println(count + ",执行中...");
count += 1;
}
System.out.println(count + ",退出while...");
}
public void doWhileFun(){
// 循环2:do-while
int count = 3;
do {
System.out.println(count+",执行中...");
count += 1;
} while (count < 3);
System.out.println(count + ",退出do-while...");
}
public void forFun(){
// 循环3:for
for (int i = 0; i < 10; i++) {
if(i == 5) {
System.out.println("continue");
continue; // 单次退出{},这次循环内不再执行后续的代码
}
System.out.println(i);
}
}
}
class DataType{
public void byteToString() {
// 1.字节数组(转换为Unicode编码的字符串) [字节,字节,字节]
byte[] dataList = {97, 105, 100, 61, 50, 52, 54, 51, 56, 55, 53, 55, 49, 38, 97, 117, 116, 111, 95, 112, 108, 97, 121, 61, 48, 38, 99, 105, 100, 61, 50, 56, 57, 48, 48, 56, 52, 52, 49, 38, 100, 105, 100, 61, 75, 82, 69, 104, 69, 83, 77, 85, 74, 104, 56, 116, 70, 67, 69, 86, 97, 82, 86, 112, 69, 50, 116, 97, 80, 81, 107, 55, 87, 67, 104, 67, 74, 103, 38, 101, 112, 105, 100, 61, 48, 38, 102, 116, 105, 109, 101, 61, 49, 54, 50, 55, 49, 48, 48, 57, 51, 55, 38, 108, 118, 61, 48, 38, 109, 105, 100, 61, 48, 38, 112, 97, 114, 116, 61, 49, 38, 115, 105, 100, 61, 48, 38, 115, 116, 105, 109, 101, 61, 49, 54, 50, 55, 49, 48, 52, 51, 55, 50, 38, 115, 117, 98, 95, 116, 121, 112, 101, 61, 48, 38, 116, 121, 112, 101, 61, 51};
// byte[] dataList = {97, 105, 100, 61}; // aid= 这不是unicode编码啊,是ascii编码 @todo
String dataString = new String(dataList);
System.out.println("字符串是:" + dataString); // 字符串是:aid=246387571&auto_play=0&cid=289008441&did=KREhESMUJh8tFCEVaRVpE2taPQk7WChCJg&epid=0&ftime=1627100937&lv=0&mid=0&part=1&sid=0&stime=1627104372&sub_type=0&type=3
}
public void stringToByte() {
// 2.Unicode编码的字符串->其他编码格式的字节数组
try {
// Python中的 name.encode("gbk") String name = "中文字符";
byte[] v1 = name.getBytes("GBK");
System.out.println(Arrays.toString(v1)); // [-42, -48, -50, -60, -41, -42, -73, -5]
// Python中的 name.encode("utf-8") byte[] v2 = name.getBytes("UTF-8");
System.out.println(Arrays.toString(v2)); // [-28, -72, -83, -26, -106, -121, -27, -83, -105, -25, -84, -90]
} catch (Exception e) {
}
}
}
class TypeOf{
public static void func(Object v1) {
// System.out.println(v1);
// System.out.println(v1.getClass()); if (v1 instanceof Integer) {
System.out.println("整型");
} else if (v1 instanceof String) {
System.out.println("字符串类型");
} else {
System.out.println("未知类型");
}
}
}
### 应用数据类型与基本数据类型之间的转换
- 应用数据类型(或称为包装类型)是对基本数据类型的封装,应用数据类型是`对象`,而基本数据类型是`值`。
- 抽象类`Number` 是 `BigDecimal`、`BigInteger`、`Byte`(byte)、`Boolean`(boolean)、`Character`(char)、`Short`(short)、`Integer`(int)、`Long`(long)、`Float`(float)、`Double`(double)的父类。
- Java中使用应用数据类型(如Integer、Double等)是为了解决基本数据类型(如int、double等)存在的问题。应用数据类型提供了更多的灵活性和安全性,使得程序员能够更容易地编写安全且易于维护的代码。
- 使用应用数据类型可以避免`空指针异常`,因为应用数据类型对象默认初始值为`null`,而基本数据类型则`没有默认值`。
- 可以使用应用数据类型作为`泛型`参数,而不能使用基本数据类型。
- 应用数据类型对象可以更容易地在`集合`中存储和检索。
```java
public class PackingDataType {
public static void main(String[] args) {
/*
java中的应用数据类型(引用类型)和基本数据类型可以通过自动装箱和拆箱进行转换。
自动装箱:将基本类型转换为对应的应用数据类型,例如int转换为Integer。
自动拆箱:将应用数据类型转换为对应的基本类型,例如Integer转换为int。
*/ int inta = 1;
System.out.println(inta); // 1
// System.out.println(inta.getClass()); // java: 无法取消引用int
Integer IntegerA = inta;
System.out.println(IntegerA);
System.out.println(IntegerA.getClass()); // class java.lang.Integer
int inta1 = IntegerA;
System.out.println(inta1); // 1
// System.out.println(inta1.getClass()); // java: 无法取消引用int
/* 在 Java 5 之前使用包装类的构造函数进行装箱操作效率较低,
建议使用包装类的静态方法进行转换,如Integer.valueOf() 和 Integer.intValue() */ int a = 5;
Integer A = Integer.valueOf(a); // int 转 Integer: 使用 Integer.valueOf(int i) 或者 new Integer(int i) int a1 = A.intValue(); // Integer 转 int: 使用 Integer.intValue()
long b = 10;
Long B = Long.valueOf(b); // long 转 Long: 使用 Long.valueOf(long l) 或者 new Long(long l) long b1 = B.longValue(); // Long 转 long: 使用 Long.longValue()
double c = 3.14;
Double C = Double.valueOf(c); // double 转 Double: 使用 Double.valueOf(double d) 或者 new Double(double d) double c1 = C.doubleValue(); // Double 转 double: 使用 Double.doubleValue()
char d = 'a';
Character D = Character.valueOf(d); // char 转 Character: 使用 new Character(char c) char d1 = D.charValue(); // Character 转 char: 使用 Character.charValue()
// short 转 Short: 使用 Short.valueOf(short s) 或者 new Short(short s) // Short 转 short: 使用 Short.shortValue() // byte 转 Byte: 使用 Byte.valueOf(byte b) 或者 new Byte(byte b) // Byte 转 byte: 使用 Byte.byteValue() // boolean 转 Boolean: 使用 Boolean.valueOf(boolean b) 或者 new Boolean(boolean b) // Boolean 转 boolean: 使用 Boolean.booleanValue() }
}
这三个类是 Java 中用来操作字符串的工具类。
StringBuffer
: 用于构建可变的字符串,可以对字符串进行增删改操作。由于其线程安全
,所以在多线程环境下可以使用。StringBuilder
: 用于构建可变的字符串,也可以对字符串进行增删改操作。但是不保证线程安全
,因此在单线程环境下效率更高。Array
: 数组是一种容器,用于存储固定数量的同类型的元素,因此可以用来存储大量的数据。Array 中的各种方法,如 sort、search、copy、fill 等,可以帮助你管理和处理数组中的数据。Java中List、Set、Map
import java.util.ArrayList; import java.util.LinkedList; import java.util.Iterator; // Java 中用于遍历集合元素的迭代器接口 import java.util.HashSet; import java.util.TreeSet; import java.util.HashMap; import java.util.TreeMap; import java.util.Map; import java.util.Set; public class ComplexType { public static void main(String[] args) { /// 1. List /* List是一个接口,接口下面有两个常见的类型(目的是可以存放动态的多个数据) ArrayList,连续的内存地址的存储(内部自动扩容)。 LinkedList,底层基于链表实现(自行车链条)。 Java中接口,是用来约束实现他的类。比如约束他里面的成员必须有add。 interface List{ public void add(Object data); // 接口中的方法,不写具体的实现,只用于约束。 } // 类ArrayList实现了接口List,此时这个类就必须有一个add方法。 class ArrayList implements List{ public void add(Object data){ // 将数据data按照连续存储的方法放在内存。 // .. } } // 类LinkedList实现了接口List,此时这个类就必须有一个add方法。 class LinkedList implements List{ public void add(Object data){ // 将数据data按照链表的形式存储 // .. } } */ // ArrayList,默认内部存放的是混合数据类型。 // ArrayList<String> data = new ArrayList<String>(); // 指定数据类型String // ArrayList<Object> data = new ArrayList<Object>(); ArrayList data = new ArrayList(); data.add("cisco1"); data.add("cisco2"); data.add(666); data.add("cisco3");
// String value = data.get(1); // java: 不兼容的类型: java.lang.Object无法转换为java.lang.String
String value = (String) data.get(1);
System.out.println(value); // cisco2
Object temp = data.get(1);
String value1 = (String) temp;
System.out.println(value1); // cisco2
int xo = (int) data.get(2);
System.out.println(xo); // 666
data.set(0, "哈哈哈哈");
System.out.println(data); // [哈哈哈哈, cisco2, 666, cisco3]
data.remove("cisco2"); // [666, cisco3]
data.remove(0);
System.out.println(data);
int size = data.size();
System.out.println(size); // 2
boolean exists = data.contains("cisco3");
System.out.println(exists); // true
for (int i = 0; i < data.size(); i++) {
Object item = data.get(i);
System.out.println(item); // 666 cisco3
}
for (Object item : data) { // 迭代器
System.out.println(item); // 666 cisco3
}
Iterator it = data.iterator(); // 迭代器
while (it.hasNext()) {
// String item = (String) it.next(); // ArrayList data中存在不是String的元素666,迭代器返回的元素不是字符串类型,将迭代器遍历出来的元素强制转换为字符串时就会抛出ClassCastException异常。
Object item = it.next();
System.out.println(item); // 666 cisco3
}
LinkedList<Integer> v1 = new LinkedList<Integer>(); // int的链表
v1.add(11);
v1.add(22);
System.out.println(v1); // [11, 22]
LinkedList<Object> v2 = new LinkedList<Object>();
v2.add("cisco1");
v2.add("cisco2");
v2.add(666);
v2.add(123);
System.out.println(v2); // [cisco1, cisco2, 666, 123]
v2.remove(1);
v2.remove("666");
System.out.println(v2); // [cisco1, 666, 123]
v2.set(2, "xxx");
v2.push("哈哈哈");
v2.addFirst(11);
System.out.println(v2); // [11, 哈哈哈, cisco1, 666, xxx]
for (int i = 0; i < v2.size(); i++) {
Object item = v2.get(i);
System.out.println(item); // 11 哈哈哈 cisco1 666 xxx }
for (Object item : v2) {
System.out.println(item); // 11 哈哈哈 cisco1 666 xxx }
Iterator it1 = data.iterator(); // 迭代器
while (it.hasNext()) {
Object item = it1.next();
System.out.println(item); // 11 哈哈哈 cisco1 666 xxx }
/// 2. Set
/* Set是一个接口,常见实现这个接口的有两个类,用于实现不重复的多元素集合。
HashSet,去重,无序。
TreeSet,去重,内部默认排序(ascii、unicode)【不同的数据类型,无法进行比较】。
*/ // HashSet s1 = new HashSet(); // Set s1 = new HashSet(); // HashSet<String> s1 = new HashSet<String>(); HashSet s1 = new HashSet();
s1.add("P站");
s1.add("B站");
s1.add("A站");
s1.add("P站");
s1.add(666);
s1.remove("A站");
System.out.println(s1); // [B站, P站, 666]
boolean exist = s1.contains("B站");
System.out.println(exist); // true
// s2 = {"东京热","东北热","南京热"}
HashSet s2 = new HashSet(){
{
add("东京热");
add("东北热");
add("南京热");
}
};
System.out.println(s2); // [东京热, 南京热, 东北热]
s2.addAll(s1);
System.out.println(s2); // [东京热, B站, P站, 南京热, 666, 东北热]
s2.retainAll(s1); // 交集 & System.out.println(s2); // [B站, P站, 666]
s2.addAll(s1); // 并集 | System.out.println(s2); // [B站, P站, 666]
s2.removeAll(s1); // 差集 s1 - s2 System.out.println(s2); // []
// Set s2 = new TreeSet(); // TreeSet<String> s2 = new TreeSet<String>(); TreeSet s3 = new TreeSet();
s3.add("P站");
s3.add("B站");
s3.add("A站");
s3.add("P站");
// s3.add(666); // 不可以,不同的数据类型,无法进行比较
System.out.println(s3); // [A站, B站, P站]
TreeSet s4 = new TreeSet(){
{
add("P站");
add("B站");
add("A站");
add("P站");
}
};
System.out.println(s4); // [A站, B站, P站]
for (Object item : s4) {
System.out.println(item); // A站 B站 P站
}
Iterator it11 = s4.iterator();
while (it11.hasNext()) {
Object item = it11.next();
System.out.println(item); // A站 B站 P站
}
/// 3.Map
/* Map是一个接口,常见实现这个接口的有两个类,用于存储键值对。
HashMap,无序。
TreeMap,默认根据key排序。(常用)
*/
HashMap h1 = new HashMap();
h1.put("name","cisco");
h1.put("age",18);
h1.put("gender","男");
System.out.println(h1); // {gender=男, name=cisco, age=18}
h1.remove("age");
System.out.println(h1); // {gender=男, name=cisco}
int size1 = h1.size();
System.out.println(size1); // 2
Object value11 = h1.get("name"); // 不存在,返回null
System.out.println(value11); // cisco
boolean existsKey = h1.containsKey("age");
System.out.println(existsKey); // false
boolean existsValue = h1.containsValue("cisco");
System.out.println(existsValue); // true
h1.replace("name", "cc");
System.out.println(h1); // {gender=男, name=cc}
// 循环: 示例1
Set<Map.Entry<String, String>> s11 = h1.entrySet(); // 使用h1.entrySet()方法获取所有键值对并将它们存储在一个set中。
System.out.println(s11); // [gender=男, name=cc]
Iterator it111 = s11.iterator();
while (it111.hasNext()) {
// ("name", "cisco")
Map.Entry<String, String> entry = (Map.Entry<String, String>) it111.next();
String k = (String) entry.getKey();
String v = (String) entry.getValue();
System.out.println(k + ':' + v); // gender:男 name:cc }
// 循环: 示例2
Set s22 = h1.entrySet();
Iterator it222 = s22.iterator();
while (it222.hasNext()) {
Map.Entry entry = (Map.Entry) it222.next();
String k = (String) entry.getKey();
String v = (String) entry.getValue();
System.out.println(k + ':' + v); // gender:男 name:cc }
HashMap<String,String> h2 = new HashMap<String,String>();
h2.put("name","cisco");
h2.put("age","18");
h2.put("gender","男");
System.out.println(h2); // {gender=男, name=cisco, age=18}
HashMap<String,String> h3 = new HashMap<String,String>(){
{
put("name","cisco");
put("age","18");
put("gender","男");
}
};
System.out.println(h3); // {gender=男, name=cisco, age=18}
TreeMap t1 = new TreeMap(); // 改为了TreeMap
t1.put("name","cisco");
t1.put("age","18");
t1.put("gender","男");
System.out.println(t1); // {age=18, gender=男, name=cisco}
TreeMap<String,String> t2 = new TreeMap<String,String>();
t2.put("name","cisco");
t2.put("age","18");
t2.put("gender","男");
System.out.println(t2); // {age=18, gender=男, name=cisco}
Map t3 = new TreeMap();
t3.put("name","cisco");
t3.put("age",18);
t3.put("gender","男");
System.out.println(t3); // {age=18, gender=男, name=cisco}
// 循环: 示例3
for (Object entry : t1.entrySet()) {
Map.Entry<String, Object> entryMap = (Map.Entry<String, Object>) entry;
String k = entryMap.getKey();
Object v = entryMap.getValue();
System.out.println(k + ':' + v); // age:18 gender:男 name:cisco }
// 循环: 示例4
for (Map.Entry<String, String> entry : t2.entrySet()) {
String k = entry.getKey();
String v = entry.getValue();
System.out.println(k + ':' + v); // age:18 gender:男 name:cisco }
}
}
- List
![](https://cisco-1306627320.cos.ap-guangzhou.myqcloud.com/Mardown/image-20220208160404756.png)
- Set
![](https://cisco-1306627320.cos.ap-guangzhou.myqcloud.com/Mardown/image-20220208160211442.png)
- Map
![](https://cisco-1306627320.cos.ap-guangzhou.myqcloud.com/Mardown/image-20220208151234641.png)
![](https://cisco-1306627320.cos.ap-guangzhou.myqcloud.com/Mardown/20230129173545.png)
![](https://cisco-1306627320.cos.ap-guangzhou.myqcloud.com/Mardown/20230129173509.png)
```python
在Python中需要自己处理key排序的问题。
v4 = {
"aid":123, "xx":999, "wid":888}
# 1.根据key进行排序
# data = ["{}={}".format(key,v4[key]) for key in sorted(v4.keys())]
# 2.再进行拼接
# result = "&".join(data)
result = "&".join(["{}={}".format(key,v4[key]) for key in sorted(v4.keys())])
包的概念、静态成员、构造函数、函数重载、抽象类、继承、接口、多态、内部类
/*
/// 1.包的概念
import utils.Helper; // 导入utils 包中的Helper类
文件目录结构
src
├── ObjectOriented.java
└── utils
└── Helper.java
// Helper类的实现,存放在helper.java 文件中
package utils;
public class Helper {
public static String getInfo() { return "Helper类的实现";
}}
*/
/**
* 类的修饰符:
* public,公共(任何人都能调用包中的类)。
* default,只能在当前包中被调用。
*/
public class ObjectOriented {
/**
* 类成员修饰符:
* public,公共,所有的只要有权限访问类,类中的成员都可以访问到。
* private,私有,只允许自己类调用。
* protected,同一个包 或 子类可以访问(即使没有在同一个包内,也可以访问父类中的受保护成员)。
* default,只能在同一个包内访问。
* @param args
*/
public static void main(String[] args) {
/// 2. 静态成员、构造函数、函数重载
// 静态成员,无需实例化就可以指定调用。
System.out.println(Person.email); // xxx@xxx.com
Person.showData(); // 静态方法
// 非静态成员需实例化才可以指定调用。
Person p1 = new Person(); // 构造方法1
Person p2 = new Person("cisco", 73); // 构造方法2
Person p3 = new Person("cisco1", "cisc@sb.com"); // 构造方法3
p1.doSomething(); // 重载函数1 cisco
p1.doSomething("你好呀,"); // 重载函数1 你好呀,-cisco
p2.doSomething();
p2.doSomething("你好呀,");
p3.doSomething();
p3.doSomething("你好呀,");
/// 3. 抽象类、继承、接口、多态
Son1 v1 = new Son1();
v1.stop(); // Son1.stop()
Son2 v2 = new Son2();
v2.stop(); // Son2.stop()
Base v3 = v1; // 让父类型的引用变量指向多种类型的子类型对象,实现多态的另一个条件
v3.stop(); // Son1.stop()
Base v4 = v2; // 让父类型的引用变量指向多种类型的子类型对象,实现多态的另一个条件
v4.stop(); // Son2.stop()
Wechat v11 = new Wechat();
v11.send(); // Wechat.send() 发送微信
DingDing v22 = new DingDing();
v22.send(); // DingDing.send() 发送钉钉
IMessage v33 = v11;
v33.send(); // Wechat.send() 发送微信
IMessage v44 = v22;
v44.send(); // DingDing.send() 发送钉钉
Iphone v55 = new Iphone();
v55.send(); // Iphone.send() 发送短信
v55.call(); // Iphone.call() 打电话
IMessage v555 = v55;
v555.send(); // Iphone.send() 发送短信
ICall v5555 = v55;
v5555.call(); // Iphone.call() 打电话
/// 4.内部类:
// 成员内部类(实例内部类):定义在类中的类,即没有被static修饰的内部类。可以访问外部类的所有成员变量和方法。
Outer1 out1 = new Outer1();
Outer1.MemberInner in1 = out1.new MemberInner();
in1.display();
// 静态内部类:定义在类中的静态类,即被static修饰的内部类。可以直接访问外部类的静态成员变量和方法
Outer2.StaticInner in2 = new Outer2.StaticInner();
in2.display(); // x = 10
// 局部内部类,指的是在方法或一个作用域内里面定义的类,它只能在该方法或该作用域内使用。
Outer3 out4 = new Outer3();
out4.method(); // x = 10
// 匿名内部类:没有类名的局部内部类,通常用于创建一个只使用一次的类。,常用于实现接口或继承父类。比较常见的可以放在方法里面,也可以类里面
// 匿名内部类的优点是简化了代码结构,减少了类的数量,并且易于使用。但是,由于它们没有名字,因此不能在多个地方重用。
// Outer4 test = new Outer4(); // java: Outer4是抽象的; 无法实例化
Outer4 s1 = new Outer4() { // 这里并不是实例化抽象类Outer4,抽象类是不能实例化的。而是定义并创建了一个匿名的类,继承抽象类Outer4的实例对象
@Override
public void display() {
System.out.println("x = " + x);
}
};
s1.display(); // x = 10
Outer5 s2 = new Outer5() { // 定义并创建了一个匿名的类,实现了接口Outer5的实例对象
@Override
public void display() {
System.out.println("x = " + x);
}
};
s2.display(); // x = 10
}
}
class Person {
// 静态变量
public static String email = "xxx@xxx.com";
// 实例变量
public String name;
public Integer age;
// 构造方法1:无参构造方法
public Person() {
this.name = "cisco";
this.age = 99999;
}
// 构造方法2
public Person(String name, Integer age) {
this.name = name;
this.age = age;
this.email = "xxx@live.com";
}
// 构造方法3
public Person(String name, String email) {
this.name = name;
this.age = 83;
this.email = email;
}
// 定义方法(重载)
public void doSomething() {
System.out.println(this.name);
}
// 定义方法(重载)
public void doSomething(String prev) {
String text = String.format("%s-%s", prev, this.name);
System.out.println(text);
}
// 普通方法
public void showInfo(){
System.out.println("普通方法");
}
// 静态方法
public static void showData(){
System.out.println("静态方法");
}
}
/**
* 抽象类,被abstract关键字修饰的类,不能实例化
* 抽象类里面可以没有抽象方法,但有抽象方法,该类必须用abstract进行修饰
*/
abstract class Base {
String member; // 抽象类和接口的区别1:抽象类可以有成员变量,而接口中只能有常量
// 抽象方法(约束子类中必须有这个方法)
public abstract void play(String name);
// 普通方法
// 抽象类和接口的区别2:抽象类可以实现公共方法和定义抽象方法,而接口只能定义抽象方法。
public void stop() {
System.out.println("Son.stop()");
}
// 抽象类和接口的区别3:接口中的方法默认是public abstract,而抽象类中的方法默认是protected
void funcDefaultAttribute() {
System.out.println("Son.default()");
}
}
/**
* Son继承抽象类Base。
* class Son extends Base,Base1{ // 这种写法不支持,java不支持继承多个类
* 如果子类不是抽象类,但要继承的父类是抽象类,则必须实现抽象方法
* 如果不想实现父类的抽象方法,可以使用abstract进行修饰,声明子类是抽象类
*/
class Son1 extends Base{
// 子类不是抽象类,但要继承的父类是抽象类,则必须实现抽象方法
public void play(String name){
System.out.println("Son1.play()");
}
// 子类Son1中重写了父类Base的stop函数,这是实现多态的的条件之一
// 另一个条件是:让父类型的引用变量指向多种类型的子类型对象
public void stop() {
System.out.println("Son1.stop()");
}
}
class Son2 extends Base{
// 子类不是抽象类,但要继承的父类是抽象类,则必须实现抽象方法
public void play(String name){
System.out.println("Son2.play()");
}
// 子类Son中重写了父类Base的stop函数,这是实现多态的的条件之一
// 另一个条件是:让父类型的引用变量指向多种类型的子类型对象
public void stop() {
System.out.println("Son2.stop()");
}
}
/**
* 定义接口的关键字interface,定义了IMessag接口,接口是特殊的抽象类
*/
interface IMessage {
final String member1 = "接口只能有常量"; // 抽象类和接口的区别1:抽象类可以有成员变量,而接口中只能有常量
// 抽象类和接口的区别2:抽象类可以实现公共方法和定义抽象方法,而接口只能定义抽象方法。
public void send();
/*
抽象类和接口的区别3:接口中的方法默认是public abstract,而抽象类中的方法默认是protected
void funcDefaultAttribute(); */
}
/**
* 定义接口的关键字interface,定义了ICall接口
*/
interface ICall {
public void call();
}
/**
* 实现接口的关键字implements,Wechat类"实现"了Imessage接口
*/
class Wechat implements IMessage {
// Wechat中重写了实现了Imessage接口,这是实现多态的的条件之一
// 另一个条件是:让父类型的引用变量指向多种类型的子类型对象
public void send() {
System.out.println("Wechat.send() 发送微信");
}
}
/**
* 实现接口的关键字implements,DingDing类"实现"了Imessage接口
*/
class DingDing implements IMessage {
public void send() {
System.out.println("DingDing.send() 发送钉钉");
}
}
/**
* 抽象类和接口的区别4:java中只能单继承(一个类只能继承一个父类),但一个类可以实现多个接口
* Iphone类实现了IMessage,ICall接口
*/
class Iphone implements IMessage,ICall{
public void send() {
System.out.println("Iphone.send() 发送短信");
}
public void call() {
System.out.println("Iphone.call() 打电话");
}
}
// 实例内部类(成员内部类):定义在类中的类,即没有被static修饰的内部类。
class Outer1 {
int x = 10;
class MemberInner {
void display() {
System.out.println("x = " + x); // 可以访问外部类的所有成员变量和方法。
}
}
}
// 静态内部类:定义在类中的静态类,即被static修饰的内部类。
class Outer2 {
static int x = 10;
static class StaticInner {
void display() {
System.out.println("x = " + x); // 可以直接访问外部类的静态成员变量和方法
}
}
}
// 局部内部类,指的是在方法或一个作用域内里面定义的类,它只能在该方法或该作用域内使用。
class Outer3 {
int x = 10;
public void method() {
class LocalInner { // 指的是在方法或一个作用域内里面定义的类
void display() {
System.out.println("x = " + x);
}
}
LocalInner in = new LocalInner();
in.display(); // 它只能在该方法或该作用域内使用。
}
}
// 匿名内部类:没有类名的局部内部类,通常用于创建一个只使用一次的类。通常用于简化代码编写,常用于实现接口或继承父类。比较常见的可以放在方法里面,也可以类里面
abstract class Outer4 {
int x = 10;
public abstract void display();
}
interface Outer5 {
final int x = 10;
public void display();
}
反射、注解、异常
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
public class ReflectionAnnotation {
/// 1. 异常捕获,反射操作失败会抛出异常,这里需要捕获一下异常
public static void main(String[] args) throws InstantiationException, IllegalAccessException, NoSuchMethodException, InvocationTargetException, NoSuchFieldException {
/// 2.反射
/*
Java中的反射是一种用于在运行时获取和操作类、接口、对象和包的能力。
反射可以让我们在运行时访问类中的属性和方法,并且可以动态地创建实例。 反射的主要用途是在运行时获取对象的类型,并且在不了解对象类型的情况下调用对象的方法。
*/ /// 2.1 使用反射获取类的三种方式
// 第一种:Class.forName
//class<?> clazz = class.forName( "com.example.helloworld"); // 第二种:直接类名.class
// <?> 是通配符,表示不确定类型,相当于 "class <? extends Object>"。这样定义的 clazz2 对象可以表示任何类型的 Class 对象。
Class<?> clazz = Person1.class; // 将类Person1的类类型赋值给类变量clazz2 。
// 第三种:通过对象获取类
// class<?> clazz3 = new MainActivity().getclass();
System.out.println(clazz.getName()); // 输出:Person1
/// 2.2 使用反射创建类的实例化对象
// Person1 person = (Person1) clazz.newInstance(); // 会报错:指的是在使用反射创建类实例化对象时,类没有默认构造函数或者没有公共的构造函数。
// 可以创建实例的时候,同时用构造函数初始化
Constructor constructor = clazz.getConstructor(String.class, int.class);
Person1 person = (Person1) constructor.newInstance("cisco", 18);
/// 2.3 使用反射调用方法
// getConstructor(返回指定公开的构造方法)、getConstructors(返回所有公开的构造方法)
// getDeclaredConstructor(返回指定的构造方法) 、getDeclaredConstructors(返回所有构造方法)
Constructor[] constructors = clazz.getConstructors();
System.out.println(constructors.length); // 输出:1
// getMethod(返回指定公开的方法)、getMethods(返回所有公开的方法)
// getDeclaredMethod(返回指定的方法)、getDeclaredMethods(返回所有的方法)
Method sayHelloMethod = clazz.getMethod("sayHello");
sayHelloMethod.invoke(person); // 输出:Hello, my name is cisco and I am 18 years old.
/// 2.4 反射获取字段
//获取该类所有公开的字段
Field[] fields = clazz.getFields();
System.out.println(constructors.length); // 输出:1
// 获取类中指定公开的字段,如果字段被private修饰,下面的代码获取字段会报错
Field field1 = clazz.getField("name");
String name = (String) field1.get(person);
System.out.println(name); // 输出:cisco
//获取类里面的指定私有字段
Field field2 = clazz.getDeclaredField("age" );
field2.setAccessible(true); // 设置为可以访问
int age = (int) field2.get(person);
System.out.println(age); // 输出:18
//获取类里面的所有字段
Field[] fields2 = clazz.getDeclaredFields();
System.out.println(constructors.length); // 输出:1
// 3.3 我们可以使用反射来读取注解,并基于它来执行特定操作,比如运行所有带有 @TestAnnotation 注解的方法
Method[] methods = MyTestClass.class.getMethods(); // 获取MyTestClass类中的所有公开的方法
for (Method method : methods) {
TestAnnotation testAnnotation = method.getAnnotation(TestAnnotation.class);
if (testAnnotation != null) {
System.out.println("Running test: " + testAnnotation.value());
method.invoke(new MyTestClass());
}
}
/*
输出:
Running test: Test Method 1 invoke testMethod1 Running test: Test Method 2 invoke testMethod2 */
}
}
// 定义一个Person类
class Person1 {
public String name;
private int age;
public Person1(String name, int age) {
this.name = name;
this.age = age;
}
public void sayHello() {
System.out.println("Hello, my name is " + name + " and I am " + age + " years old.");
}
}
/// 3.注解
/*
Java中的注解是一种元数据,可以在编译时或运行时为类、方法、属性等元素添加额外的信息。
注解的主要用途是通过反射来获取这些额外的信息。在Hook的时候把这些信息打印出来有助于我们逆向。
@Override注解来标识某个方法是重写自父类的,这样编译器就可以检测出在子类中是否存在与父类同名的方法,如果不存在,编译器就会抛出错误。
使用@Deprecated注解来标识某个方法已经过时,编译器会在编译时给出警告信息。
也可以自定义注解
注解还有很多其他用途,例如在编译时进行类型检查、生成文档、配置等。
*/// 3.1 假设我们有一个 @TestAnnotation 注解,用于标记可测试的方法
@Retention(RetentionPolicy.RUNTIME)
@interface TestAnnotation {
String value() default "";
}
// 3.2 然后在我们的类中使用这个注解
class MyTestClass {
@TestAnnotation("Test Method 1")
public void testMethod1() {
System.out.println("invoke testMethod1");
}
@TestAnnotation("Test Method 2")
public void testMethod2() {
System.out.println("invoke testMethod2");
}
}
多线程
import java.util.concurrent.*;
public class MultiThreaded {
public static void main(String[] args){
Thread t = new Thread(new MyTask(1)); // 用这个MyTask类的实例创建了一个Thread对象
t.start(); // 启动新线程
try {
Thread.sleep(1000); // 等待1s后
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
t.interrupt(); // 调用 myThread.interrupt() 来终止线程
// 使用Executor框架来创建线程和管理线程池
/*
ExecutorService的submit()方法和executor.execute()方法的主要区别在于返回值。
submit()方法返回一个Future对象,可以通过这个对象来检查任务的状态,并在任务完成后获取返回值。
Future对象支持取消任务、检查任务是否完成、获取任务结果等操作。
而execute()方法只是向线程池提交了一个任务,不会返回任何结果。
*/ Executor executor = Executors.newFixedThreadPool(3); // 创建了一个线程池,大小为3
executor.execute(new MyTask(11));
executor.execute(new MyTask(22));
executor.execute(new MyTask(33));
executor.execute(new MyTask(44)); // 线程池大小为3,但是向其中提交了4个任务,可能会导致最后一个任务无法立即执行,需要等待其它任务完成之后才能执行
/*
输出:
start 线程:1
stop 线程:1
start 线程:11
start 线程:22
start 线程:33
*/ }
}
class MyTask implements Runnable {
public int num;
public MyTask(int arg){
num = arg;
}
public void run() {
// 执行任务的代码
System.out.println("start 线程:" + num);
// 使用 while 循环来检查中断标记位,如果被设置了中断标记位,则线程会结束自己。
while(!Thread.currentThread().isInterrupted()) {
// do some work
// ... try {
Thread.sleep(100);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
System.out.println("stop 线程:" + num);
}
}
}
}
/*
使用标志变量来控制线程的终止。
在线程的代码中定义一个标志变量,在线程运行时检查这个标志变量的值,
如果标志变量的值为 true,那么线程就可以自行终止。
class MyThread extends Thread {
private volatile boolean running = true;
public void run() {
while (running) {
// do some work
}
}
public void stopRunning() {
running = false;
}
}
*/
// 对于只使用1次的线程,可以简写,直接定义和创建线程,并执行线程
new Thread() {
@Override
public void run() {
// do something
}
}.start();
二、Java常见加密
逆向出算法之后,用加密工具先跑一边得到密文再与抓包的数据对比,没问题之后再动手用python去还原
加密工具工具: https://icyberchef.com/
1. 隐藏字节
原理
// 除了隐藏参数sign,还可以隐藏MD5加密盐、AES加密key、iv; public class Hello { public static void main(String[] args){ TreeMap<String,String> v1 = new TreeMap<String,String>(); v1.put("sign" , "12356"); // 这种情况,java层逆向时搜索关键字 sign 可以找到代码中对应的位置 // 但如果将字符串sign变成字节数组,就会隐藏起来了(java层逆向时搜索关键字 sign 找不到代码中对应的位置) // String a = new String(new byte[]{115, 105, 103, 110}); // v1.put(a , "12356"); v1.put("xxx","proxy"); v1.put("bs", "info"); // [ ("sign","123456"),("xxx","proxy" ),("bs ", "info")] // 发送请求的时候要先拼接成字符串 StringBuilder sb = new StringBuilder(); for(Map.Entry<String,String> entry : v1.entrySet()) { String key = entry.getKey(); String value = entry.getValue(); sb.append(key); sb.append("="); sb.append(value); sb.append("&"); } sb.deleteCharAt(sb.length()-1); // 把最后的'&'去掉 System.out.println(sb.toString()); // sign=12356&bs=info&xxx=proxy }
#### 破解
- 假设:现在有一个app关键字定位不到位置,接下来可以做什么尝试
- hook验证
```javascript
// 破解方法1:找到java代码中的TreeMap类的put方法,hook替换成我自己的方法newPut。
// hook的js代码
function newPut(key,value){
console.log(key,value); // 获取到转换之后的值,判断key是否是sign关键字然后输出调用栈,从而确定sign在代码中的位置
}
// 破解方法2:找到StringBuilder的append或toString,hook替换成自己的方法。
function newappend(data){
// 判断 data 中是否包含关键字+输出调用栈
this.append(data);
}
- 还原字符串
- #坑/python/语法 java的字节表示范围和python的字节表示范围不一样::需要进行转换
# Java中"sign"的字节数组 # String v1 = new String(new byte[]{115, 105, 103, 110});
- #坑/python/语法 java的字节表示范围和python的字节表示范围不一样::需要进行转换
用python将Java的字节数组还原成字符串
1. Java的字节数组->python字节列表
byte_list = [115, 105, 103, 110, -1]
print(type(byte_list[0])) # <class 'int'>
2. 字节列表->字符串
方法1:
string = ''.join(chr(i) for i in byte_list) # ValueError: chr() arg not in range(0x110000)
Java 中的字节是有符号的(-128 ~ 127,当>127会怎么样,溢出变成了负数,128-255的ascii码为扩展 ASCII 码主要用于表示一些特殊字符和符号,但是由于其表示的字符集有限,并且在不同地区有不同的含义) @todo 还是说清楚,有点含糊,有空再回来梳理
而Python 中的字节是无符号的(0-255)。如果字节列表中的元素不在0-255之间,可能会出现错误。
所以在处理字节列表时,如果列表中的项为负数,为了使其能够在python的字节的范围内(0-255),需要将其加上256。
比如-1 + 256 = 255。-1和255的二进制表示是一样的:-1 的二进制表示是 11111111(补码),255的二进制表示是 11111111(原码),在计算机中存的都是1111 1111,计算机根据数据类型的不同,将同一个二进制数值解读成-1和255而已.
这里加上256(这里+256的时候还是按照有符号数的规则去看待byte_list中的元素(有符号的int))
相当于取了一个模256的余数,也就是 & 0xff的效果(截8bit的长度的数据,这8bit会被当做无符号数去解读)
模的直观表现就是圈
-1 0 1
( )
-127 -128 127
#
Java 0 1 2 3 4 .. 127 -128 -127 -126 ...-3 -2 -1
Python 0 1 2 3 4 .. 127 128 129 130 ...253 254 255
string = ''.join(chr(i) for i in [(x & 0xff) for x in byte_list]) # 列表推导式和 (x & 0xff) print(string) # signÿ
string = ''.join(map(chr, byte_list)) # ValueError: chr() arg not in range(0x110000)
string = ''.join(map(chr, [(x & 0xff) for x in byte_list]))
print(string) # signÿ
方法2:
2.1 字节列表 -> python的字节数组
bytes 是不可变的,它的值一旦创建就不能更改。这意味着,你不能使用索引赋值操作符来更改某个字节的值,也不能使用切片赋值操作符来更改字节数组中的一段字节。
bytearray 是可变的,它的值可以在创建后进行修改。这意味着,你可以使用索引赋值操作符来更改某个字节的值,也可以使用切片赋值操作符来更改字节数组中的一段字节。
bytes_array = bytearray(byte_list)
直接将字节列表 -> python的字节数组,但这里还需要对负数进行处理
bytes_array = bytearray()
for item in byte_list:
item = item & 0xff # item<0时,让item+256
bytes_array.append(item)
print(bytes_array) # bytearray(b'sign\xff')
2.2 python的字节数组 -解码-> 字符串
str_data = bytes_array.decode('utf-8')
print(str_data) # UnicodeDecodeError: 'utf-8' codec can't decode byte 0xff in position 4: invalid start byte
python 字符串->字节数组
方法1:用encode()
string = "sign" # 字符串
byte_array = string.encode('utf-8') # 字符串 -编码-> 字节数组
print(byte_array) # b'sign'
方法2:用bytearray()
string = "sign"
byte_array = bytes(string, "utf-8")
bytes_list = list(bytes(string, "utf-8"))
print(byte_array) # b'sign'
print(bytes_list) # [115, 105, 103, 110]
### 2. uuid
- 抖音:uuid
- ![](https://cisco-1306627320.cos.ap-guangzhou.myqcloud.com/Mardown/image-20211012191034675.png)
- ![](https://cisco-1306627320.cos.ap-guangzhou.myqcloud.com/Mardown/image-20211012191034675.png)
#### 原理1
- 抓包发现`每次`请求的uuid都`不一样`。
- 大概率是随机生成的
- 比如:`info = 3d2337a3-77da-41d0-8db9-741a7ccb3b84`
```java
// 用java随机生成uuid的原理
import java.util.UUID;
public class Hello {
public static void main(String[] args){
String uid = UUID.randomUUID().toString(); // 破解方法:通过HOOK toString这个方法,把调用栈打印输出,找到代码的位置
System.out.println(uid);
}
}
破解
- 不用逆向uuid的生成算法了,直接随机生成uuid来还原uuid
# 用python随机生成uuid来还原uuid import uuid # uuid.uuid4() # UUID('3d2337a3-77da-41d0-8db9-741a7ccb3b84') uuid = str(uuid.uuid4()) print(uuid) # 3d2337a3-77da-41d0-8db9-741a7ccb3b84
原理2
- 抓包发现uuid是
固定
的,但重装app
之后,uuid发生了变化
。 - 第一次运行app
- 调用uuid算法生成UUID 。
- 把uuid写入XML文件
- 以后运行app
- 优先去XML文件中找
- 如果找不到,调用uuid算法生成UUID。把uuid写入XML文件
破解
- 这种情况也是不用逆向uuid的生成算法,可以尝试直接随机生成uuid来伪造uuid。
- 同上
原理3
- 抓包发现uuid是
固定
的,重装app
之后,uuid也没有发生变化
。 - 首次运行app
- 获取手机指纹信息(CPU、硬盘、各种ID、各种信息)
- 发送后端API,设备注册(建立一个
硬件信息
->UUID
的映射,保存到服务器上) - 返回给手机端app
- 以后运行app
- 获取手机指纹信息(CPU、硬盘、各种ID、各种信息)
- 发送后端API,获取到UUID
- 返回给手机端app
破解
- 伪造手机的硬件信息,把伪造的硬件信息发送过去 @Todo
实战:抖音uuid
3. 随机值
原理
- 情况1:抖音:openudid (数字 + 26个英文字母 ->16进制字符串 )
// java层生成随机字符串的方法 import java.math.BigInteger; import java.security.SecureRandom;
public class Hello {
public static void main(String[] args) {
BigInteger v4 = new BigInteger(80, new SecureRandom()); // 随机生成80位,10个字节
String res = v4.toString(16); // 让字节以16进制展示 可以Hook toString 看输出的字符串是否和抓包的一致,进而可以确定确定生成随机字符串的代码位置再打印调用栈就可以顺藤摸瓜找到调用它的地方 @Todo
System.out.println(res); // d7decba6d59fb91c1e30
}}
- **情况2**:数字 + ABCDEF ->16进制字符串(绝大多数)
- ![](https://cisco-1306627320.cos.ap-guangzhou.myqcloud.com/Mardown/20230130140532.png)
#### 破解
```python
# 用python还原随机字符串
import random
data = random.randbytes(10) # pytho3.9 才有这个方法
# data = [random.randint(0, 255) for i in range(10)] # python3.8
print(data) # b'\xea\x11\xf4\xc9\xc2-\t\x08*S'
print([item for item in data]) # [234, 17, 244, 201, 194, 45, 9, 8, 42, 83]
# 这是一个 Python 中的列表推导式,它会生成一个新的列表。这个推导式包含了三个部分:
# 1.`for item in data`: 这是一个循环,它会对 data 中的每个元素执行后面的操作。
# 2.`hex(item)`: 这是一个内置函数,它会将参数(item)转换成十六进制字符串。
# 3.`[2:]`: 这是一个切片操作,它会截取字符串的第三个字符之后的部分。
# for item in data 每次从data中取出1Byte,然后用hex(item)将10进制转换成16进制,再用[2:]切片,去掉前两位"0x",最后把每一项组成一个新的列表.等价于
# ele_list = []
# for item in data:
# ele = hex(item)[2:] # 或者ele = "%02x" % item
# ele_list.append(ele)
print([hex(item)[2:] for item in data]) # ['ea', '11', 'f4', 'c9', 'c2', '2d', '9', '8', '2a', '53']
print("".join([hex(item)[2:] for item in data])) # ea11f4c9c22d982a53
# rjust(2, "0")不满2位,用0去填充
print([hex(item)[2:].rjust(2, "0") for item in data]) # ['ea', '11', 'f4', 'c9', 'c2', '2d', '09', '08', '2a', '53']
print("".join([hex(item)[2:].rjust(2, "0") for item in data])) # ea11f4c9c22
# 随机生成一个长度为10Byte的十六进制的字符串最终可以简写为
data = "".join([ hex(item)[2:].rjust(2,"0") for item in random.randbytes(10)]) # pyton3.9
data = "".join([ hex(item)[2:].rjust(2,"0") for item in [random.randint(0, 255) for i in range(10)]]) # pyton3.8
# 更简单的生成16进制字符串方法: % 字符串格式化。 `%d % format f-string `
# 其中 %02x 是格式化字符串的一部分,其中 % 是格式化字符串的开始,02是设置字符串长度为两位,如果不够两位会在前面补0, x是转换成16进制字符串。
# "%02x" % var 这段代码就是将 var 这个变量转换成了2位的16进制字符串
data = "".join(["%02x" % item for item in random.randbytes(10)]) # pyton3.9
data = "".join(["%02x" % item for item in [random.randint(0, 255) for i in range(10)]]) # pyton3.8
4. 时间戳
原理
- 抖音:ticket
public class Hello {
public static void main(String[] args) {
String t1 = String.valueOf(System.currentTimeMillis() / 1000); // 秒
String t2 = String.valueOf(System.currentTimeMillis()); // 毫秒
System.out.println(t1);
System.out.println(t2);
}
}
破解
import time
v1 = int(time.time()) # 1673941738
v2 = int(time.time()*1000) # 1673941738547 毫秒
v3 = str(int(time.time())) # 1673941738 请求头需要的是字符串,不然会报错
v4 = str(int(time.time()*1000)) # 1673941738547
5. md5加密
- 情况1:每次发送POST请求时,抖音都会携带一些请求头:
X-SS-STUB = "fjaku9asdf"
- 读取请求体中的数据,对请求体中的数据进行md5加密。
- 情况2:反编译app看有没有使用md5加密
- 关键字没有搜索到。
解决办法
:去Hook Java中的可能是MD5算法的代码(观察这部分MD5的代码有没有被触发,进而确定加密的代码位置,同时也可以把加密前的参数打印出来)。原理
#坑/java/字节数组 nameBytes是字节数组, System.out.println(nameBytes) 会输出这个数组的地址,而不是数组中的具体内容:: 要用System.out.println(Arrays.toString(nameBytes));才行import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.Arrays;
public class md5_salt {
public static void main(String[] args) throws NoSuchAlgorithmException{
String salt = "xxxxxx";
String password = "cisco";
MessageDigest instance = MessageDigest.getInstance("MD5");
instance.update((password + salt).getBytes()); // 加盐
byte[] nameBytes = instance.digest();
System.out.println(nameBytes); // [B@255316f2 nameBytes是字节数组, System.out.println(nameBytes) 会输出这个数组的地址,而不是数组中的具体内容。
System.out.println(Arrays.toString(nameBytes)); // [-16, 100, 96, 16, -89, -111, -5, -25, 125, -80, 123, -35, -93, -12, -13, 10] 将一个数组转换为一个字符串,并用逗号将它们分隔开
System.out.println(new String(nameBytes)); // �d`����}�{ݣ�� 将字节数组转换为字符串对象, 每个字节将被转换为对应的字符。在转换时,默认使用的字符集是系统的默认字符集
// 可以hook digest()方法来确定这个md5加密是否被触发执行,同时把传入的参数的明文和加密后的密文(与抓包的密文对比)打印出来,进而能确定该代码位置是否为加密参数的MD5加密算法
// js hook代码
// MessageDigest.digest = function(body){
// consle.log(字节转换字符串输出);
// var res = this.digest(body);
// consle.log(res字节转换字符串输出);
// return res;
// }
// 十六进制展示
StringBuilder sb = new StringBuilder(); // StringBuilder是Java中的一个类,它提供了一种可变的字符序列。与Java中的String类不同,String是不可变的,所以在对字符串进行大量修改时,使用StringBuilder会更有效率。
for(int i=0;i<nameBytes.length;i++){
int val = nameBytes[i] & 255; // nameBytes[i]<0时,效果等同于让nameBytes[i]+256
if (val<16){
sb.append("0");
}
sb.append(Integer.toHexString(val)); // 将整数值转换为一个十六进制字符串
}
String hexData = sb.toString();
System.out.println(hexData); // f0646010a791fbe77db07bdda3f4f30a
}
}
#### 破解
```python
import hashlib
salt = "xxxxxx"
password = "cisco"
m = hashlib.md5() # 创建一个 md5 哈希对象
m.update((password + salt).encode("utf-8")) # 加盐
v1 = m.digest()
print(v1) # b'\xf0d`\x10\xa7\x91\xfb\xe7}\xb0{\xdd\xa3\xf4\xf3\n'
# java中没有这个功能。m.hexdigest()会得到一个16进制的哈希
v2 = m.hexdigest()
print(v2) # f0646010a791fbe77db07bdda3f4f30a
6. sha-256加密
原理
- B站:x/report/andriod2,请求体
import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.Arrays;
public class sha256 {
public static void main(String[] args) throws NoSuchAlgorithmException{
String salt = "xxxxxx";
String password = "cisco";
MessageDigest instance = MessageDigest.getInstance("SHA-256");
instance.update((password + salt).getBytes()); // 加盐
byte[] nameBytes = instance.digest();
System.out.println(nameBytes); // [B@41906a77 nameBytes是字节数组, System.out.println(nameBytes) 会输出这个数组的地址,而不是数组中的具体内容。
System.out.println(Arrays.toString(nameBytes)); // [115, -41, 17, 24, -1, 33, -67, -124, -99, 32, 116, -76, -4, 77, 16, -52, -69, -113, 109, -47, -86, -4, -82, -15, 17, 95, -39, 26, -35, -107, 9, 91] 将一个数组转换为一个字符串,并用逗号将它们分隔开
System.out.println(new String(nameBytes)); // s��!��� t��M̻�mѪ���_�ݕ [ 将字节数组转换为字符串对象, 每个字节将被转换为对应的字符。在转换时,默认使用的字符集是系统的默认字符集
// 可以hook digest()方法来确定这个md5加密是否被触发执行,同时把传入的参数的明文和加密后的密文(与抓包的密文对比)打印出来,进而能确定该代码位置是否为加密参数的MD5加密算法
// js hook代码
// MessageDigest.digest = function(body){
// consle.log(字节转换字符串输出);
// var res = this.digest(body);
// consle.log(res字节转换字符串输出);
// return res;
// }
// 十六进制展示
StringBuilder sb = new StringBuilder(); // StringBuilder是Java中的一个类,它提供了一种可变的字符序列。与Java中的String类不同,String是不可变的,所以在对字符串进行大量修改时,使用StringBuilder会更有效率。
for(int i=0;i<nameBytes.length;i++){
int val = nameBytes[i] & 255; // nameBytes[i]<0时,效果等同于让nameBytes[i]+256
if (val<16){
sb.append("0");
}
sb.append(Integer.toHexString(val)); // 将整数值转换为一个十六进制字符串
}
String hexData = sb.toString();
System.out.println(hexData); // 73d71118ff21bd849d2074b4fc4d10ccbb8f6dd1aafcaef1115fd91add95095b
}
}
#### 破解
```python
import hashlib
salt = "xxxxxx"
password = "cisco"
m = hashlib.sha256() # 创建一个 sha256 哈希对象
m.update((password + salt).encode("utf-8")) # 加盐
v1 = m.digest()
print(v1) # b's\xd7\x11\x18\xff!\xbd\x84\x9d t\xb4\xfcM\x10\xcc\xbb\x8fm\xd1\xaa\xfc\xae\xf1\x11_\xd9\x1a\xdd\x95\t['
# java中没有这个功能。m.hexdigest()会得到一个16进制的哈希
v2 = m.hexdigest()
print(v2) # 73d71118ff21bd849d2074b4fc4d10ccbb8f6dd1aafcaef1115fd91add95095b
7. AES加密
原理
- 情况1: AES密文(0100100) --> 字节(抓包看起来像乱码)
requests.post( url="?", data="字节" )
- 情况2:
AES密文
+base64编码
+十六进制
--> 字符串(抓包看起来像乱码) - 情况3:
AES密文
+gzip压缩
->字节(看起来像乱码) - 刷B站播放时,发送POST请求。
- AES加密(请求体中的数据) -> 密文(key & iv 加密)。
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.io.UnsupportedEncodingException;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;
import java.util.Base64;
public class aes {
public static void main(String[] args) throws Exception {
String data = "cisco";
String key = "fd6b639dbcff0c2a1b03b389ec763c4b";
String iv = "77b07a672d57d64c";
// 加密
byte[] raw = key.getBytes();
SecretKeySpec skeySpec = new SecretKeySpec(raw, "AES"); // 举一反三:以后不确定是不是AES加密,可以hook SecretKeySpec看传入的参数是否包含"AES",进而验证了app是使用AES加密
IvParameterSpec ivSpec = new IvParameterSpec(iv.getBytes());
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, skeySpec, ivSpec);
byte[] encrypted = cipher.doFinal(data.getBytes());
System.out.println(Arrays.toString(encrypted)); // [99, -17, -126, 52, -74, 103, -10, -126, 9, -117, -86, 94, 1, 10, 114, -39]
}
}
破解
#坑/python/库/pycryptodome 安装了pycryptodome还是报错ModuleNotFoundError: No module named 'Crypto::不用命令行安装,去在pycharm上面去安装,问题依旧未解决@todo
# pip install pycryptodome
# pycryptodome 是 pycrypto 的一个分支,主要用于解决 pycrypto 的一些问题和提供更多的功能。它提供了许多对称密钥算法(AES, DES, Blowfish, CAST, ARC4, ...)、
# 非对称密钥算法(RSA, DSA, ECC, ElGamal, ...)、散列算法(MD5, SHA-1, SHA-2, ...)等。
# pycryptodome 可以用来加密和解密文件和数据,生成和验证数字签名,生成和验证消息认证码,生成随机数等。
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad
KEY = "fd6b639dbcff0c2a1b03b389ec763c4b"
IV = "77b07a672d57d64c"
def aes_encrypt(data_string):
aes = AES.new(key=KEY.encode('utf-8'), mode=AES.MODE_CBC, iv=IV.encode('utf-8'))
raw = pad(data_string.encode('utf-8'), 16)
return aes.encrypt(raw)
data = aes_encrypt("cisco")
print(data)
print([i for i in data])
8. base64编码
原理
import java.util.Base64;
public class base64 {
public static void main(String[] args) {
String name = "cisco";
// 加密
Base64.Encoder encoder = Base64.getEncoder();
String res = encoder.encodeToString(name.getBytes());
System.out.println(res); // "Y2lzY28="
// 解密
Base64.Decoder decoder = Base64.getDecoder();
byte[] origin = decoder.decode(res);
String data = new String(origin);
System.out.println(data); // cisco
}
}
破解
import base64
name = "cisco"
res = base64.b64encode(name.encode('utf-8'))
print(res) # b'Y2lzY28='
data = base64.b64decode(res)
origin = data.decode('utf-8')
print(origin) # "cisco"
# 不同,换行符 + == @todo
9. gzip压缩
原理
- 抖音注册设备:设备。
- 注册设备:生成一些值(cdid、手机型号、手机品牌....) ,后端读取到时候,发现cdid是一个全新的请求。那么抖音就会生成
device_id、install_id、tt
(cdid、手机型号、手机品牌....) --> gzip压缩(字节) --> 加密 --> 密文
- 注册设备:生成一些值(cdid、手机型号、手机品牌....) ,后端读取到时候,发现cdid是一个全新的请求。那么抖音就会生成
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.math.BigInteger;
import java.security.SecureRandom;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.io.OutputStream;
import java.util.zip.GZIPInputStream;
import java.util.zip.GZIPOutputStream;
public class gzip {
public static void main(String[] args) throws IOException {
// 压缩
String data = "cisco";
System.out.println(Arrays.toString(data.getBytes())); // [99, 105, 115, 99, 111]
ByteArrayOutputStream v0_1 = new ByteArrayOutputStream();
GZIPOutputStream v1 = new GZIPOutputStream((v0_1));
v1.write(data.getBytes()); // 将字符串"cisco"转换为字节数组,然后将字节数组数据写入 v1 中进行压缩。
v1.close();
byte[] arg6 = v0_1.toByteArray(); // 获取压缩后的字节数组
System.out.println(Arrays.toString(arg6)); // [31, -117, 8, 0, 0, 0, 0, 0, 0, -1, 75, -50, 44, 78, -50, 7, 0, -83, -48, -12, -101, 5, 0, 0, 0]
// 解压缩
ByteArrayOutputStream out = new ByteArrayOutputStream();
ByteArrayInputStream in = new ByteArrayInputStream(arg6); // 将压缩后的字节数组写入ungzip中进行解压缩。
GZIPInputStream ungzip = new GZIPInputStream(in);
byte[] buffer = new byte[256];
int n;
while ((n = ungzip.read(buffer)) >= 0) {
out.write(buffer, 0, n);
}
byte[] res = out.toByteArray();
System.out.println(Arrays.toString(res)); // [99, 105, 115, 99, 111]
System.out.println(out.toString("UTF-8")); // cisco
}
}
破解
import gzip
# 压缩
s_in = "ciso".encode('utf-8')
s_out = gzip.compress(s_in)
print([i for i in s_out])
# java、Python语言区别。(个别字节是不同,不影响整个的结果),。
# python: [31, 139, 8, 0, 51, 18, 200, 99, 2, 255, 75, 206, 44, 206, 7, 0, 162, 33, 206, 150, 4, 0, 0, 0]
# java: [31, -117, 8, 0, 0, 0, 0, 0, 0, -1, 75, -50, 44, 78, -50, 7, 0, -83, -48, -12, -101, 5, 0, 0, 0]
# 解压缩
res = gzip.decompress(s_out)
print(res) # b'ciso'
print(res.decode('utf-8')) # ciso