首页 文章 精选 留言 我的

精选列表

搜索[JDK 25],共10000篇文章
优秀的个人博客,低调大师

Java入门系列-25-NIO(实现非阻塞网络通信)

还记得之前介绍NIO时对比传统IO的一大特点吗?就是NIO是非阻塞式的,这篇文章带大家来看一下非阻塞的网络操作。 补充:以数组的形式使用缓冲区 package testnio; import java.io.IOException; import java.io.RandomAccessFile; import java.nio.ByteBuffer; import java.nio.channels.FileChannel; public class TestBufferArray { public static void main(String[] args) throws IOException { RandomAccessFile raf1=new RandomAccessFile("D:/1.txt","rw"); //1.获取通道 FileChannel channel1=raf1.getChannel(); //2.创建缓冲区数组 ByteBuffer buf1=ByteBuffer.allocate(512); ByteBuffer buf2=ByteBuffer.allocate(512); ByteBuffer[] bufs= {buf1,buf2}; //3.将数据读入缓冲区数组 channel1.read(bufs); for (ByteBuffer byteBuffer : bufs) { byteBuffer.flip(); } System.out.println(new String(bufs[0].array(),0,bufs[0].limit())); System.out.println("-----------"); System.out.println(new String(bufs[1].array(),0,bufs[1].limit())); //写入缓冲区数组到通道中 RandomAccessFile raf2=new RandomAccessFile("D:/2.txt","rw"); FileChannel channel2=raf2.getChannel(); channel2.write(bufs); } } 使用NIO实现阻塞式网络通信 TCP协议的网络通信传统实现方式是通过套接字编程(Socket和ServerSocket),NIO实现TCP网络通信需要用到 Channel 接口的两个实现类:SocketChannel和ServerSocketChannel 使用NIO实现阻塞式网络通信 客户端 package com.jikedaquan.blockingnio; import java.io.IOException; import java.net.InetSocketAddress; import java.nio.ByteBuffer; import java.nio.channels.FileChannel; import java.nio.channels.SocketChannel; import java.nio.file.Paths; import java.nio.file.StandardOpenOption; public class Client { public static void main(String[] args) { SocketChannel sChannel=null; FileChannel inChannel=null; try { //1、获取通道 sChannel = SocketChannel.open(new InetSocketAddress("127.0.0.1", 1666)); //用于读取文件 inChannel = FileChannel.open(Paths.get("F:/a.jpg"), StandardOpenOption.READ); //2、分配指定大小的缓冲区 ByteBuffer buf=ByteBuffer.allocate(1024); //3、读取本地文件,发送到服务器端 while(inChannel.read(buf)!=-1) { buf.flip(); sChannel.write(buf); buf.clear(); } } catch (IOException e) { e.printStackTrace(); }finally { //关闭通道 if (inChannel!=null) { try { inChannel.close(); } catch (IOException e) { e.printStackTrace(); } } if(sChannel!=null) { try { sChannel.close(); } catch (IOException e) { e.printStackTrace(); } } } } } new InetSocketAddress("127.0.0.1", 1666) 用于向客户端套接字通道(SocketChannel)绑定要连接地址和端口 服务端 package com.jikedaquan.blockingnio; import java.io.IOException; import java.net.InetSocketAddress; import java.nio.ByteBuffer; import java.nio.channels.FileChannel; import java.nio.channels.ServerSocketChannel; import java.nio.channels.SocketChannel; import java.nio.file.Paths; import java.nio.file.StandardOpenOption; public class Server { public static void main(String[] args) { ServerSocketChannel ssChannel=null; FileChannel outChannel=null; SocketChannel sChannel=null; try { //1、获取通道 ssChannel = ServerSocketChannel.open(); //用于保存文件的通道 outChannel = FileChannel.open(Paths.get("F:/b.jpg"), StandardOpenOption.WRITE,StandardOpenOption.CREATE); //2、绑定要监听的端口号 ssChannel.bind(new InetSocketAddress(1666)); //3、获取客户端连接的通道 sChannel = ssChannel.accept(); //4、分配指定大小的缓冲区 ByteBuffer buf=ByteBuffer.allocate(1024); //5、接收客户端的数据,并保存到本地 while(sChannel.read(buf)!=-1) { buf.flip(); outChannel.write(buf); buf.clear(); } } catch (IOException e) { e.printStackTrace(); }finally { //6、关闭通道 if(sChannel!=null) { try { sChannel.close(); } catch (IOException e) { e.printStackTrace(); } } if(outChannel!=null) { try { outChannel.close(); } catch (IOException e) { e.printStackTrace(); } } if(ssChannel!=null) { try { ssChannel.close(); } catch (IOException e) { e.printStackTrace(); } } } } } 服务端套接字仅绑定要监听的端口即可 ssChannel.bind(new InetSocketAddress(1666)); 上面的代码使用NIO实现的网络通信,可能有同学会问,没有看到阻塞效果啊,确实是阻塞式的看不到效果,因为客户端发送一次数据就结束了,服务端也是接收一次数据就结束了。那如果服务端接收完成数据后,再向客户端反馈呢? 能够看到阻塞效果的网络通信 客户端 package com.jikedaquan.blockingnio2; import java.io.IOException; import java.net.InetSocketAddress; import java.nio.ByteBuffer; import java.nio.channels.FileChannel; import java.nio.channels.SocketChannel; import java.nio.file.Paths; import java.nio.file.StandardOpenOption; public class Client { public static void main(String[] args) { SocketChannel sChannel=null; FileChannel inChannel=null; try { sChannel = SocketChannel.open(new InetSocketAddress("127.0.0.1", 1666)); inChannel = FileChannel.open(Paths.get("F:/a.jpg"), StandardOpenOption.READ); ByteBuffer buf=ByteBuffer.allocate(1024); while(inChannel.read(buf)!=-1) { buf.flip(); sChannel.write(buf); buf.clear(); } //sChannel.shutdownOutput();//去掉注释掉将不会阻塞 //接收服务器端的反馈 int len=0; while((len=sChannel.read(buf))!=-1) { buf.flip(); System.out.println(new String(buf.array(),0,len)); buf.clear(); } } catch (IOException e) { e.printStackTrace(); }finally { if(inChannel!=null) { try { inChannel.close(); } catch (IOException e) { e.printStackTrace(); } } if(sChannel!=null) { try { sChannel.close(); } catch (IOException e) { e.printStackTrace(); } } } } } 服务端 package com.jikedaquan.blockingnio2; import java.io.IOException; import java.net.InetSocketAddress; import java.nio.ByteBuffer; import java.nio.channels.FileChannel; import java.nio.channels.ServerSocketChannel; import java.nio.channels.SocketChannel; import java.nio.file.Paths; import java.nio.file.StandardOpenOption; public class Server { public static void main(String[] args) { ServerSocketChannel ssChannel=null; FileChannel outChannel=null; SocketChannel sChannel=null; try { ssChannel = ServerSocketChannel.open(); outChannel = FileChannel.open(Paths.get("F:/a.jpg"),StandardOpenOption.WRITE,StandardOpenOption.CREATE); ssChannel.bind(new InetSocketAddress(1666)); sChannel = ssChannel.accept(); ByteBuffer buf=ByteBuffer.allocate(1024); while(sChannel.read(buf)!=-1) { buf.flip(); outChannel.write(buf); buf.clear(); } //发送反馈给客户端 buf.put("服务端接收数据成功".getBytes()); buf.flip(); sChannel.write(buf); } catch (IOException e) { e.printStackTrace(); }finally { if(sChannel!=null) { try { sChannel.close(); } catch (IOException e) { e.printStackTrace(); } } if(outChannel!=null) { try { outChannel.close(); } catch (IOException e) { e.printStackTrace(); } } if(ssChannel!=null) { try { ssChannel.close(); } catch (IOException e) { e.printStackTrace(); } } } } } 服务端将向客户端发送两次数据 选择器(Selector) 想要实现非阻塞的IO,必须要先弄懂选择器。Selector 抽象类,可通过调用此类的 open 方法创建选择器,该方法将使用系统的默认选择器提供者创建新的选择器。 将通道设置为非阻塞之后,需要将通道注册到选择器中,注册的同时需要指定一个选择键的类型 (SelectionKey)。 选择键(SelectionKey)可以认为是一种标记,标记通道的类型和状态。 SelectionKey的静态字段: OP_ACCEPT:用于套接字接受操作的操作集位OP_CONNECT:用于套接字连接操作的操作集位OP_READ:用于读取操作的操作集位OP_WRITE:用于写入操作的操作集位 用于检测通道状态的方法: 方法名称 说明 isAcceptable() 测试此键的通道是否已准备好接受新的套接字连接 isConnectable() 测试此键的通道是否已完成其套接字连接操作 isReadable() 测试此键的通道是否已准备好进行读取 isWritable() 测试此键的通道是否已准备好进行写入 将通道注册到选择器: ssChannel.register(selector, SelectionKey.OP_ACCEPT); IO操作准备就绪的通道大于0,轮询选择器 while(selector.select()>0) { //获取选择键,根据不同的状态做不同的操作 } 实现非阻塞式TCP协议网络通信 非阻塞模式:channel.configureBlocking(false); 客户端 package com.jikedaquan.nonblockingnio; import java.io.IOException; import java.net.InetSocketAddress; import java.nio.ByteBuffer; import java.nio.channels.SocketChannel; import java.util.Date; import java.util.Scanner; public class Client { public static void main(String[] args) { SocketChannel sChannel=null; try { //1、获取通道 sChannel = SocketChannel.open(new InetSocketAddress("127.0.0.1",1666)); //2、切换非阻塞模式 sChannel.configureBlocking(false); //3、分配指定大小的缓冲区 ByteBuffer buf=ByteBuffer.allocate(1024); //4、发送数据给服务端 Scanner scanner=new Scanner(System.in); //循环从控制台录入数据发送给服务端 while(scanner.hasNext()) { String str=scanner.next(); buf.put((new Date().toString()+"\n"+str).getBytes()); buf.flip(); sChannel.write(buf); buf.clear(); } } catch (IOException e) { e.printStackTrace(); }finally { //5、关闭通道 if(sChannel!=null) { try { sChannel.close(); } catch (IOException e) { e.printStackTrace(); } } } } } 服务端 package com.jikedaquan.nonblockingnio; import java.io.IOException; import java.net.InetSocketAddress; import java.nio.ByteBuffer; import java.nio.channels.SelectionKey; import java.nio.channels.Selector; import java.nio.channels.ServerSocketChannel; import java.nio.channels.SocketChannel; import java.util.Iterator; public class Server { public static void main(String[] args) throws IOException { //1、获取通道 ServerSocketChannel ssChannel=ServerSocketChannel.open(); //2、切换非阻塞模式 ssChannel.configureBlocking(false); //3、绑定监听的端口号 ssChannel.bind(new InetSocketAddress(1666)); //4、获取选择器 Selector selector=Selector.open(); //5、将通道注册到选择器上,并指定“监听接收事件” ssChannel.register(selector, SelectionKey.OP_ACCEPT); //6、轮询式的获取选择器上已经 “准备就绪”的事件 while(selector.select()>0) { //7、获取当前选择器中所有注册的“选择键(已就绪的监听事件)” Iterator<SelectionKey> it=selector.selectedKeys().iterator(); while(it.hasNext()) { //8、获取准备就绪的事件 SelectionKey sk=it.next(); //9、判断具体是什么事件准备就绪 if(sk.isAcceptable()) { //10、若“接收就绪”,获取客户端连接 SocketChannel sChannel=ssChannel.accept(); //11、切换非阻塞模式 sChannel.configureBlocking(false); //12、将该通道注册到选择器上 sChannel.register(selector, SelectionKey.OP_READ); }else if(sk.isReadable()) { //13、获取当前选择器上“读就绪”状态的通道 SocketChannel sChannel=(SocketChannel)sk.channel(); //14、读取数据 ByteBuffer buf=ByteBuffer.allocate(1024); int len=0; while((len=sChannel.read(buf))>0) { buf.flip(); System.out.println(new String(buf.array(),0,len)); buf.clear(); } } //15、取消选择键 SelectionKey it.remove(); } } } } 服务端接收客户端的操作需要在判断 isAcceptable() 方法内将就绪的套接字通道以读操作注册到 选择器中 在判断 isReadable() 内从通道中获取数据 实现非阻塞式UDP协议网络通信 发送端 package com.jikedaquan.nonblockingnio; import java.io.IOException; import java.net.InetSocketAddress; import java.nio.ByteBuffer; import java.nio.channels.DatagramChannel; import java.util.Scanner; public class TestDatagramSend { public static void main(String[] args) throws IOException { //获取通道 DatagramChannel dChannel=DatagramChannel.open(); //非阻塞 dChannel.configureBlocking(false); ByteBuffer buf=ByteBuffer.allocate(1024); Scanner scanner=new Scanner(System.in); while(scanner.hasNext()) { String str=scanner.next(); buf.put(str.getBytes()); buf.flip(); //发送数据到目标地址和端口 dChannel.send(buf,new InetSocketAddress("127.0.0.1", 1666)); buf.clear(); } dChannel.close(); } } 接收端 package com.jikedaquan.nonblockingnio; import java.io.IOException; import java.net.InetSocketAddress; import java.nio.ByteBuffer; import java.nio.channels.DatagramChannel; import java.nio.channels.SelectionKey; import java.nio.channels.Selector; import java.util.Iterator; public class TestDatagramReceive { public static void main(String[] args) throws IOException { //获取通道 DatagramChannel dChannel=DatagramChannel.open(); dChannel.configureBlocking(false); //绑定监听端口 dChannel.bind(new InetSocketAddress(1666)); //获取选择器 Selector selector=Selector.open(); //读操作注册通道 dChannel.register(selector, SelectionKey.OP_READ); while(selector.select()>0) { Iterator<SelectionKey> it=selector.selectedKeys().iterator(); //迭代选择键 while(it.hasNext()) { SelectionKey sk=it.next(); //通道可读 if(sk.isReadable()) { ByteBuffer buf=ByteBuffer.allocate(1024); //接收数据存入缓冲区 dChannel.receive(buf); buf.flip(); System.out.println(new String(buf.array(),0,buf.limit())); buf.clear(); } } it.remove(); } } }

优秀的个人博客,低调大师

25.Android Studio下Ndk开发(参数加密解决方案)

网络请求通过http传递到后台,如果不对数据做加密处理的话,很容易会被抓包,此时,app就是很不安全的,被截取到接口地址和参数后容易被攻击。今天我要分享的就是如何提高网络接口安全性的解决方案。 之前做的项目是采取直接在java层对参数进行加密,加密方式也有很多,RSA加密,MD5加密,AES加密,DES加密,Base64加密等等,具体介绍可以参考这里 Android中的加密方法(http://www.cnblogs.com/whoislcj/p/5470095.html),这种方式在一定程度上可以提高数据的安全性,但是深入来看,我们的加密方式对外暴露出来,当app被反编译时,对方可以拿到我们的代码,可以看到我们加密的方式,这样一来,会更加容易让对方找到破解密文的方法,因为在目前所有加密方式中,既具备实用性又具备绝对安全性的方法是不存在的。所以我们是否可以做到加密方式也对外不可见呢,或者如果不能做到绝对不可见,是否可以大大的提高对方破解密文的难度。这就是今天要做的,通过jni将加密方法打包到so库中,防止被放编译,算是在这些加密算法的上面加一层壳,这里以md5加密为例。 so库破解的难度之大,远远超过破解混淆后的apk,所以jni是解决安全性隐患的一个切入点。 创建CMakeLists文件,配置相关的内容 #参数加密 cmake_minimum_required(VERSION 3.4.1) find_library( log-lib log ) add_library( encrypt SHARED src/main/cpp/encrypt.cpp src/main/cpp/md5.cpp) # 将预构建库与本地库相连 target_link_libraries( encrypt ${log-lib} ) EncryptUtils package com.app.rzm.utils; import android.content.Context; /** * ndk实现参数加密 */ public class EncryptUtils { static { System.loadLibrary("encrypt"); } public static String encrypt(Context context, String param){ checkSignature(context); return encryptNative(context,param); } /** * 对一个字符串进行加密 * @param context * @param param * @return */ private static native String encryptNative(Context context, String param); /** * 校验app签名 * @param context */ private static native void checkSignature(Context context); } 调用方式 public class TestParamsEncryptActivity extends AppCompatActivity { private TextView mText; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_test_params_encrypt); mText = (TextView) findViewById(R.id.text); //拿到签名 try { PackageInfo packageInfo = getPackageManager().getPackageInfo(getPackageName(), PackageManager.GET_SIGNATURES); Signature[] signatures = packageInfo.signatures; LogUtils.d("signature:"+signatures[0].toCharsString()); } catch (PackageManager.NameNotFoundException e) { e.printStackTrace(); } //将网络请求中的http参数拼接成这样的字符串username=renzhenming&password=123456 //然后将这个参数字符串进行加密 String params = EncryptUtils.encrypt(this,"username=renzhenming&password=123456"); //作为参数给到服务器,服务器也生成同样的密文,然后将加密的字符串进行比较 mText.setText(params); } } 接下来是关键代码,在c++中实现md5加密, * 加密解密的过程: md5.h #ifndef MD5_H #define MD5_H typedef struct { unsigned int count[2]; unsigned int state[4]; unsigned char buffer[64]; }MD5_CTX; #define F(x,y,z) ((x & y) | (~x & z)) #define G(x,y,z) ((x & z) | (y & ~z)) #define H(x,y,z) (x^y^z) #define I(x,y,z) (y ^ (x | ~z)) #define ROTATE_LEFT(x,n) ((x << n) | (x >> (32-n))) #define FF(a,b,c,d,x,s,ac) { \ a += F(b, c, d) + x + ac; \ a = ROTATE_LEFT(a, s); \ a += b; \ } #define GG(a,b,c,d,x,s,ac) { \ a += G(b, c, d) + x + ac; \ a = ROTATE_LEFT(a, s); \ a += b; \ } #define HH(a,b,c,d,x,s,ac) { \ a += H(b, c, d) + x + ac; \ a = ROTATE_LEFT(a, s); \ a += b; \ } #define II(a,b,c,d,x,s,ac) { \ a += I(b, c, d) + x + ac; \ a = ROTATE_LEFT(a, s); \ a += b; \ } void MD5Init(MD5_CTX *context); void MD5Update(MD5_CTX *context, unsigned char *input, unsigned int inputlen); void MD5Final(MD5_CTX *context, unsigned char digest[16]); void MD5Transform(unsigned int state[4], unsigned char block[64]); void MD5Encode(unsigned char *output, unsigned int *input, unsigned int len); void MD5Decode(unsigned int *output, unsigned char *input, unsigned int len); #endif md5.cpp #include "md5.h" #include "string" unsigned char PADDING[] = { 0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; //在逆向代码的时候,需要关注下面的特征值 void MD5Init(MD5_CTX *context) { context->count[0] = 0; context->count[1] = 0; context->state[0] = 0x67452301; context->state[1] = 0xEFCDAB89; context->state[2] = 0x98BADCFE; context->state[3] = 0x10325476; } void MD5Update(MD5_CTX *context, unsigned char *input, unsigned int inputlen) { unsigned int i = 0, index = 0, partlen = 0; index = (context->count[0] >> 3) & 0x3F; partlen = 64 - index; context->count[0] += inputlen << 3; if (context->count[0] < (inputlen << 3)) context->count[1]++; context->count[1] += inputlen >> 29; if (inputlen >= partlen) { memcpy(&context->buffer[index], input, partlen); MD5Transform(context->state, context->buffer); for (i = partlen; i + 64 <= inputlen; i += 64) MD5Transform(context->state, &input[i]); index = 0; } else { i = 0; } memcpy(&context->buffer[index], &input[i], inputlen - i); } void MD5Final(MD5_CTX *context, unsigned char digest[16]) { unsigned int index = 0, padlen = 0; unsigned char bits[8]; index = (context->count[0] >> 3) & 0x3F; padlen = (index < 56) ? (56 - index) : (120 - index); MD5Encode(bits, context->count, 8); MD5Update(context, PADDING, padlen); MD5Update(context, bits, 8); MD5Encode(digest, context->state, 16); } void MD5Encode(unsigned char *output, unsigned int *input, unsigned int len) { unsigned int i = 0, j = 0; while (j < len) { output[j] = input[i] & 0xFF; output[j + 1] = (input[i] >> 8) & 0xFF; output[j + 2] = (input[i] >> 16) & 0xFF; output[j + 3] = (input[i] >> 24) & 0xFF; i++; j += 4; } } void MD5Decode(unsigned int *output, unsigned char *input, unsigned int len) { unsigned int i = 0, j = 0; while (j < len) { output[i] = (input[j]) | (input[j + 1] << 8) | (input[j + 2] << 16) | (input[j + 3] << 24); i++; j += 4; } } void MD5Transform(unsigned int state[4], unsigned char block[64]) { unsigned int a = state[0]; unsigned int b = state[1]; unsigned int c = state[2]; unsigned int d = state[3]; unsigned int x[64]; MD5Decode(x, block, 64); FF(a, b, c, d, x[0], 7, 0xd76aa478); FF(d, a, b, c, x[1], 12, 0xe8c7b756); FF(c, d, a, b, x[2], 17, 0x242070db); FF(b, c, d, a, x[3], 22, 0xc1bdceee); FF(a, b, c, d, x[4], 7, 0xf57c0faf); FF(d, a, b, c, x[5], 12, 0x4787c62a); FF(c, d, a, b, x[6], 17, 0xa8304613); FF(b, c, d, a, x[7], 22, 0xfd469501); FF(a, b, c, d, x[8], 7, 0x698098d8); FF(d, a, b, c, x[9], 12, 0x8b44f7af); FF(c, d, a, b, x[10], 17, 0xffff5bb1); FF(b, c, d, a, x[11], 22, 0x895cd7be); FF(a, b, c, d, x[12], 7, 0x6b901122); FF(d, a, b, c, x[13], 12, 0xfd987193); FF(c, d, a, b, x[14], 17, 0xa679438e); FF(b, c, d, a, x[15], 22, 0x49b40821); GG(a, b, c, d, x[1], 5, 0xf61e2562); GG(d, a, b, c, x[6], 9, 0xc040b340); GG(c, d, a, b, x[11], 14, 0x265e5a51); GG(b, c, d, a, x[0], 20, 0xe9b6c7aa); GG(a, b, c, d, x[5], 5, 0xd62f105d); GG(d, a, b, c, x[10], 9, 0x2441453); GG(c, d, a, b, x[15], 14, 0xd8a1e681); GG(b, c, d, a, x[4], 20, 0xe7d3fbc8); GG(a, b, c, d, x[9], 5, 0x21e1cde6); GG(d, a, b, c, x[14], 9, 0xc33707d6); GG(c, d, a, b, x[3], 14, 0xf4d50d87); GG(b, c, d, a, x[8], 20, 0x455a14ed); GG(a, b, c, d, x[13], 5, 0xa9e3e905); GG(d, a, b, c, x[2], 9, 0xfcefa3f8); GG(c, d, a, b, x[7], 14, 0x676f02d9); GG(b, c, d, a, x[12], 20, 0x8d2a4c8a); HH(a, b, c, d, x[5], 4, 0xfffa3942); HH(d, a, b, c, x[8], 11, 0x8771f681); HH(c, d, a, b, x[11], 16, 0x6d9d6122); HH(b, c, d, a, x[14], 23, 0xfde5380c); HH(a, b, c, d, x[1], 4, 0xa4beea44); HH(d, a, b, c, x[4], 11, 0x4bdecfa9); HH(c, d, a, b, x[7], 16, 0xf6bb4b60); HH(b, c, d, a, x[10], 23, 0xbebfbc70); HH(a, b, c, d, x[13], 4, 0x289b7ec6); HH(d, a, b, c, x[0], 11, 0xeaa127fa); HH(c, d, a, b, x[3], 16, 0xd4ef3085); HH(b, c, d, a, x[6], 23, 0x4881d05); HH(a, b, c, d, x[9], 4, 0xd9d4d039); HH(d, a, b, c, x[12], 11, 0xe6db99e5); HH(c, d, a, b, x[15], 16, 0x1fa27cf8); HH(b, c, d, a, x[2], 23, 0xc4ac5665); II(a, b, c, d, x[0], 6, 0xf4292244); II(d, a, b, c, x[7], 10, 0x432aff97); II(c, d, a, b, x[14], 15, 0xab9423a7); II(b, c, d, a, x[5], 21, 0xfc93a039); II(a, b, c, d, x[12], 6, 0x655b59c3); II(d, a, b, c, x[3], 10, 0x8f0ccc92); II(c, d, a, b, x[10], 15, 0xffeff47d); II(b, c, d, a, x[1], 21, 0x85845dd1); II(a, b, c, d, x[8], 6, 0x6fa87e4f); II(d, a, b, c, x[15], 10, 0xfe2ce6e0); II(c, d, a, b, x[6], 15, 0xa3014314); II(b, c, d, a, x[13], 21, 0x4e0811a1); II(a, b, c, d, x[4], 6, 0xf7537e82); II(d, a, b, c, x[11], 10, 0xbd3af235); II(c, d, a, b, x[2], 15, 0x2ad7d2bb); II(b, c, d, a, x[9], 21, 0xeb86d391); state[0] += a; state[1] += b; state[2] += c; state[3] += d; } 客户端通过定义的规则将参数加密后,将密文和铭文参数同时传递到服务器,服务器收到参数进行解析,使用同样的加密算法将参数加密,然后对比此次得到的密文和客户端传递的密文是否相同,如果相同说明数据安全,没有被篡改,如果不同,则表示数据改变,不再发送数据到客户端 将加密方法打包到so库中的好处就是可以防止对方反编译看到我们的加密条件,如果对方不知道我们是如何加密的,也就可以在一定程度上防止数据泄漏,但是只是单纯的这样做并不能保证绝对的安全,比如,我不需要知道你是怎么加密的,只需要反编译apk后得到几个信息1.你应用的包名,2.你的so库,3.你的native方法 的完整类名和方法名(native方法不能被混淆,混淆后无法使用,所以可以得到),只要得到这三个信息,我就可以创建包名相同方法名相同的一个应用,把so放进去,然后就可以绕过密钥检查,轻松的调用你的接口了。 解决这个问题的方法就是在so库中加入签名验证,当调用加密方法对操作参数的时候,验证此时应用签名是否是我们本应用的,如果不是,则表示当前应用是伪应用,直接返回, 防止上边那种恶意调用接口情况的出现。对签名做校验,也就是只允许指定的应用可以使用,类似在微信支付中,也有在官方管理后台申请和配置应用的的签名和包名,否则就禁止使用,签名和包名必须得要一致。 com_app_rzm_utils_EncryptUtils.h /* DO NOT EDIT THIS FILE - it is machine generated */ #include <jni.h> /* Header for class com_app_rzm_utils_Encryptils */ #ifndef _Included_com_app_rzm_utils_EncryptUtils #define _Included_com_app_rzm_utils_EncryptUtils #ifdef __cplusplus extern "C" { #endif /* * Class: com_app_rzm_utils_Encryptils * Method: encryptNative * Signature: (Ljava/lang/String;)Ljava/lang/String; */ JNIEXPORT jstring JNICALL Java_com_app_rzm_utils_EncryptUtils_encryptNative (JNIEnv *, jclass, jobject ,jstring); JNIEXPORT void JNICALL Java_com_app_rzm_utils_EncryptUtils_checkSignature (JNIEnv *, jclass, jobject); #ifdef __cplusplus } #endif #endif encrypt.cpp #include "com_app_rzm_utils_EncryptUtils.h" #include "md5.h" #include <string> #include <android/log.h> using namespace std; #define LOGI(FORMAT,...) __android_log_print(ANDROID_LOG_INFO,"renzhenming",FORMAT,##__VA_ARGS__); #define LOGE(FORMAT,...) __android_log_print(ANDROID_LOG_ERROR,"renzhenming",FORMAT,##__VA_ARGS__); //我们加密的方式是对参数进行md5加密,在md5加密之前还有一层加密,就是对参数字符串进行改造 //在字符串前加上自定义的key值,然后去掉字符串后边两位字符串,这个规则按需定制,增加破解的难度 #define MD5_KEY "renzhenming" //签名校验是否通过,否返回-1 static int signature_verify = -1; //app包名 static char* PACKAGE_NAME = "com.app.rzm"; //app签名,在这里配置我们app的正式签名,在so库中,可以保证安全性 static char* APP_SIGNATURE = "308201dd30820146020101300d06092a864886f70d010105050030373116301406035504030c0d416e64726f69642044656275673110300e060355040a0c07416e64726f6964310b3009060355040613025553301e170d3137303332333033323030305a170d3437303331363033323030305a30373116301406035504030c0d416e64726f69642044656275673110300e060355040a0c07416e64726f6964310b300906035504061302555330819f300d06092a864886f70d010101050003818d00308189028181008658a9a532d1c5e8c5a1a78c61535636220f73deb9b36d0912e2b6b1c50f5ed7eccb5cd8e0e4b2fd192d983fa15aeef6be5e5258e809b3fbad538fb68d1c78ebbdd89080664d707e9731c706386a45242a1e3a9e4819789bf832206a2bb7a45b663b6deb9be153ca4fe77b26ca1c43d85cbc20465cc6046f1e1dc16bbc65fe310203010001300d06092a864886f70d010105050003818100213550aa14811a7a407e7eb148b9cb50709c2c84185340b4c52f22caff07fd2e4a79e2814dc5a16fbd7a4b3ec638574eb5ee6baf7537b69aed6529a594bf556f1e0f073884739271f3e2572c4c174031b547846212643ae57bb35e8157c65b3760e37fc3c74f4e24daf3d91086d6ddd7a3f1e54b69ad235a6eb6c5524ce24800"; /** * @param env * @param jclazz * @param jparam * @return */ JNIEXPORT jstring JNICALL Java_com_app_rzm_utils_EncryptUtils_encryptNative (JNIEnv *env, jclass jclazz,jobject context,jstring jparam){ if(signature_verify == -1){ return env->NewStringUTF("EncryptUtils--> signature check err"); } const char *param = env->GetStringUTFChars(jparam,NULL); //在参数头位置加上MD5_KEY,然后去掉后面两位字符串 string signature_str(param); //insert(int p0, const char *s);在p0位置插入字符串s signature_str.insert(0,MD5_KEY); signature_str = signature_str.substr(0,signature_str.length()-2); //md5加密 MD5_CTX *ctx = new MD5_CTX(); MD5Init(ctx); MD5Update(ctx,(unsigned char *)signature_str.c_str(),signature_str.length()); unsigned char digest[16]; MD5Final(ctx, digest); int i = 0; char szMd5[32] = {0}; for(i = 0;i< 16 ; i++){ LOGI("EncryptUtils--> szMd5[%d]:%s",i,szMd5); //最终生成32位,不足前面补一位0 //x 表示以十六进制形式输出 ,02 表示不足两位,前面补0输出;出过两位,不影响 sprintf(szMd5,"%s%02x",szMd5,digest[i]); } env->ReleaseStringUTFChars(jparam,param); return env->NewStringUTF(szMd5); } JNIEXPORT void JNICALL Java_com_app_rzm_utils_EncryptUtils_checkSignature (JNIEnv *env, jclass jclazz, jobject context){ //1.获取包名 通过Context的getPackageName方法获取 //获取Context对象的class jclass context_class = env->GetObjectClass(context); //获取getPackageName的方法id jmethodID context_method_id = env->GetMethodID(context_class,"getPackageName","()Ljava/lang/String;"); //调用getPackageName方法 jstring package_name = (jstring)env->CallObjectMethod(context,context_method_id); //转换为char* const char *c_package_name = (char *)env->GetStringUTFChars(package_name,NULL); LOGI("EncryptUtils--> package name:%s\n",c_package_name); //2.对比包名 if(strcmp(c_package_name,PACKAGE_NAME)){ LOGI("EncryptUtils--> package name check err"); return; } //3.获取签名(通过下边这种方式) //PackageInfo packageInfo = getPackageManager().getPackageInfo(getPackageName(), PackageManager.GET_SIGNATURES); //Signature[] signatures = packageInfo.signatures; //LogUtils.d("signature:"+signatures[0].toCharsString()); //获取Context中的getPackageManager方法id jmethodID get_package_manager_method_id = env->GetMethodID(context_class,"getPackageManager","()Landroid/content/pm/PackageManager;"); //从Context中通过调用getPackageManager获取PackageManager对象 jobject package_manager = env->CallObjectMethod(context,get_package_manager_method_id); //获取PackageManager对象的class jclass package_manager_class = env->GetObjectClass(package_manager); //获取PackageManager对象中的getPackageInfo方法id jmethodID get_package_info_method_id = env->GetMethodID(package_manager_class,"getPackageInfo","(Ljava/lang/String;I)Landroid/content/pm/PackageInfo;"); //调用PackageInfo中的getPackageInfo方法获取PackageInfo对象(PackageManager.GET_SIGNATURES=64) jobject package_manager_info = env->CallObjectMethod(package_manager,get_package_info_method_id,package_name,64); //获取PackageInfo的class,获取它的属性的时候要用到 jclass package_info_class = env->GetObjectClass(package_manager_info); //获取PackageInfo中的signatures属性的fieldid jfieldID signatures_field_id = env->GetFieldID(package_info_class,"signatures","[Landroid/content/pm/Signature;"); //获取PackageInfo中的signatures属性 jobjectArray signatures_arrary = (jobjectArray) env->GetObjectField(package_manager_info, signatures_field_id); //获取数组中[0]位置的元素 jobject signature = env->GetObjectArrayElement(signatures_arrary,0); //获取String的class jclass signature_class = env->GetObjectClass(signature); //获取String中的toCharsString方法的methodid jmethodID to_chars_string_method_id = env->GetMethodID(signature_class,"toCharsString","()Ljava/lang/String;"); //调用String的toCharsString jstring signature_string = (jstring) env->CallObjectMethod(signature, to_chars_string_method_id); //转换为char* const char * signature_char = env->GetStringUTFChars(signature_string,NULL); LOGI("EncryptUtils--> current app signature:%s\n",signature_char); LOGI("EncryptUtils--> real app signature:%s\n",APP_SIGNATURE); //4.对比签名 if(strcmp(signature_char,APP_SIGNATURE) == 0){ signature_verify = 1; LOGI("EncryptUtils--> signature_verify success:%d\n",signature_verify); }else{ signature_verify = -1; LOGI("EncryptUtils--> signature_verify check failed:%d\n",signature_verify); } } 总结一下: 我们通过使用纯c++代码实现md5加密,将加密实现方式打包成so库,提高反编译的难度,另外在md5加密之外我们还设置了另一层加密规则,对参数字符串头尾进行处理,双层加密,确保数据的安全性。在加密手段之外,再进行app包名和签名的校验,从而保证so库只能在我们自己的app中使用。三层保护,这样一来,相信即便是遇到逆向工程师,要破解我们的app也是有一定难度的。

优秀的个人博客,低调大师

Android应用程序键盘(Keyboard)消息处理机制分析(25

Step 2. ActivityStack.activityIdleInternal 这个函数定义在frameworks/base/services/java/com/android/server/am/ActivityStack.java文件中: publicclassActivityStack{ ...... finalvoidactivityIdleInternal(IBindertoken,booleanfromTimeout, Configurationconfig){ ...... ArrayList<ActivityRecord>stops=null; ...... intNS=0; ...... synchronized(mService){ ...... //Atomicallyretrievealloftheotherthingstodo. stops=processStoppingActivitiesLocked(true); NS=stops!=null?stops.size():0; ...... } inti; ...... //Stopanyactivitiesthatarescheduledtodosobuthavebeen //waitingforthenextonetostart. for(i=0;i<NS;i++){ ActivityRecordr=(ActivityRecord)stops.get(i); synchronized(mService){ if(r.finishing){ finishCurrentActivityLocked(r,FINISH_IMMEDIATELY); }else{ ...... } } } ...... } ...... } 这个函数首先会调用processStoppingActivitiesLocked函数把所有处于Stopped状态的Activity取回来,然后逐个分析它们,如果它们的ActivityRecord中的finishing成员变量为true,就说明这个Activity需要销毁了,于是,就调用finishCurrentActivityLocked函数来销毁它们。 Step 3.ActivityStack.finishCurrentActivityLocked 这个函数定义在frameworks/base/services/java/com/android/server/am/ActivityStack.java文件中: publicclassActivityStack{ ...... privatefinalActivityRecordfinishCurrentActivityLocked(ActivityRecordr, intmode){ ...... returnfinishCurrentActivityLocked(r,index,mode); } privatefinalActivityRecordfinishCurrentActivityLocked(ActivityRecordr, intindex,intmode){ ...... //makesuretherecordiscleanedoutofotherplaces. mStoppingActivities.remove(r); mWaitingVisibleActivities.remove(r); ...... finalActivityStateprevState=r.state; r.state=ActivityState.FINISHING; if(mode==FINISH_IMMEDIATELY ||prevState==ActivityState.STOPPED ||prevState==ActivityState.INITIALIZING){ //Ifthisactivityisalreadystopped,wecanjustfinish //itrightnow. returndestroyActivityLocked(r,true)?null:r; }else{ ...... } returnr; } ...... } 从上面的Step 2中传进来的参数mode为FINISH_IMMEDIATELY,并且这个即将要被销毁的Activity的状态为Stopped,因此,接下来就会调用destroyActivityLocked函数来销毁它。 Step 4.ActivityStack.destroyActivityLocked 这个函数定义在frameworks/base/services/java/com/android/server/am/ActivityStack.java文件中: publicclassActivityStack{ ...... finalbooleandestroyActivityLocked(ActivityRecordr, booleanremoveFromApp){ ...... booleanremovedFromHistory=false; ...... finalbooleanhadApp=r.app!=null; if(hadApp){ ...... try{ ...... r.app.thread.scheduleDestroyActivity(r,r.finishing, r.configChangeFlags); }catch(Exceptione){ ...... } ...... }else{ ...... } ...... returnremovedFromHistory; } ...... } 在前面一篇文章Android应用程序启动过程源代码分析中,我们说到,每一个应用程序进程在ActivityManagerService中,都ProcessRecord记录与之对应,而每一个Activity,都是运行在一个进程上下文中,因此,在ActivityManagerService中,每一个ActivityRecord的app成员变量都应该指向一个ProcessRecord记录,于是,这里得到的hadApp为true。在ProcessRecord类中,有一个成员变量thread,它的类型为IApplicationThread。在文章Android应用程序启动过程源代码分析中,我们也曾经说过,每一个应用程序在启动的时候,它都会在内部创建一个ActivityThread对象,而在这个ActivityThread对象中,有一个成员变量mAppThread,它的类型为ApplicationThread,这是一个Binder对象,专门用来负责在应用程序和ActivityManagerService之间执行进程间通信工作的。应用程序在启动的时候,就会将这个Binder对象传递给ActivityManagerService,而ActivityManagerService就会把它保存在相应的ProcessRecord记录的thread成员变量中。因此,ProcessRecord记录的thread成员变量其实就是ApplicationThread对象的远程接口,于是,执行下面这个语句的时候: r.app.thread.scheduleDestroyActivity(r,r.finishing, r.configChangeFlags); 就会进入到ApplicationThread类中的scheduleDestroyActivity函数来。 Step 5.ApplicationThread.scheduleDestroyActivity 这个函数定义在frameworks/base/core/java/android/app/ActivityThread.java文件中: publicfinalclassActivityThread{ ...... privatefinalclassApplicationThreadextendsApplicationThreadNative{ ...... publicfinalvoidscheduleDestroyActivity(IBindertoken,booleanfinishing, intconfigChanges){ queueOrSendMessage(H.DESTROY_ACTIVITY,token,finishing?1:0, configChanges); } ...... } ...... } 这个函数调用外部类ActivityThread的queueOrSendMessage函数来往应用程序的消息队列中发送一个H.DESTROY_ACTIVITY消息,这个消息最终由ActivityThread类的handleDestroyActivity函数来处理。 本文转自 Luoshengyang 51CTO博客,原文链接:http://blog.51cto.com/shyluo/966656,如需转载请自行联系原作者

资源下载

更多资源
腾讯云软件源

腾讯云软件源

为解决软件依赖安装时官方源访问速度慢的问题,腾讯云为一些软件搭建了缓存服务。您可以通过使用腾讯云软件源站来提升依赖包的安装速度。为了方便用户自由搭建服务架构,目前腾讯云软件源站支持公网访问和内网访问。

Spring

Spring

Spring框架(Spring Framework)是由Rod Johnson于2002年提出的开源Java企业级应用框架,旨在通过使用JavaBean替代传统EJB实现方式降低企业级编程开发的复杂性。该框架基于简单性、可测试性和松耦合性设计理念,提供核心容器、应用上下文、数据访问集成等模块,支持整合Hibernate、Struts等第三方框架,其适用范围不仅限于服务器端开发,绝大多数Java应用均可从中受益。

Rocky Linux

Rocky Linux

Rocky Linux(中文名:洛基)是由Gregory Kurtzer于2020年12月发起的企业级Linux发行版,作为CentOS稳定版停止维护后与RHEL(Red Hat Enterprise Linux)完全兼容的开源替代方案,由社区拥有并管理,支持x86_64、aarch64等架构。其通过重新编译RHEL源代码提供长期稳定性,采用模块化包装和SELinux安全架构,默认包含GNOME桌面环境及XFS文件系统,支持十年生命周期更新。

WebStorm

WebStorm

WebStorm 是jetbrains公司旗下一款JavaScript 开发工具。目前已经被广大中国JS开发者誉为“Web前端开发神器”、“最强大的HTML5编辑器”、“最智能的JavaScript IDE”等。与IntelliJ IDEA同源,继承了IntelliJ IDEA强大的JS部分的功能。

用户登录
用户注册