如何用java语言对即时通讯软件进行加密
发布时间:2025-05-19 12:31:03 发布人:远客网络
一、如何用java语言对即时通讯软件进行加密
对于应用软件的保护笔者从两个方面进行考虑,第一是阻止盗版使用软件,第二是阻止竞争对手对软件反编译,即阻止对软件的逆向工程。
在软件运行时对自身存在的合法性进行判断,如果认为自身的存在和运行是被授权的、合法的,就运行;否则终止运行。这样即使软件可以被随意复制,只要盗版用户没有相应的授权信息就无法使用软件。
对编译产生的Class文件加密处理,并在运行时进行解密,解密者无法对软件进行反编译。
为了保护用Java语言开发的软件,我们设计并实现了一个实用、高强度的加密算法。以下称需要保护的Java软件为“受保护程序”,称对“受保护程序”进行加密保护的软件为“加密程序”。对软件加密保护的流程如图1所示。
为了防止用户发布序列号而导致“一次发行,到处都是”的盗版问题,提取用户机器中硬件相关的、具有唯一性的信息——用户计算机的硬盘分区C的序列号,并要求用户将此信息与用户名一起返回,之后用“序列号生成器”根据用户返回信息生成一个唯一合法的软件注册序列号发回用户,用户即可使用此号码注册使用软件。
这个信息提取器使用Winclows 32汇编以一个独立的小程序方式实现,程序代码如图2所示。
2、序列号生成器与序列号合法性判断函数的设计
序列号生成器与序列号合法性判断函数中运用RSA加密算法。在序列号生成器中是使用私钥将用户返回的信息(硬盘序列号,用户名)进行加密得到相应的注册序列号;在序列号合法性判断函数中使用私钥将用户输入的注册序列号解密,再与(硬盘序列号,用户名)进行比较,一致则调用程序装载器将程序其他部分解密装入内存,初始化删环境并运行程序主体;否则退出。
RSA加密算法的实现需要使用大数运算库,我们使用MIRACL大数库来实现RSA计算,序列号生成器的主要代码如下:
char szlnputString[]=”机器码和用户名组成的字符串”;
char szSerial[256]=[0];//用于存放生成的注册码
bign,d,c,m;//MIRACL中的大数类型
c= mirvar(0);//C存放输入的字符串大数
bytes to big( len, szlnputString,c);
//将输入字符串转换成大数形式并存入变量c中
cinstr(n,”以字符串形成表示的模数”);//初始化模数
cinstr(d,”以字符串形成表示的公钥”)://初始化公钥
powmod(c,d,n,m);//计算m=cdmod n
cotstr(m,szSerial);//m的16进制字符串即为注册码
序列号合法性检测函数的主要代码如下:
char szlnputStringL]=”机器码和用户名组成的字符串”;
char szSerial[ 256]=”用户输入的序列号”
bign,e,c,m;//MIRACL中的大数类型
cinstr(m,szSerial);//将序列号的16进制转成大数形式
cinstr(n,”模数n的字符串形式”);//初始化模数n
cinstr(e,”字符串形式的公钥”);//初始化公钥
if compare(m,n)==-1)//m<n时才进行解密
powmod(m,e,n,c);//计算m=me mod n
big_to _bytes(0,c,szSerial,0);//转为字符串
return lstrcmp( szlnputString,szSerial);
如果在序列号合法性检测函数中简单地使用图3所示流程:
解密者可以使用以下几种手段进行攻击:
(1)修改“判断合法性子函数”的返回指令,让它永远返回正确值,这样可以使用任意的序列号,安装/使用软件。
(2)修改判断后的跳转指令,使程序永远跳到正确的分支运行,效果和上一种一样。
(3)在“判断合法性子函数”之前执行一条跳转指令,绕过判断,直接跳转到“正常执行”分支运行,这样可以不用输入序列号安装/使用软件。
为阻止以上攻击手段,笔者在程序中增加了“序列号合法性检测函数”与程序其他部分“强耦合”(即增强其与程序其他部分的关联度,成为程序整体密不可分的一部分,一旦被修改程序将无法正常工作)的要求(见图1),并且设置一个“完整性检测函数”用于判断相关的代码是否被修改过。当然,基于同样的原因,“完整性检测函数”也必须与程序其他部分存在“强耦合”关系。
在程序其他部分的函数(例如函数A)中随机的访问需要强耦合的“序列号合法性检测函数”和“完整性检测函数”,在调用时随机的选择使用一个错误的序列号或是用户输入的序列号,并根据返回结果选择执行A中正常的功能代码还是错误退出的功能代码,流程如图4所示。
经过这种改进,如果破解者通过修改代码的方式破解将因“完整性检测”失败导致程序退出;如果使用SMC等技术绕过“序列号合法性判断函数”而直接跳至序列号正确时的执行入口,在后续的运行中,将因为随机的耦合调用失败导致程序退出。破解者要破解软件将不得不跟踪所有进行了耦合调用的函数,这显然是一个艰巨的任务。
我们使用CRC算法算出需进行完整性检测的文件的校验码,并用RSA加密算法的公钥(不同于序列号合法性检测中的公钥/私钥对)将其加密存放在特定的文件中,在检测时先用CRC算法重新生成需进行完
整性检测的文件的校验码,并用私钥将保存的校验码解密,两者相比较,相等则正常运行;否则退出。
与编译成机器码执行的程序不同,Java程序只能由Java虚拟机解释执行,因此程序加载器的工作包括:初始化Java虚拟机;在内存中解密当前要运行的class文件;使解密后的c:lass文件在虚拟机中运行,在
需要时解密另一个class文件。图5是用于初始化JVM的代码:
以上介绍了我们设计的针对Java软件的加密保护方法,其中综合运用了多种加密技术,抗破解强度高;使用纯软件保护技术,成本低。经笔者在Windows系列平台上进行测试,运行稳定,效果良好。
在研宄开发过程中,我们还总结出加密保护软件的一些经验:
1、对关键代码和数据要静态加密,再动态解密执行;要结合具体的工作平台使用反跟踪/调试技术;
2、要充分利用系统的功能,如在Windows下使用DLL文件或驱动程序形式能得到最大的丰又限,可以充分利用系统具有的各种功能;
3、如果可能应该将关键代码存放在不可禚复制的地方;
4、序列号要与机器码等用户信息相关以阻止盐复布序列号;
5、加密流程的合理性比加密算法本身的强度更重要。
二、为自己搭建一个分布式 IM(即时通讯) 系统
1、 CIM(CROSS-IM)一款面向开发者的 IM(即时通讯)系统;同时提供了一些组件帮助开发者构建一款属于自己可水平扩展的 IM。
2、 IM服务端;用于接收 client连接、消息透传、消息推送等功能。
3、消息路由服务器;用于处理消息路由、消息转发、用户登录、用户下线以及一些运营工具(获取在线用户数等)。
4、 IM客户端;给用户使用的消息终端,一个命令即可启动并向其他人发起通讯(群聊、私聊);同时内置了一些常用命令方便使用。
5、整体的流程也比较简单,流程图如下:
6、所以当我们自己部署时需要以下步骤:
7、接下来重点看看具体的实现,比如群聊、私聊消息如何流转;IM服务端负载均衡;服务如何注册发现等等。
8、先来看看服务端;主要是实现客户端上下线、消息下发等功能。
9、由于是在 SpringBoot中搭建的,所以在应用启动时需要启动 Netty服务。
10、从 pipline中可以看出使用了 Protobuf的编解码(具体报文在客户端中分析)。
11、需要满足 IM服务端的水平扩展需求,所以 cim-server是需要将自身数据发布到注册中心的。
12、所以在应用启动成功后需要将自身数据注册到 Zookeeper中。
13、最主要的目的就是将当前应用的 ip+ cim-server-port+ http-port注册上去。
14、上图是我在演示环境中注册的两个 cim-server实例(由于在一台服务器,所以只是端口不同)。
15、这样在客户端(监听这个 Zookeeper节点)就能实时的知道目前可用的服务信息。
16、当客户端请求 cim-forward-route中的登录接口(详见下文)做完业务验证(就相当于日常登录其他网站一样)之后,客户端会向服务端发起一个长连接,如之前的流程所示:
17、这时客户端会发送一个特殊报文,表明当前是登录信息。
18、服务端收到后就需要将该客户端的 userID和当前 Channel通道关系保存起来。
19、同时也缓存了用户的信息,也就是 userID和用户名。
20、当客户端断线后也需要将刚才缓存的信息清除掉。
21、同时也需要调用 route接口清除相关信息(具体接口看下文)。
22、从架构图中可以看出,路由层是非常重要的一环;它提供了一系列的 HTTP服务承接了客户端和服务端。
23、由于每一个客户端都是需要登录才能使用的,所以第一步自然是注册。
24、这里就设计的比较简单,直接利用 Redis来存储用户信息;用户信息也只有 ID和 userName而已。
25、只是为了方便查询在 Redis中的 KV又反过来存储了一份 VK,这样 ID和 userName都必须唯一。
26、这里的登录和 cim-server中的登录不一样,具有业务性质,
27、为了实现只能一个用户登录,使用了 Redis中的 set来保存登录信息;利用 userID作为 key,重复的登录就会写入失败。
28、获取一台可用的路由实例也比较简单:
29、当然要获取 Zookeeper中的服务实例前自然是需要监听 cim-server之前注册上去的那个节点。
30、也是在应用启动之后监听 Zookeeper中的路由节点,一旦发生变化就会更新内部缓存。
31、这是一个真正发消息的接口,实现的效果就是其中一个客户端发消息,其余所有客户端都能收到!
32、流程肯定是客户端发送一条消息到服务端,服务端收到后在上文介绍的 SessionSocketHolder中遍历所有 Channel(通道)然后下发消息即可。
33、服务端是单机倒也可以,但现在是集群设计。所以所有的客户端会根据之前的轮询算法分配到不同的 cim-server实例中。
34、因此就需要路由层来发挥作用了。
35、路由接口收到消息后首先遍历出所有的客户端和服务实例的关系。
36、路由关系在 Redis中的存放如下:
37、由于 Redis单线程的特质,当数据量大时;一旦使用 keys匹配所有 cim-route:*数据,会导致 Redis不能处理其他请求。
38、所以这里改为使用 scan命令来遍历所有的 cim-route:*。
39、接着会挨个调用每个客户端所在的服务端的 HTTP接口用于推送消息。
40、 cim-server收到消息后会在内部缓存中查询该 userID的通道,接着只需要发消息即可。
41、这是一个辅助接口,可以查询出当前在线用户信息。
42、实现也很简单,也就是查询之前保存”用户登录状态的那个去重 set“即可。
43、之所以说获取在线用户是一个辅助接口,其实就是用于辅助私聊使用的。
44、一般我们使用私聊的前提肯定得知道当前哪些用户在线,接着你才会知道你要和谁进行私聊。
45、在我们这个场景中,私聊的前提就是需要获得在线用户的 userID。
46、所以私聊接口在收到消息后需要查询到接收者所在的 cim-server实例信息,后续的步骤就和群聊一致了。调用接收者所在实例的 HTTP接口下发信息。
47、只是群聊是遍历所有的在线用户,私聊只发送一个的区别。
48、一旦客户端下线,我们就需要将之前存放在 Redis中的一些信息删除掉(路由信息、登录状态)。
49、客户端中的一些逻辑其实在上文已经谈到一些了。
50、第一步也就是登录,需要在启动时调用 route的登录接口,获得 cim-server信息再创建连接。
51、登录过程中 route接口会判断是否为重复登录,重复登录则会直接退出程序。
52、接下来是利用 route接口返回的 cim-server实例信息(ip+port)创建连接。
53、最后一步就是发送一个登录标志的信息到服务端,让它保持客户端和 Channel的关系。
54、上文提到的一些登录报文、真正的消息报文这些其实都是在我们自定义协议中可以区别出来的。
55、由于是使用 Google Protocol Buffer编解码,所以先看看原始格式。
56、其实这个协议中目前一共就三个字段:
57、目前主要是三种类型,分别对应不同的业务:
58、为了保持客户端和服务端的连接,每隔一段时间没有发送消息都需要自动的发送心跳。
59、目前的策略是每隔一分钟就是发送一个心跳包到服务端:
60、这样服务端每隔一分钟没有收到业务消息时就会收到 ping的心跳包:
61、客户端也内置了一些基本命令来方便使用。
62、比如输入:q就会退出客户端,同时会关闭一些系统资源。
63、当输入:olu(onlineUser的简写)就会去调用 route的获取所有在线用户接口。
64、群聊的使用非常简单,只需要在控制台输入消息回车即可。
65、这时会去调用 route的群聊接口。
66、私聊也是同理,但前提是需要触发关键字;使用 userId;;消息内容这样的格式才会给某个用户发送消息,所以一般都需要先使用:olu命令获取所以在线用户才方便使用。
67、为了满足一些定制需求,比如消息需要保存之类的。
68、所以在客户端收到消息之后会回调一个接口,在这个接口中可以自定义实现。
69、因此先创建了一个 caller的 bean,这个 bean中包含了一个 CustomMsgHandleListener接口,需要自行处理只需要实现此接口即可。
70、由于我自己不怎么会写界面,但保不准有其他大牛会写。所以客户端中的群聊、私聊、获取在线用户、消息回调等业务(以及之后的业务)都是以接口形式提供。
71、也方便后面做页面集成,只需要调这些接口就行了;具体实现不用怎么关心。
72、 cim目前只是第一版,BUG多,功能少(只拉了几个群友做了测试);不过后续还会接着完善,至少这一版会给那些没有相关经验的朋友带来一些思路。
73、欢迎工作一到五年的Java工程师朋友们加入Java程序员开发: 721575865
74、群内提供免费的Java架构学习资料(里面有高可用、高并发、高性能及分布式、Jvm性能调优、Spring源码,MyBatis,Netty,Redis,Kafka,Mysql,Zookeeper,Tomcat,Docker,Dubbo,Nginx等多个知识点的架构资料)合理利用自己每一分每一秒的时间来学习提升自己,不要再用"没有时间“来掩饰自己思想上的懒惰!趁年轻,使劲拼,给未来的自己一个交代!
三、什么是java五大框架
Spring是一个轻量级的DI和AOP容器框架。
说它轻量级有一大部分原因是相对与EJB的(虽然本人从没有接触过EJB的应用),重要的是,Spring是非侵入式的,基于spring开发的应用一般不依赖于spring的类。
2、Struts2是一个相当强大的Java Web开源框架,是一个基于POJO的Action的MVC Web框架。它基于当年的Webwork和XWork框架,继承其优点,同时做了相当的改进。Struts2现在在Java Web开发界的地位可以说是大红大紫,从开发人员的角度来分析,Struts2之所以能够如此的深入开发人员之心,与其优良的设计是分不开的。