
本公众号分享的所有技术仅用于学习交流,请勿用于其他非法活动,如果错漏,欢迎留言指正
《Java加密与解密的艺术》 c语言的实现请参照之前发布的这篇文章:加解密快速入门
java快速入门
一、java基础
.java->.class
- 不记得概念,回头看即可
- 遇到陌生的api,查JDK_API文档即可
java变量、 数据类型、条件语句、循环语句
1import java.util.Scanner; 2import java.util.Arrays; 3/** 4 * 对这个类进行注释 5 * Main.java 6 * 类名:首字母大写且驼峰式命名,例如:Main、UserInfo、PersonApplication 7 * 类修饰符:public、default(不写) 8 * 一个文件中最多只能有一个public类 且文件名必须和public类名一致。 9 * 如果文件中有多个类,文件名与public类名一致。 10 * 如果文件中有多个类且无public类,文件名可以是任意类名。 11 */ 12public class Main { 13 /** 14 * 对这个方法进行注释 15 * 主函数必须要是main 16 * 方法名: 首字母小写且驼峰式命名,后面的每一个单词的首字母大写。例如:getName() 17 * 类中成员修饰符:public、private、protected、default(不写) 18 * void表示方法没有返回值。如果有返回则必须在定义方式就要写明他是返回的什么类型。 19 * @param args 字符数组 20 */ 21 public static void main(String[] args) { 22 String name = ""; // 单行注释,变量 23 int age = 24; 24 final char gender = 'M'; // 常量 25 26 // 输出 27 System.out.print("please input name:"); // 尾部不加换行 please input name:cisco // 输入 28 Scanner input = new Scanner(System.in); 29 name = input.nextLine(); 30 System.out.println(name + ',' + gender + ','+ age); // 尾部自动加换行 cisco,M,24 31 32 // 静态成员,无需实例化就可以指定调用。 33 ControlFlow.switchFun(gender); // man 34 // 非静态成员需实例化才可以指定调用。 35 ControlFlow obj = new ControlFlow(); 36 obj.ifFun(age); // 大叔 37 38 obj.whileFun(); 39 /* 40 多行注释 41 0,执行中... 42 1,执行中... 43 2,执行中... 44 3,退出while... 45 */ 46 obj.doWhileFun(); 47 /* 48 3,执行中... do-while至少执行一次do{}。即使一开始就不满足条件((count = 3) < 3),也会执行一次do{}里面的内容 49 4,退出do-while... 50 */ 51 obj.forFun(); 52 /* 53 0 1 2 3 4 continue 6 7 8 9 */ 54 /* 整数类型 55 byte,字节 【1字节】表示范围:-128 ~ 127 即:-2^7 ~ 2^7 -1 56 short,短整型 【2字节】表示范围:-32768 ~ 32767 57 int,整型 【4字节】表示范围:-2147483648 ~ 2147483647 58 long,长整型 【8字节】表示范围:-9223372036854775808 ~ 9223372036854775807 59 */ DataType obj2 = new DataType(); 60 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 61 62 /* 字符:'a','b','c' 63 字符串: "abc",字符串是由多个字符串组成。在Java中,字符串是由码点组成的,而不是由字节组成的。 64 由于网络传输或存储需要,把unicode编码的字符串压缩成UTF-8、GBK编码的字节数组 65 */ obj2.stringToByte(); 66 /* 67 [-42, -48, -50, -60, -41, -42, -73, -5] GBK编码 68 [-28, -72, -83, -26, -106, -121, -27, -83, -105, -25, -84, -90] UTF-8编码 69 */ 70 // 字符串的定义 71 String v11 = "中文字符"; 72 System.out.println(v11); // 中文字符 73 String v12 = new String("中文字符"); 74 System.out.println(v12); // 中文字符 75 String v13 = new String(new byte[]{-28, -72, -83, -26, -106, -121, -27, -83, -105, -25, -84, -90}); 76 System.out.println(v13); // 中文字符 77 try { 78 String v14 = new String(new byte[]{-42, -48, -50, -60, -41, -42, -73, -5}, "GBK"); 79 System.out.println(v14); // 中文字符 80 } catch (Exception e) { 81 82 } 83 String v15 = new String( new char[]{'中', '文', '字', '符'} ) ; 84 System.out.println(v15); // 中文字符 85 86 // 字符串的方法 87 String origin = "cisco 字符串 使用方法"; 88 char v1 = origin.charAt(6); // 指定字符 89 System.out.println(v1); // 字 90 int len = origin.length(); // 长度 91 System.out.println(len); // 14 92 for (int i = 0; i < len; i++) { 93 char item = origin.charAt(i); 94 } 95 96 String v2 = origin.trim(); // 去除空白 97 System.out.println(v2); // cisco 字符串 使用方法 98 String v3 = origin.toLowerCase(); // 小写 99 System.out.println(v3); // cisco 字符串 使用方法 100 String v4 = origin.toUpperCase(); // 大写 101 System.out.println(v4); // CISCO 字符串 使用方法 102 String[] v5 = origin.split("字"); // 分割 103 System.out.println(v5); // [Ljava.lang.String;@5197848c 104 System.out.println(Arrays.toString(v5)); // [cisco , 符串 使用方法] 105 String v6 = origin.replace("o", "i"); // 替换 106 System.out.println(v6); // cisci 字符串 使用方法 107 String v7 = origin.substring(2, 6); // 子字符串=切片 [2:6] System.out.println(v7); // "sco " 108 boolean v8 = origin.equals("cisco 字符串 使用方法"); // "alex是个大DB" "alex是个大SB" 109 System.out.println(v8); // false 110 boolean v9 = origin.contains("co"); 111 System.out.println(v9); // false 112 boolean v10 = origin.startsWith("c"); 113 System.out.println(v10); // false 114 String v100 = origin.concat("哈哈哈"); // 末尾追加 115 System.out.println(v100); // cisco 字符串 使用方法哈哈哈 116 117 // 字符串拼接 118 StringBuilder sb = new StringBuilder(); // StringBuffer是多线程安全的 119 sb.append("name"); 120 sb.append("="); 121 sb.append("cisco"); 122 sb.append("&"); 123 sb.append("age"); 124 sb.append("="); 125 sb.append("18"); 126 System.out.println(sb); // name=cisco&age=18 127 System.out.println(sb.toString()); // name=cisco&age=18 128 129 /* 数组,存放固定长度的元素。容器+特定类型+固定长度(数组一旦创建个数就不可调整) 130 */ // [123,1,999] int[] numArray = new int[3]; 131 numArray[0] = 123; 132 numArray[1] = 1; 133 numArray[2] = 99; 134 System.out.println(Arrays.toString(numArray)); // [123, 1, 99 135 136 String[] names = new String[]{"cisco", "123", "456"}; // 使用 "new" 关键字来创建数组对象,并在括号中指定数组的大小和初始值。 137 System.out.println(Arrays.toString(names)); // [cisco, 123, 456] 138 139 String[] nameArray = {"cisco", "123", "456"}; // 使用 "{" 和 "}" 来创建数组对象,并在括号中指定数组的初始值,不需要使用 "new" 关键字。 140 System.out.println(Arrays.toString(nameArray)); // [cisco, 123, 456] 141 142 // 索引 143 // nameArray[0] 144 // nameArray.length for (int idx = 0; idx < nameArray.length; idx++) { 145 String item = nameArray[idx]; 146 } 147 148 /* 149 object 在Python中每个类都默认继承Object类(所有的类都是Object的子类)。 150 在Java所有的类都是默认继承Object类。 151 */ // 用基类可以泛指他的子类的类型。 152 String v00 = "cisco"; 153 System.out.println(v00); // cisco 154 System.out.println(v00.getClass()); // class java.lang.String 155 Object v01 = new String("cisco"); 156 System.out.println(v01); // cisco 157 System.out.println(v01.getClass()); // class java.lang.String 158 159 // 声明数组,数组中元素必须int类型; 160 int[] v02 = new int[3]; 161 162 // 声明数组,数组中元素必须String类型; 163 String[] v03 = new String[3]; 164 165 // 想要声明的数组中想要是混合类型,就可以用Object来实现。数组中可以是int/String类型; 166 Object[] v04 = new Object[3]; 167 v04[0] = 123; 168 v04[1] = "cisco"; 169 System.out.println(Arrays.toString(v04)); // [123, cisco, null] 170 171 // 类型判断 172 TypeOf.func(123); // 整型 173 TypeOf.func("123"); // 字符串类型 174 } 175} 176 177class ControlFlow{ 178 public void ifFun(int age){ 179 // 条件1:if 180 if (age < 18) { 181 System.out.println("少年"); 182 } else if (age < 40) { 183 System.out.println("大叔"); 184 } else { 185 System.out.println("老汉"); 186 } 187 } 188 // 静态成员,无需实例化就可以指定调用。 189 public static void switchFun(char gender){ 190 // 条件2:switch 191 switch (gender) { 192 case 'F': 193 System.out.println("female"); 194 break; // 退出{},不再执行后续的代码 195 case 'M': 196 System.out.println("man"); 197 break; default: 198 System.out.println("unknown"); 199 break; } 200 } 201 public void whileFun(){ 202 // 循环1:while 203 int count = 0; 204 while (count < 3) { 205 System.out.println(count + ",执行中..."); 206 count += 1; 207 } 208 System.out.println(count + ",退出while..."); 209 } 210 211 public void doWhileFun(){ 212 // 循环2:do-while 213 int count = 3; 214 do { 215 System.out.println(count+",执行中..."); 216 count += 1; 217 } while (count < 3); 218 System.out.println(count + ",退出do-while..."); 219 } 220 221 public void forFun(){ 222 // 循环3:for 223 for (int i = 0; i < 10; i++) { 224 if(i == 5) { 225 System.out.println("continue"); 226 continue; // 单次退出{},这次循环内不再执行后续的代码 227 } 228 System.out.println(i); 229 } 230 } 231 232} 233 234class DataType{ 235 public void byteToString() { 236 // 1.字节数组(转换为Unicode编码的字符串) [字节,字节,字节] 237 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}; 238 // byte[] dataList = {97, 105, 100, 61}; // aid= 这不是unicode编码啊,是ascii编码 @todo 239 String dataString = new String(dataList); 240 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 241 } 242 public void stringToByte() { 243 // 2.Unicode编码的字符串->其他编码格式的字节数组 244 try { 245 // Python中的 name.encode("gbk") String name = "中文字符"; 246 byte[] v1 = name.getBytes("GBK"); 247 System.out.println(Arrays.toString(v1)); // [-42, -48, -50, -60, -41, -42, -73, -5] 248 249 // Python中的 name.encode("utf-8") byte[] v2 = name.getBytes("UTF-8"); 250 System.out.println(Arrays.toString(v2)); // [-28, -72, -83, -26, -106, -121, -27, -83, -105, -25, -84, -90] 251 } catch (Exception e) { 252 253 } 254 } 255} 256 257 258class TypeOf{ 259 public static void func(Object v1) { 260 // System.out.println(v1); 261 // System.out.println(v1.getClass()); if (v1 instanceof Integer) { 262 System.out.println("整型"); 263 } else if (v1 instanceof String) { 264 System.out.println("字符串类型"); 265 } else { 266 System.out.println("未知类型"); 267 } 268 } 269}
应用数据类型与基本数据类型之间的转换
- 应用数据类型(或称为包装类型)是对基本数据类型的封装,应用数据类型是
对象,而基本数据类型是值。- 抽象类
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,而基本数据类型则没有默认值。 - 可以使用应用数据类型作为
泛型参数,而不能使用基本数据类型。 - 应用数据类型对象可以更容易地在
集合中存储和检索。
- 使用应用数据类型可以避免
1public class PackingDataType { 2 public static void main(String[] args) { 3 /* 4 java中的应用数据类型(引用类型)和基本数据类型可以通过自动装箱和拆箱进行转换。 5 自动装箱:将基本类型转换为对应的应用数据类型,例如int转换为Integer。 6 自动拆箱:将应用数据类型转换为对应的基本类型,例如Integer转换为int。 7 */ int inta = 1; 8 System.out.println(inta); // 1 9 // System.out.println(inta.getClass()); // java: 无法取消引用int 10 Integer IntegerA = inta; 11 System.out.println(IntegerA); 12 System.out.println(IntegerA.getClass()); // class java.lang.Integer 13 int inta1 = IntegerA; 14 System.out.println(inta1); // 1 15 // System.out.println(inta1.getClass()); // java: 无法取消引用int 16 17 /* 在 Java 5 之前使用包装类的构造函数进行装箱操作效率较低, 18 建议使用包装类的静态方法进行转换,如Integer.valueOf() 和 Integer.intValue() */ int a = 5; 19 Integer A = Integer.valueOf(a); // int 转 Integer: 使用 Integer.valueOf(int i) 或者 new Integer(int i) int a1 = A.intValue(); // Integer 转 int: 使用 Integer.intValue() 20 21 long b = 10; 22 Long B = Long.valueOf(b); // long 转 Long: 使用 Long.valueOf(long l) 或者 new Long(long l) long b1 = B.longValue(); // Long 转 long: 使用 Long.longValue() 23 24 double c = 3.14; 25 Double C = Double.valueOf(c); // double 转 Double: 使用 Double.valueOf(double d) 或者 new Double(double d) double c1 = C.doubleValue(); // Double 转 double: 使用 Double.doubleValue() 26 27 char d = 'a'; 28 Character D = Character.valueOf(d); // char 转 Character: 使用 new Character(char c) char d1 = D.charValue(); // Character 转 char: 使用 Character.charValue() 29 // 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() } 30}
- 这三个类是 Java 中用来操作字符串的工具类。
StringBuffer: 用于构建可变的字符串,可以对字符串进行增删改操作。由于其线程安全,所以在多线程环境下可以使用。StringBuilder: 用于构建可变的字符串,也可以对字符串进行增删改操作。但是不保证线程安全,因此在单线程环境下效率更高。Array: 数组是一种容器,用于存储固定数量的同类型的元素,因此可以用来存储大量的数据。Array 中的各种方法,如 sort、search、copy、fill 等,可以帮助你管理和处理数组中的数据。
Java中List、Set、Map
1import java.util.ArrayList; 2import java.util.LinkedList; 3import java.util.Iterator; // Java 中用于遍历集合元素的迭代器接口 4import java.util.HashSet; 5import java.util.TreeSet; 6import java.util.HashMap; 7import java.util.TreeMap; 8import java.util.Map; 9import java.util.Set; 10public class ComplexType { 11 12 public static void main(String[] args) { 13 /// 1. List 14 /* List是一个接口,接口下面有两个常见的类型(目的是可以存放动态的多个数据) 15 ArrayList,连续的内存地址的存储(内部自动扩容)。 16 LinkedList,底层基于链表实现(自行车链条)。 17 Java中接口,是用来约束实现他的类。比如约束他里面的成员必须有add。 18 interface List{ public void add(Object data); // 接口中的方法,不写具体的实现,只用于约束。 19 } 20 // 类ArrayList实现了接口List,此时这个类就必须有一个add方法。 21 class ArrayList implements List{ public void add(Object data){ // 将数据data按照连续存储的方法放在内存。 22 // .. } } 23 // 类LinkedList实现了接口List,此时这个类就必须有一个add方法。 24 class LinkedList implements List{ public void add(Object data){ // 将数据data按照链表的形式存储 25 // .. } } */ 26 // ArrayList,默认内部存放的是混合数据类型。 27 // ArrayList<String> data = new ArrayList<String>(); // 指定数据类型String 28 // ArrayList<Object> data = new ArrayList<Object>(); 29 ArrayList data = new ArrayList(); 30 data.add("cisco1"); 31 data.add("cisco2"); 32 data.add(666); 33 data.add("cisco3"); 34 35 36 // String value = data.get(1); // java: 不兼容的类型: java.lang.Object无法转换为java.lang.String 37 String value = (String) data.get(1); 38 System.out.println(value); // cisco2 39 40 Object temp = data.get(1); 41 String value1 = (String) temp; 42 System.out.println(value1); // cisco2 43 44 int xo = (int) data.get(2); 45 System.out.println(xo); // 666 46 47 data.set(0, "哈哈哈哈"); 48 System.out.println(data); // [哈哈哈哈, cisco2, 666, cisco3] 49 50 data.remove("cisco2"); // [666, cisco3] 51 data.remove(0); 52 System.out.println(data); 53 54 int size = data.size(); 55 System.out.println(size); // 2 56 57 boolean exists = data.contains("cisco3"); 58 System.out.println(exists); // true 59 60 61 for (int i = 0; i < data.size(); i++) { 62 Object item = data.get(i); 63 System.out.println(item); // 666 cisco3 64 } 65 66 for (Object item : data) { // 迭代器 67 System.out.println(item); // 666 cisco3 68 } 69 70 Iterator it = data.iterator(); // 迭代器 71 while (it.hasNext()) { 72 // String item = (String) it.next(); // ArrayList data中存在不是String的元素666,迭代器返回的元素不是字符串类型,将迭代器遍历出来的元素强制转换为字符串时就会抛出ClassCastException异常。 73 Object item = it.next(); 74 System.out.println(item); // 666 cisco3 75 } 76 77 78 LinkedList<Integer> v1 = new LinkedList<Integer>(); // int的链表 79 v1.add(11); 80 v1.add(22); 81 System.out.println(v1); // [11, 22] 82 83 LinkedList<Object> v2 = new LinkedList<Object>(); 84 v2.add("cisco1"); 85 v2.add("cisco2"); 86 v2.add(666); 87 v2.add(123); 88 System.out.println(v2); // [cisco1, cisco2, 666, 123] 89 90 v2.remove(1); 91 v2.remove("666"); 92 System.out.println(v2); // [cisco1, 666, 123] 93 94 v2.set(2, "xxx"); 95 v2.push("哈哈哈"); 96 v2.addFirst(11); 97 System.out.println(v2); // [11, 哈哈哈, cisco1, 666, xxx] 98 99 100 for (int i = 0; i < v2.size(); i++) { 101 Object item = v2.get(i); 102 System.out.println(item); // 11 哈哈哈 cisco1 666 xxx } 103 104 for (Object item : v2) { 105 System.out.println(item); // 11 哈哈哈 cisco1 666 xxx } 106 107 Iterator it1 = data.iterator(); // 迭代器 108 while (it.hasNext()) { 109 Object item = it1.next(); 110 System.out.println(item); // 11 哈哈哈 cisco1 666 xxx } 111 112 113 /// 2. Set 114 /* Set是一个接口,常见实现这个接口的有两个类,用于实现不重复的多元素集合。 115 HashSet,去重,无序。 116 TreeSet,去重,内部默认排序(ascii、unicode)【不同的数据类型,无法进行比较】。 117 */ // HashSet s1 = new HashSet(); // Set s1 = new HashSet(); // HashSet<String> s1 = new HashSet<String>(); HashSet s1 = new HashSet(); 118 s1.add("P站"); 119 s1.add("B站"); 120 s1.add("A站"); 121 s1.add("P站"); 122 s1.add(666); 123 s1.remove("A站"); 124 System.out.println(s1); // [B站, P站, 666] 125 126 boolean exist = s1.contains("B站"); 127 System.out.println(exist); // true 128 129 // s2 = {"东京热","东北热","南京热"} 130 HashSet s2 = new HashSet(){ 131 { 132 add("东京热"); 133 add("东北热"); 134 add("南京热"); 135 } 136 }; 137 System.out.println(s2); // [东京热, 南京热, 东北热] 138 139 s2.addAll(s1); 140 System.out.println(s2); // [东京热, B站, P站, 南京热, 666, 东北热] 141 s2.retainAll(s1); // 交集 & System.out.println(s2); // [B站, P站, 666] 142 143 s2.addAll(s1); // 并集 | System.out.println(s2); // [B站, P站, 666] 144 145 s2.removeAll(s1); // 差集 s1 - s2 System.out.println(s2); // [] 146 147 // Set s2 = new TreeSet(); // TreeSet<String> s2 = new TreeSet<String>(); TreeSet s3 = new TreeSet(); 148 s3.add("P站"); 149 s3.add("B站"); 150 s3.add("A站"); 151 s3.add("P站"); 152 // s3.add(666); // 不可以,不同的数据类型,无法进行比较 153 154 System.out.println(s3); // [A站, B站, P站] 155 156 TreeSet s4 = new TreeSet(){ 157 { 158 add("P站"); 159 add("B站"); 160 add("A站"); 161 add("P站"); 162 } 163 }; 164 System.out.println(s4); // [A站, B站, P站] 165 166 for (Object item : s4) { 167 System.out.println(item); // A站 B站 P站 168 } 169 170 Iterator it11 = s4.iterator(); 171 while (it11.hasNext()) { 172 Object item = it11.next(); 173 System.out.println(item); // A站 B站 P站 174 } 175 176 /// 3.Map 177 /* Map是一个接口,常见实现这个接口的有两个类,用于存储键值对。 178 HashMap,无序。 179 TreeMap,默认根据key排序。(常用) 180 */ 181 HashMap h1 = new HashMap(); 182 h1.put("name","cisco"); 183 h1.put("age",18); 184 h1.put("gender","男"); 185 System.out.println(h1); // {gender=男, name=cisco, age=18} 186 h1.remove("age"); 187 System.out.println(h1); // {gender=男, name=cisco} 188 int size1 = h1.size(); 189 System.out.println(size1); // 2 190 Object value11 = h1.get("name"); // 不存在,返回null 191 System.out.println(value11); // cisco 192 boolean existsKey = h1.containsKey("age"); 193 System.out.println(existsKey); // false 194 boolean existsValue = h1.containsValue("cisco"); 195 System.out.println(existsValue); // true 196 h1.replace("name", "cc"); 197 System.out.println(h1); // {gender=男, name=cc} 198 199 // 循环: 示例1 200 Set<Map.Entry<String, String>> s11 = h1.entrySet(); // 使用h1.entrySet()方法获取所有键值对并将它们存储在一个set中。 201 System.out.println(s11); // [gender=男, name=cc] 202 Iterator it111 = s11.iterator(); 203 while (it111.hasNext()) { 204 // ("name", "cisco") 205 Map.Entry<String, String> entry = (Map.Entry<String, String>) it111.next(); 206 String k = (String) entry.getKey(); 207 String v = (String) entry.getValue(); 208 System.out.println(k + ':' + v); // gender:男 name:cc } 209 // 循环: 示例2 210 Set s22 = h1.entrySet(); 211 Iterator it222 = s22.iterator(); 212 while (it222.hasNext()) { 213 Map.Entry entry = (Map.Entry) it222.next(); 214 String k = (String) entry.getKey(); 215 String v = (String) entry.getValue(); 216 System.out.println(k + ':' + v); // gender:男 name:cc } 217 218 HashMap<String,String> h2 = new HashMap<String,String>(); 219 h2.put("name","cisco"); 220 h2.put("age","18"); 221 h2.put("gender","男"); 222 System.out.println(h2); // {gender=男, name=cisco, age=18} 223 224 225 HashMap<String,String> h3 = new HashMap<String,String>(){ 226 { 227 put("name","cisco"); 228 put("age","18"); 229 put("gender","男"); 230 } 231 }; 232 System.out.println(h3); // {gender=男, name=cisco, age=18} 233 234 TreeMap t1 = new TreeMap(); // 改为了TreeMap 235 t1.put("name","cisco"); 236 t1.put("age","18"); 237 t1.put("gender","男"); 238 System.out.println(t1); // {age=18, gender=男, name=cisco} 239 240 TreeMap<String,String> t2 = new TreeMap<String,String>(); 241 t2.put("name","cisco"); 242 t2.put("age","18"); 243 t2.put("gender","男"); 244 System.out.println(t2); // {age=18, gender=男, name=cisco} 245 246 Map t3 = new TreeMap(); 247 t3.put("name","cisco"); 248 t3.put("age",18); 249 t3.put("gender","男"); 250 System.out.println(t3); // {age=18, gender=男, name=cisco} 251 252 // 循环: 示例3 253 for (Object entry : t1.entrySet()) { 254 Map.Entry<String, Object> entryMap = (Map.Entry<String, Object>) entry; 255 String k = entryMap.getKey(); 256 Object v = entryMap.getValue(); 257 System.out.println(k + ':' + v); // age:18 gender:男 name:cisco } 258 259 // 循环: 示例4 260 for (Map.Entry<String, String> entry : t2.entrySet()) { 261 String k = entry.getKey(); 262 String v = entry.getValue(); 263 System.out.println(k + ':' + v); // age:18 gender:男 name:cisco } 264 265 } 266}
- List

- Set

- Map


1在Python中需要自己处理key排序的问题。 2v4 = { 3 "aid":123, "xx":999, "wid":888} 4 5# 1.根据key进行排序 6# data = ["{}={}".format(key,v4[key]) for key in sorted(v4.keys())] 7# 2.再进行拼接 8# result = "&".join(data) 9 10result = "&".join(["{}={}".format(key,v4[key]) for key in sorted(v4.keys())])
包的概念、静态成员、构造函数、函数重载、抽象类、继承、接口、多态、内部类
1/* 2/// 1.包的概念 3import utils.Helper; // 导入utils 包中的Helper类 4 5文件目录结构 6src 7├── ObjectOriented.java 8└── utils 9 └── Helper.java 10// Helper类的实现,存放在helper.java 文件中 11package utils; 12 13public class Helper { 14 public static String getInfo() { return "Helper类的实现"; 15 }} 16 */ 17 18/** 19 * 类的修饰符: 20 * public,公共(任何人都能调用包中的类)。 21 * default,只能在当前包中被调用。 22 */ 23public class ObjectOriented { 24 /** 25 * 类成员修饰符: 26 * public,公共,所有的只要有权限访问类,类中的成员都可以访问到。 27 * private,私有,只允许自己类调用。 28 * protected,同一个包 或 子类可以访问(即使没有在同一个包内,也可以访问父类中的受保护成员)。 29 * default,只能在同一个包内访问。 30 * @param args 31 */ 32 public static void main(String[] args) { 33 34 /// 2. 静态成员、构造函数、函数重载 35 // 静态成员,无需实例化就可以指定调用。 36 System.out.println(Person.email); // xxx@xxx.com 37 Person.showData(); // 静态方法 38 39 // 非静态成员需实例化才可以指定调用。 40 Person p1 = new Person(); // 构造方法1 41 Person p2 = new Person("cisco", 73); // 构造方法2 42 Person p3 = new Person("cisco1", "cisc@sb.com"); // 构造方法3 43 44 p1.doSomething(); // 重载函数1 cisco 45 p1.doSomething("你好呀,"); // 重载函数1 你好呀,-cisco 46 47 p2.doSomething(); 48 p2.doSomething("你好呀,"); 49 50 p3.doSomething(); 51 p3.doSomething("你好呀,"); 52 /// 3. 抽象类、继承、接口、多态 53 Son1 v1 = new Son1(); 54 v1.stop(); // Son1.stop() 55 Son2 v2 = new Son2(); 56 v2.stop(); // Son2.stop() 57 Base v3 = v1; // 让父类型的引用变量指向多种类型的子类型对象,实现多态的另一个条件 58 v3.stop(); // Son1.stop() 59 Base v4 = v2; // 让父类型的引用变量指向多种类型的子类型对象,实现多态的另一个条件 60 v4.stop(); // Son2.stop() 61 62 Wechat v11 = new Wechat(); 63 v11.send(); // Wechat.send() 发送微信 64 DingDing v22 = new DingDing(); 65 v22.send(); // DingDing.send() 发送钉钉 66 IMessage v33 = v11; 67 v33.send(); // Wechat.send() 发送微信 68 IMessage v44 = v22; 69 v44.send(); // DingDing.send() 发送钉钉 70 Iphone v55 = new Iphone(); 71 v55.send(); // Iphone.send() 发送短信 72 v55.call(); // Iphone.call() 打电话 73 IMessage v555 = v55; 74 v555.send(); // Iphone.send() 发送短信 75 ICall v5555 = v55; 76 v5555.call(); // Iphone.call() 打电话 77 78 79 /// 4.内部类: 80 // 成员内部类(实例内部类):定义在类中的类,即没有被static修饰的内部类。可以访问外部类的所有成员变量和方法。 81 Outer1 out1 = new Outer1(); 82 Outer1.MemberInner in1 = out1.new MemberInner(); 83 in1.display(); 84 // 静态内部类:定义在类中的静态类,即被static修饰的内部类。可以直接访问外部类的静态成员变量和方法 85 Outer2.StaticInner in2 = new Outer2.StaticInner(); 86 in2.display(); // x = 10 87 // 局部内部类,指的是在方法或一个作用域内里面定义的类,它只能在该方法或该作用域内使用。 88 Outer3 out4 = new Outer3(); 89 out4.method(); // x = 10 90 // 匿名内部类:没有类名的局部内部类,通常用于创建一个只使用一次的类。,常用于实现接口或继承父类。比较常见的可以放在方法里面,也可以类里面 91 // 匿名内部类的优点是简化了代码结构,减少了类的数量,并且易于使用。但是,由于它们没有名字,因此不能在多个地方重用。 92 // Outer4 test = new Outer4(); // java: Outer4是抽象的; 无法实例化 93 Outer4 s1 = new Outer4() { // 这里并不是实例化抽象类Outer4,抽象类是不能实例化的。而是定义并创建了一个匿名的类,继承抽象类Outer4的实例对象 94 @Override 95 public void display() { 96 System.out.println("x = " + x); 97 } 98 }; 99 s1.display(); // x = 10 100 101 Outer5 s2 = new Outer5() { // 定义并创建了一个匿名的类,实现了接口Outer5的实例对象 102 @Override 103 public void display() { 104 System.out.println("x = " + x); 105 } 106 }; 107 s2.display(); // x = 10 108 109 } 110} 111 112class Person { 113 // 静态变量 114 public static String email = "xxx@xxx.com"; 115 116 // 实例变量 117 public String name; 118 public Integer age; 119 120 // 构造方法1:无参构造方法 121 public Person() { 122 this.name = "cisco"; 123 this.age = 99999; 124 } 125 126 // 构造方法2 127 public Person(String name, Integer age) { 128 this.name = name; 129 this.age = age; 130 this.email = "xxx@live.com"; 131 } 132 133 // 构造方法3 134 public Person(String name, String email) { 135 this.name = name; 136 this.age = 83; 137 this.email = email; 138 } 139 140 // 定义方法(重载) 141 public void doSomething() { 142 System.out.println(this.name); 143 } 144 145 // 定义方法(重载) 146 public void doSomething(String prev) { 147 String text = String.format("%s-%s", prev, this.name); 148 System.out.println(text); 149 } 150 151 // 普通方法 152 public void showInfo(){ 153 System.out.println("普通方法"); 154 } 155 156 // 静态方法 157 public static void showData(){ 158 System.out.println("静态方法"); 159 } 160} 161 162/** 163 * 抽象类,被abstract关键字修饰的类,不能实例化 164 * 抽象类里面可以没有抽象方法,但有抽象方法,该类必须用abstract进行修饰 165 */ 166abstract class Base { 167 String member; // 抽象类和接口的区别1:抽象类可以有成员变量,而接口中只能有常量 168 // 抽象方法(约束子类中必须有这个方法) 169 public abstract void play(String name); 170 // 普通方法 171 // 抽象类和接口的区别2:抽象类可以实现公共方法和定义抽象方法,而接口只能定义抽象方法。 172 public void stop() { 173 System.out.println("Son.stop()"); 174 } 175 // 抽象类和接口的区别3:接口中的方法默认是public abstract,而抽象类中的方法默认是protected 176 void funcDefaultAttribute() { 177 System.out.println("Son.default()"); 178 } 179} 180 181/** 182 * Son继承抽象类Base。 183 * class Son extends Base,Base1{ // 这种写法不支持,java不支持继承多个类 184 * 如果子类不是抽象类,但要继承的父类是抽象类,则必须实现抽象方法 185 * 如果不想实现父类的抽象方法,可以使用abstract进行修饰,声明子类是抽象类 186 */ 187class Son1 extends Base{ 188 // 子类不是抽象类,但要继承的父类是抽象类,则必须实现抽象方法 189 public void play(String name){ 190 System.out.println("Son1.play()"); 191 } 192 // 子类Son1中重写了父类Base的stop函数,这是实现多态的的条件之一 193 // 另一个条件是:让父类型的引用变量指向多种类型的子类型对象 194 public void stop() { 195 System.out.println("Son1.stop()"); 196 } 197} 198 199class Son2 extends Base{ 200 // 子类不是抽象类,但要继承的父类是抽象类,则必须实现抽象方法 201 public void play(String name){ 202 System.out.println("Son2.play()"); 203 } 204 // 子类Son中重写了父类Base的stop函数,这是实现多态的的条件之一 205 // 另一个条件是:让父类型的引用变量指向多种类型的子类型对象 206 public void stop() { 207 System.out.println("Son2.stop()"); 208 } 209} 210 211/** 212 * 定义接口的关键字interface,定义了IMessag接口,接口是特殊的抽象类 213 */ 214interface IMessage { 215 final String member1 = "接口只能有常量"; // 抽象类和接口的区别1:抽象类可以有成员变量,而接口中只能有常量 216 // 抽象类和接口的区别2:抽象类可以实现公共方法和定义抽象方法,而接口只能定义抽象方法。 217 public void send(); 218 219 /* 220 抽象类和接口的区别3:接口中的方法默认是public abstract,而抽象类中的方法默认是protected 221 void funcDefaultAttribute(); */ 222} 223 224/** 225 * 定义接口的关键字interface,定义了ICall接口 226 */ 227interface ICall { 228 public void call(); 229} 230 231/** 232 * 实现接口的关键字implements,Wechat类"实现"了Imessage接口 233 */ 234class Wechat implements IMessage { 235 // Wechat中重写了实现了Imessage接口,这是实现多态的的条件之一 236 // 另一个条件是:让父类型的引用变量指向多种类型的子类型对象 237 public void send() { 238 System.out.println("Wechat.send() 发送微信"); 239 } 240} 241 242/** 243 * 实现接口的关键字implements,DingDing类"实现"了Imessage接口 244 */ 245class DingDing implements IMessage { 246 public void send() { 247 System.out.println("DingDing.send() 发送钉钉"); 248 } 249} 250 251/** 252 * 抽象类和接口的区别4:java中只能单继承(一个类只能继承一个父类),但一个类可以实现多个接口 253 * Iphone类实现了IMessage,ICall接口 254 */ 255class Iphone implements IMessage,ICall{ 256 public void send() { 257 System.out.println("Iphone.send() 发送短信"); 258 } 259 public void call() { 260 System.out.println("Iphone.call() 打电话"); 261 } 262} 263 264// 实例内部类(成员内部类):定义在类中的类,即没有被static修饰的内部类。 265class Outer1 { 266 int x = 10; 267 268 class MemberInner { 269 void display() { 270 System.out.println("x = " + x); // 可以访问外部类的所有成员变量和方法。 271 } 272 } 273} 274 275// 静态内部类:定义在类中的静态类,即被static修饰的内部类。 276class Outer2 { 277 static int x = 10; 278 279 static class StaticInner { 280 void display() { 281 System.out.println("x = " + x); // 可以直接访问外部类的静态成员变量和方法 282 } 283 } 284} 285 286// 局部内部类,指的是在方法或一个作用域内里面定义的类,它只能在该方法或该作用域内使用。 287class Outer3 { 288 int x = 10; 289 public void method() { 290 class LocalInner { // 指的是在方法或一个作用域内里面定义的类 291 void display() { 292 System.out.println("x = " + x); 293 } 294 } 295 LocalInner in = new LocalInner(); 296 in.display(); // 它只能在该方法或该作用域内使用。 297 } 298} 299 300// 匿名内部类:没有类名的局部内部类,通常用于创建一个只使用一次的类。通常用于简化代码编写,常用于实现接口或继承父类。比较常见的可以放在方法里面,也可以类里面 301abstract class Outer4 { 302 int x = 10; 303 public abstract void display(); 304} 305 306interface Outer5 { 307 final int x = 10; 308 public void display(); 309}
反射、注解、异常
1import java.lang.annotation.Retention; 2import java.lang.annotation.RetentionPolicy; 3import java.lang.reflect.Constructor; 4import java.lang.reflect.Field; 5import java.lang.reflect.InvocationTargetException; 6import java.lang.reflect.Method; 7 8public class ReflectionAnnotation { 9 /// 1. 异常捕获,反射操作失败会抛出异常,这里需要捕获一下异常 10 public static void main(String[] args) throws InstantiationException, IllegalAccessException, NoSuchMethodException, InvocationTargetException, NoSuchFieldException { 11 12 13 /// 2.反射 14 /* 15 Java中的反射是一种用于在运行时获取和操作类、接口、对象和包的能力。 16 反射可以让我们在运行时访问类中的属性和方法,并且可以动态地创建实例。 反射的主要用途是在运行时获取对象的类型,并且在不了解对象类型的情况下调用对象的方法。 17 */ /// 2.1 使用反射获取类的三种方式 18 // 第一种:Class.forName 19 //class<?> clazz = class.forName( "com.example.helloworld"); // 第二种:直接类名.class 20 // <?> 是通配符,表示不确定类型,相当于 "class <? extends Object>"。这样定义的 clazz2 对象可以表示任何类型的 Class 对象。 21 Class<?> clazz = Person1.class; // 将类Person1的类类型赋值给类变量clazz2 。 22 // 第三种:通过对象获取类 23 // class<?> clazz3 = new MainActivity().getclass(); 24 System.out.println(clazz.getName()); // 输出:Person1 25 26 /// 2.2 使用反射创建类的实例化对象 27 // Person1 person = (Person1) clazz.newInstance(); // 会报错:指的是在使用反射创建类实例化对象时,类没有默认构造函数或者没有公共的构造函数。 28 29 // 可以创建实例的时候,同时用构造函数初始化 30 Constructor constructor = clazz.getConstructor(String.class, int.class); 31 Person1 person = (Person1) constructor.newInstance("cisco", 18); 32 33 /// 2.3 使用反射调用方法 34 // getConstructor(返回指定公开的构造方法)、getConstructors(返回所有公开的构造方法) 35 // getDeclaredConstructor(返回指定的构造方法) 、getDeclaredConstructors(返回所有构造方法) 36 Constructor[] constructors = clazz.getConstructors(); 37 System.out.println(constructors.length); // 输出:1 38 39 // getMethod(返回指定公开的方法)、getMethods(返回所有公开的方法) 40 // getDeclaredMethod(返回指定的方法)、getDeclaredMethods(返回所有的方法) 41 Method sayHelloMethod = clazz.getMethod("sayHello"); 42 sayHelloMethod.invoke(person); // 输出:Hello, my name is cisco and I am 18 years old. 43 44 /// 2.4 反射获取字段 45 //获取该类所有公开的字段 46 Field[] fields = clazz.getFields(); 47 System.out.println(constructors.length); // 输出:1 48 // 获取类中指定公开的字段,如果字段被private修饰,下面的代码获取字段会报错 49 Field field1 = clazz.getField("name"); 50 String name = (String) field1.get(person); 51 System.out.println(name); // 输出:cisco 52 53 //获取类里面的指定私有字段 54 Field field2 = clazz.getDeclaredField("age" ); 55 field2.setAccessible(true); // 设置为可以访问 56 int age = (int) field2.get(person); 57 System.out.println(age); // 输出:18 58 //获取类里面的所有字段 59 Field[] fields2 = clazz.getDeclaredFields(); 60 System.out.println(constructors.length); // 输出:1 61 62 // 3.3 我们可以使用反射来读取注解,并基于它来执行特定操作,比如运行所有带有 @TestAnnotation 注解的方法 63 Method[] methods = MyTestClass.class.getMethods(); // 获取MyTestClass类中的所有公开的方法 64 for (Method method : methods) { 65 TestAnnotation testAnnotation = method.getAnnotation(TestAnnotation.class); 66 if (testAnnotation != null) { 67 System.out.println("Running test: " + testAnnotation.value()); 68method.invoke(new MyTestClass()); 69 } 70 } 71 /* 72 输出: 73 Running test: Test Method 1 invoke testMethod1 Running test: Test Method 2 invoke testMethod2 */ 74 } 75} 76 77 78// 定义一个Person类 79class Person1 { 80 public String name; 81 private int age; 82 public Person1(String name, int age) { 83 this.name = name; 84 this.age = age; 85 } 86 public void sayHello() { 87 System.out.println("Hello, my name is " + name + " and I am " + age + " years old."); 88 } 89} 90 91 92 93 94 95/// 3.注解 96/* 97Java中的注解是一种元数据,可以在编译时或运行时为类、方法、属性等元素添加额外的信息。 98注解的主要用途是通过反射来获取这些额外的信息。在Hook的时候把这些信息打印出来有助于我们逆向。 99@Override注解来标识某个方法是重写自父类的,这样编译器就可以检测出在子类中是否存在与父类同名的方法,如果不存在,编译器就会抛出错误。 100使用@Deprecated注解来标识某个方法已经过时,编译器会在编译时给出警告信息。 101也可以自定义注解 102注解还有很多其他用途,例如在编译时进行类型检查、生成文档、配置等。 103 */// 3.1 假设我们有一个 @TestAnnotation 注解,用于标记可测试的方法 104@Retention(RetentionPolicy.RUNTIME) 105@interface TestAnnotation { 106 String value() default ""; 107} 108// 3.2 然后在我们的类中使用这个注解 109class MyTestClass { 110 @TestAnnotation("Test Method 1") 111 public void testMethod1() { 112 System.out.println("invoke testMethod1"); 113 } 114 115 @TestAnnotation("Test Method 2") 116 public void testMethod2() { 117 System.out.println("invoke testMethod2"); 118 } 119}
多线程
1import java.util.concurrent.*; 2 3public class MultiThreaded { 4 public static void main(String[] args){ 5 Thread t = new Thread(new MyTask(1)); // 用这个MyTask类的实例创建了一个Thread对象 6 t.start(); // 启动新线程 7 try { 8 Thread.sleep(1000); // 等待1s后 9 } catch (InterruptedException e) { 10 throw new RuntimeException(e); 11 } 12 t.interrupt(); // 调用 myThread.interrupt() 来终止线程 13 14 // 使用Executor框架来创建线程和管理线程池 15 /* 16 ExecutorService的submit()方法和executor.execute()方法的主要区别在于返回值。 17 submit()方法返回一个Future对象,可以通过这个对象来检查任务的状态,并在任务完成后获取返回值。 18 Future对象支持取消任务、检查任务是否完成、获取任务结果等操作。 19 而execute()方法只是向线程池提交了一个任务,不会返回任何结果。 20 */ Executor executor = Executors.newFixedThreadPool(3); // 创建了一个线程池,大小为3 21 executor.execute(new MyTask(11)); 22 executor.execute(new MyTask(22)); 23 executor.execute(new MyTask(33)); 24 executor.execute(new MyTask(44)); // 线程池大小为3,但是向其中提交了4个任务,可能会导致最后一个任务无法立即执行,需要等待其它任务完成之后才能执行 25 /* 26 输出: 27 start 线程:1 28 stop 线程:1 29 start 线程:11 30 start 线程:22 31 start 线程:33 32 */ } 33} 34class MyTask implements Runnable { 35 public int num; 36 public MyTask(int arg){ 37 num = arg; 38 } 39 public void run() { 40 // 执行任务的代码 41 System.out.println("start 线程:" + num); 42 // 使用 while 循环来检查中断标记位,如果被设置了中断标记位,则线程会结束自己。 43 while(!Thread.currentThread().isInterrupted()) { 44 // do some work 45 // ... try { 46 Thread.sleep(100); 47 } catch (InterruptedException e) { 48 Thread.currentThread().interrupt(); 49 System.out.println("stop 线程:" + num); 50 } 51 } 52 } 53 54} 55/* 56使用标志变量来控制线程的终止。 57在线程的代码中定义一个标志变量,在线程运行时检查这个标志变量的值, 58如果标志变量的值为 true,那么线程就可以自行终止。 59class MyThread extends Thread { 60 private volatile boolean running = true; 61 public void run() { 62 while (running) { 63 // do some work 64 } 65 } 66 public void stopRunning() { 67 running = false; 68 } 69} 70 */ 71 72 73// 对于只使用1次的线程,可以简写,直接定义和创建线程,并执行线程 74new Thread() { 75 @Override 76 public void run() { 77 // do something 78 } 79}.start();
二、Java常见加密
- 逆向出算法之后,用加密工具先跑一边得到密文再与抓包的数据对比,没问题之后再动手用python去还原
- 加密工具工具: https://icyberchef.com/
1. 隐藏字节
原理
1// 除了隐藏参数sign,还可以隐藏MD5加密盐、AES加密key、iv; 2public class Hello { 3 public static void main(String[] args){ 4 TreeMap<String,String> v1 = new TreeMap<String,String>(); 5 v1.put("sign" , "12356"); // 这种情况,java层逆向时搜索关键字 sign 可以找到代码中对应的位置 6 // 但如果将字符串sign变成字节数组,就会隐藏起来了(java层逆向时搜索关键字 sign 找不到代码中对应的位置) 7 // String a = new String(new byte[]{115, 105, 103, 110}); 8 // v1.put(a , "12356"); 9 v1.put("xxx","proxy"); 10 v1.put("bs", "info"); 11 // [ ("sign","123456"),("xxx","proxy" ),("bs ", "info")] 12 13 // 发送请求的时候要先拼接成字符串 14 StringBuilder sb = new StringBuilder(); 15 for(Map.Entry<String,String> entry : v1.entrySet()) { 16 String key = entry.getKey(); 17 String value = entry.getValue(); 18 sb.append(key); 19 sb.append("="); 20 sb.append(value); 21 sb.append("&"); 22 } 23 sb.deleteCharAt(sb.length()-1); // 把最后的'&'去掉 24 System.out.println(sb.toString()); // sign=12356&bs=info&xxx=proxy 25} 26
破解
- 假设:现在有一个app关键字定位不到位置,接下来可以做什么尝试
- hook验证
1// 破解方法1:找到java代码中的TreeMap类的put方法,hook替换成我自己的方法newPut。 2// hook的js代码 3function newPut(key,value){ 4 console.log(key,value); // 获取到转换之后的值,判断key是否是sign关键字然后输出调用栈,从而确定sign在代码中的位置 5} 6 7// 破解方法2:找到StringBuilder的append或toString,hook替换成自己的方法。 8function newappend(data){ 9 // 判断 data 中是否包含关键字+输出调用栈 10 this.append(data); 11}
- 还原字符串
- #坑/python/语法 java的字节表示范围和python的字节表示范围不一样::需要进行转换
1# Java中"sign"的字节数组 2# String v1 = new String(new byte[]{115, 105, 103, 110}); 3 4# 用python将Java的字节数组还原成字符串 5# 1. Java的字节数组->python字节列表 6byte_list = [115, 105, 103, 110, -1] 7print(type(byte_list[0])) # <class 'int'> 8# 2. 字节列表->字符串 9# 方法1: 10# string = ''.join(chr(i) for i in byte_list) # ValueError: chr() arg not in range(0x110000) 11# Java 中的字节是有符号的(-128 ~ 127,当>127会怎么样,溢出变成了负数,128-255的ascii码为扩展 ASCII 码主要用于表示一些特殊字符和符号,但是由于其表示的字符集有限,并且在不同地区有不同的含义) @todo 还是说清楚,有点含糊,有空再回来梳理 12# 而Python 中的字节是无符号的(0-255)。如果字节列表中的元素不在0-255之间,可能会出现错误。 13# 所以在处理字节列表时,如果列表中的项为负数,为了使其能够在python的字节的范围内(0-255),需要将其加上256。 14# 比如-1 + 256 = 255。-1和255的二进制表示是一样的:-1 的二进制表示是 11111111(补码),255的二进制表示是 11111111(原码),在计算机中存的都是1111 1111,计算机根据数据类型的不同,将同一个二进制数值解读成-1和255而已. 15# 这里加上256(这里+256的时候还是按照有符号数的规则去看待byte_list中的元素(有符号的int)) 16# 相当于取了一个模256的余数,也就是 & 0xff的效果(截8bit的长度的数据,这8bit会被当做无符号数去解读) 17# 模的直观表现就是圈 18# -1 0 1 19# ( ) 20# -127 -128 127 21# 22# Java 0 1 2 3 4 .. 127 -128 -127 -126 ...-3 -2 -1 23# Python 0 1 2 3 4 .. 127 128 129 130 ...253 254 255 24string = ''.join(chr(i) for i in [(x & 0xff) for x in byte_list]) # 列表推导式和 (x & 0xff) 25print(string) # signÿ 26# string = ''.join(map(chr, byte_list)) # ValueError: chr() arg not in range(0x110000) 27string = ''.join(map(chr, [(x & 0xff) for x in byte_list])) 28print(string) # signÿ 29# 方法2: 30# 2.1 字节列表 -> python的字节数组 31# bytes 是不可变的,它的值一旦创建就不能更改。这意味着,你不能使用索引赋值操作符来更改某个字节的值,也不能使用切片赋值操作符来更改字节数组中的一段字节。 32# bytearray 是可变的,它的值可以在创建后进行修改。这意味着,你可以使用索引赋值操作符来更改某个字节的值,也可以使用切片赋值操作符来更改字节数组中的一段字节。 33# bytes_array = bytearray(byte_list) 34# 直接将字节列表 -> python的字节数组,但这里还需要对负数进行处理 35bytes_array = bytearray() 36for item in byte_list: 37 item = item & 0xff # item<0时,让item+256 38 bytes_array.append(item) 39print(bytes_array) # bytearray(b'sign\xff') 40 41# 2.2 python的字节数组 -解码-> 字符串 42str_data = bytes_array.decode('utf-8') 43print(str_data) # UnicodeDecodeError: 'utf-8' codec can't decode byte 0xff in position 4: invalid start byte 44 45# python 字符串->字节数组 46# 方法1:用encode() 47string = "sign" # 字符串 48byte_array = string.encode('utf-8') # 字符串 -编码-> 字节数组 49print(byte_array) # b'sign' 50 51# 方法2:用bytearray() 52string = "sign" 53byte_array = bytes(string, "utf-8") 54bytes_list = list(bytes(string, "utf-8")) 55print(byte_array) # b'sign' 56print(bytes_list) # [115, 105, 103, 110]
2. uuid
- 抖音:uuid
原理1
- 抓包发现
每次请求的uuid都不一样。 - 大概率是随机生成的
- 比如:
info = 3d2337a3-77da-41d0-8db9-741a7ccb3b84
- 比如:
1// 用java随机生成uuid的原理 2import java.util.UUID; 3 4public class Hello { 5 public static void main(String[] args){ 6 String uid = UUID.randomUUID().toString(); // 破解方法:通过HOOK toString这个方法,把调用栈打印输出,找到代码的位置 7 System.out.println(uid); 8 } 9}
破解
- 不用逆向uuid的生成算法了,直接随机生成uuid来还原uuid
1# 用python随机生成uuid来还原uuid 2import uuid 3# uuid.uuid4() # UUID('3d2337a3-77da-41d0-8db9-741a7ccb3b84') 4uuid = str(uuid.uuid4()) 5print(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进制字符串 )
1// java层生成随机字符串的方法 2import java.math.BigInteger; 3import java.security.SecureRandom; 4 5public class Hello { 6 7 public static void main(String[] args) { 8 BigInteger v4 = new BigInteger(80, new SecureRandom()); // 随机生成80位,10个字节 9 String res = v4.toString(16); // 让字节以16进制展示 可以Hook toString 看输出的字符串是否和抓包的一致,进而可以确定确定生成随机字符串的代码位置再打印调用栈就可以顺藤摸瓜找到调用它的地方 @Todo 10 System.out.println(res); // d7decba6d59fb91c1e30 11 }}
- 情况2:数字 + ABCDEF ->16进制字符串(绝大多数)
破解
1# 用python还原随机字符串 2import random 3 4data = random.randbytes(10) # pytho3.9 才有这个方法 5# data = [random.randint(0, 255) for i in range(10)] # python3.8 6print(data) # b'\xea\x11\xf4\xc9\xc2-\t\x08*S' 7print([item for item in data]) # [234, 17, 244, 201, 194, 45, 9, 8, 42, 83] 8 9# 这是一个 Python 中的列表推导式,它会生成一个新的列表。这个推导式包含了三个部分: 10# 1.`for item in data`: 这是一个循环,它会对 data 中的每个元素执行后面的操作。 11# 2.`hex(item)`: 这是一个内置函数,它会将参数(item)转换成十六进制字符串。 12# 3.`[2:]`: 这是一个切片操作,它会截取字符串的第三个字符之后的部分。 13# for item in data 每次从data中取出1Byte,然后用hex(item)将10进制转换成16进制,再用[2:]切片,去掉前两位"0x",最后把每一项组成一个新的列表.等价于 14# ele_list = [] 15# for item in data: 16# ele = hex(item)[2:] # 或者ele = "%02x" % item 17# ele_list.append(ele) 18 19print([hex(item)[2:] for item in data]) # ['ea', '11', 'f4', 'c9', 'c2', '2d', '9', '8', '2a', '53'] 20print("".join([hex(item)[2:] for item in data])) # ea11f4c9c22d982a53 21 22# rjust(2, "0")不满2位,用0去填充 23print([hex(item)[2:].rjust(2, "0") for item in data]) # ['ea', '11', 'f4', 'c9', 'c2', '2d', '09', '08', '2a', '53'] 24print("".join([hex(item)[2:].rjust(2, "0") for item in data])) # ea11f4c9c22 25 26# 随机生成一个长度为10Byte的十六进制的字符串最终可以简写为 27data = "".join([ hex(item)[2:].rjust(2,"0") for item in random.randbytes(10)]) # pyton3.9 28 29data = "".join([ hex(item)[2:].rjust(2,"0") for item in [random.randint(0, 255) for i in range(10)]]) # pyton3.8 30 31 32# 更简单的生成16进制字符串方法: % 字符串格式化。 `%d % format f-string ` 33# 其中 %02x 是格式化字符串的一部分,其中 % 是格式化字符串的开始,02是设置字符串长度为两位,如果不够两位会在前面补0, x是转换成16进制字符串。 34# "%02x" % var 这段代码就是将 var 这个变量转换成了2位的16进制字符串 35data = "".join(["%02x" % item for item in random.randbytes(10)]) # pyton3.9 36 37data = "".join(["%02x" % item for item in [random.randint(0, 255) for i in range(10)]]) # pyton3.8
4. 时间戳
原理
- 抖音:ticket
1public class Hello { 2 3 public static void main(String[] args) { 4 String t1 = String.valueOf(System.currentTimeMillis() / 1000); // 秒 5 String t2 = String.valueOf(System.currentTimeMillis()); // 毫秒 6 System.out.println(t1); 7 System.out.println(t2); 8 } 9}
破解
1import time 2v1 = int(time.time()) # 1673941738 3v2 = int(time.time()*1000) # 1673941738547 毫秒 4v3 = str(int(time.time())) # 1673941738 请求头需要的是字符串,不然会报错 5v4 = 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));才行
<!--SR:!2023-01-31,3,250-->1import java.security.MessageDigest; 2import java.security.NoSuchAlgorithmException; 3import java.util.Arrays; 4 5public class md5_salt { 6 public static void main(String[] args) throws NoSuchAlgorithmException{ 7 String salt = "xxxxxx"; 8 String password = "cisco"; 9 MessageDigest instance = MessageDigest.getInstance("MD5"); 10 instance.update((password + salt).getBytes()); // 加盐 11 byte[] nameBytes = instance.digest(); 12 System.out.println(nameBytes); // [B@255316f2 nameBytes是字节数组, System.out.println(nameBytes) 会输出这个数组的地址,而不是数组中的具体内容。 13 System.out.println(Arrays.toString(nameBytes)); // [-16, 100, 96, 16, -89, -111, -5, -25, 125, -80, 123, -35, -93, -12, -13, 10] 将一个数组转换为一个字符串,并用逗号将它们分隔开 14 System.out.println(new String(nameBytes)); // �d`����}�{ݣ�� 将字节数组转换为字符串对象, 每个字节将被转换为对应的字符。在转换时,默认使用的字符集是系统的默认字符集 15 // 可以hook digest()方法来确定这个md5加密是否被触发执行,同时把传入的参数的明文和加密后的密文(与抓包的密文对比)打印出来,进而能确定该代码位置是否为加密参数的MD5加密算法 16 // js hook代码 17 // MessageDigest.digest = function(body){ 18 // consle.log(字节转换字符串输出); 19 // var res = this.digest(body); 20 // consle.log(res字节转换字符串输出); 21 // return res; 22 // } 23 // 十六进制展示 24 StringBuilder sb = new StringBuilder(); // StringBuilder是Java中的一个类,它提供了一种可变的字符序列。与Java中的String类不同,String是不可变的,所以在对字符串进行大量修改时,使用StringBuilder会更有效率。 25 for(int i=0;i<nameBytes.length;i++){ 26 int val = nameBytes[i] & 255; // nameBytes[i]<0时,效果等同于让nameBytes[i]+256 27 if (val<16){ 28 sb.append("0"); 29 } 30 sb.append(Integer.toHexString(val)); // 将整数值转换为一个十六进制字符串 31 } 32 String hexData = sb.toString(); 33 System.out.println(hexData); // f0646010a791fbe77db07bdda3f4f30a 34 } 35}
破解
1import hashlib 2 3salt = "xxxxxx" 4password = "cisco" 5 6m = hashlib.md5() # 创建一个 md5 哈希对象 7m.update((password + salt).encode("utf-8")) # 加盐 8v1 = m.digest() 9print(v1) # b'\xf0d`\x10\xa7\x91\xfb\xe7}\xb0{\xdd\xa3\xf4\xf3\n' 10 11# java中没有这个功能。m.hexdigest()会得到一个16进制的哈希 12v2 = m.hexdigest() 13print(v2) # f0646010a791fbe77db07bdda3f4f30a
6. sha-256加密
原理
- B站:x/report/andriod2,请求体
1import java.security.MessageDigest; 2import java.security.NoSuchAlgorithmException; 3import java.util.Arrays; 4 5public class sha256 { 6 public static void main(String[] args) throws NoSuchAlgorithmException{ 7 String salt = "xxxxxx"; 8 String password = "cisco"; 9 MessageDigest instance = MessageDigest.getInstance("SHA-256"); 10 instance.update((password + salt).getBytes()); // 加盐 11 byte[] nameBytes = instance.digest(); 12 System.out.println(nameBytes); // [B@41906a77 nameBytes是字节数组, System.out.println(nameBytes) 会输出这个数组的地址,而不是数组中的具体内容。 13 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] 将一个数组转换为一个字符串,并用逗号将它们分隔开 14 System.out.println(new String(nameBytes)); // s��!��� t��M̻�mѪ���_�ݕ [ 将字节数组转换为字符串对象, 每个字节将被转换为对应的字符。在转换时,默认使用的字符集是系统的默认字符集 15 // 可以hook digest()方法来确定这个md5加密是否被触发执行,同时把传入的参数的明文和加密后的密文(与抓包的密文对比)打印出来,进而能确定该代码位置是否为加密参数的MD5加密算法 16 // js hook代码 17 // MessageDigest.digest = function(body){ 18 // consle.log(字节转换字符串输出); 19 // var res = this.digest(body); 20 // consle.log(res字节转换字符串输出); 21 // return res; 22 // } 23 // 十六进制展示 24 StringBuilder sb = new StringBuilder(); // StringBuilder是Java中的一个类,它提供了一种可变的字符序列。与Java中的String类不同,String是不可变的,所以在对字符串进行大量修改时,使用StringBuilder会更有效率。 25 for(int i=0;i<nameBytes.length;i++){ 26 int val = nameBytes[i] & 255; // nameBytes[i]<0时,效果等同于让nameBytes[i]+256 27 if (val<16){ 28 sb.append("0"); 29 } 30 sb.append(Integer.toHexString(val)); // 将整数值转换为一个十六进制字符串 31 } 32 String hexData = sb.toString(); 33 System.out.println(hexData); // 73d71118ff21bd849d2074b4fc4d10ccbb8f6dd1aafcaef1115fd91add95095b 34 } 35}
破解
1import hashlib 2 3salt = "xxxxxx" 4password = "cisco" 5 6m = hashlib.sha256() # 创建一个 sha256 哈希对象 7m.update((password + salt).encode("utf-8")) # 加盐 8v1 = m.digest() 9print(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[' 10 11# java中没有这个功能。m.hexdigest()会得到一个16进制的哈希 12v2 = m.hexdigest() 13print(v2) # 73d71118ff21bd849d2074b4fc4d10ccbb8f6dd1aafcaef1115fd91add95095b
7. AES加密
原理
- 情况1: AES密文(0100100) --> 字节(抓包看起来像乱码)
1requests.post( 2 url="?", 3 data="字节" 4)
- 情况2:
AES密文+base64编码+十六进制--> 字符串(抓包看起来像乱码) - 情况3:
AES密文+gzip压缩->字节(看起来像乱码) - 刷B站播放时,发送POST请求。
- AES加密(请求体中的数据) -> 密文(key & iv 加密)。


1import javax.crypto.BadPaddingException; 2import javax.crypto.Cipher; 3import javax.crypto.IllegalBlockSizeException; 4import javax.crypto.NoSuchPaddingException; 5import javax.crypto.spec.IvParameterSpec; 6import javax.crypto.spec.SecretKeySpec; 7import java.io.UnsupportedEncodingException; 8import java.security.InvalidAlgorithmParameterException; 9import java.security.InvalidKeyException; 10import java.security.MessageDigest; 11import java.security.NoSuchAlgorithmException; 12import java.util.Arrays; 13import java.util.Base64; 14 15public class aes { 16 public static void main(String[] args) throws Exception { 17 String data = "cisco"; 18 String key = "fd6b639dbcff0c2a1b03b389ec763c4b"; 19 String iv = "77b07a672d57d64c"; 20 // 加密 21 byte[] raw = key.getBytes(); 22 SecretKeySpec skeySpec = new SecretKeySpec(raw, "AES"); // 举一反三:以后不确定是不是AES加密,可以hook SecretKeySpec看传入的参数是否包含"AES",进而验证了app是使用AES加密 23 IvParameterSpec ivSpec = new IvParameterSpec(iv.getBytes()); 24 Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); 25 cipher.init(Cipher.ENCRYPT_MODE, skeySpec, ivSpec); 26 byte[] encrypted = cipher.doFinal(data.getBytes()); 27 System.out.println(Arrays.toString(encrypted)); // [99, -17, -126, 52, -74, 103, -10, -126, 9, -117, -86, 94, 1, 10, 114, -39] 28 } 29}
破解
#坑/python/库/pycryptodome 安装了pycryptodome还是报错ModuleNotFoundError: No module named 'Crypto::不用命令行安装,去在pycharm上面去安装,问题依旧未解决@todo

1# pip install pycryptodome 2# pycryptodome 是 pycrypto 的一个分支,主要用于解决 pycrypto 的一些问题和提供更多的功能。它提供了许多对称密钥算法(AES, DES, Blowfish, CAST, ARC4, ...)、 3# 非对称密钥算法(RSA, DSA, ECC, ElGamal, ...)、散列算法(MD5, SHA-1, SHA-2, ...)等。 4# pycryptodome 可以用来加密和解密文件和数据,生成和验证数字签名,生成和验证消息认证码,生成随机数等。 5from Crypto.Cipher import AES 6from Crypto.Util.Padding import pad 7 8KEY = "fd6b639dbcff0c2a1b03b389ec763c4b" 9IV = "77b07a672d57d64c" 10 11 12def aes_encrypt(data_string): 13 aes = AES.new(key=KEY.encode('utf-8'), mode=AES.MODE_CBC, iv=IV.encode('utf-8')) 14 raw = pad(data_string.encode('utf-8'), 16) 15 return aes.encrypt(raw) 16 17data = aes_encrypt("cisco") 18print(data) 19print([i for i in data])
8. base64编码
原理
1import java.util.Base64; 2 3public class base64 { 4 5 public static void main(String[] args) { 6 String name = "cisco"; 7 // 加密 8 Base64.Encoder encoder = Base64.getEncoder(); 9 String res = encoder.encodeToString(name.getBytes()); 10 System.out.println(res); // "Y2lzY28=" 11 // 解密 12 Base64.Decoder decoder = Base64.getDecoder(); 13 byte[] origin = decoder.decode(res); 14 String data = new String(origin); 15 System.out.println(data); // cisco 16 } 17}
破解
1import base64 2 3name = "cisco" 4 5res = base64.b64encode(name.encode('utf-8')) 6print(res) # b'Y2lzY28=' 7 8data = base64.b64decode(res) 9origin = data.decode('utf-8') 10print(origin) # "cisco" 11 12# 不同,换行符 + == @todo
9. gzip压缩
原理
- 抖音注册设备:设备。
- 注册设备:生成一些值(cdid、手机型号、手机品牌....) ,后端读取到时候,发现cdid是一个全新的请求。那么抖音就会生成
device_id、install_id、tt
- 注册设备:生成一些值(cdid、手机型号、手机品牌....) ,后端读取到时候,发现cdid是一个全新的请求。那么抖音就会生成
(cdid、手机型号、手机品牌....) --> gzip压缩(字节) --> 加密 --> 密文
1import java.io.ByteArrayInputStream; 2import java.io.ByteArrayOutputStream; 3import java.io.IOException; 4import java.math.BigInteger; 5import java.security.SecureRandom; 6import java.util.ArrayList; 7import java.util.Arrays; 8import java.util.HashMap; 9import java.io.OutputStream; 10import java.util.zip.GZIPInputStream; 11import java.util.zip.GZIPOutputStream; 12 13public class gzip { 14 15 public static void main(String[] args) throws IOException { 16 // 压缩 17 String data = "cisco"; 18 System.out.println(Arrays.toString(data.getBytes())); // [99, 105, 115, 99, 111] 19 20 ByteArrayOutputStream v0_1 = new ByteArrayOutputStream(); 21 GZIPOutputStream v1 = new GZIPOutputStream((v0_1)); 22 v1.write(data.getBytes()); // 将字符串"cisco"转换为字节数组,然后将字节数组数据写入 v1 中进行压缩。 23 v1.close(); 24 25 byte[] arg6 = v0_1.toByteArray(); // 获取压缩后的字节数组 26 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] 27 28 // 解压缩 29 ByteArrayOutputStream out = new ByteArrayOutputStream(); 30 ByteArrayInputStream in = new ByteArrayInputStream(arg6); // 将压缩后的字节数组写入ungzip中进行解压缩。 31 GZIPInputStream ungzip = new GZIPInputStream(in); 32 byte[] buffer = new byte[256]; 33 int n; 34 while ((n = ungzip.read(buffer)) >= 0) { 35 out.write(buffer, 0, n); 36 } 37 byte[] res = out.toByteArray(); 38 System.out.println(Arrays.toString(res)); // [99, 105, 115, 99, 111] 39 System.out.println(out.toString("UTF-8")); // cisco 40 } 41}
破解
1import gzip 2 3# 压缩 4s_in = "ciso".encode('utf-8') 5s_out = gzip.compress(s_in) 6print([i for i in s_out]) 7# java、Python语言区别。(个别字节是不同,不影响整个的结果),。 8# 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] 9# 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] 10# 解压缩 11res = gzip.decompress(s_out) 12print(res) # b'ciso' 13print(res.decode('utf-8')) # ciso






