一个简单的网络验证小例子,各位大佬勿喷
突发奇想,如果一位A友找你拿一份 Working Fruits,但是你不想这位A友把你辛苦劳作、熬夜加点写出的代码分享他或她的另外一位朋友B友,也许并不是很有价值的一个小作业而已,但是就像我一样,多多少少有着私心的幼稚心理。这种想法就像你下载一个收费软件一样,所属权是你,需要授权码或者特定的方式得到你的允许,才能够运行整体代码。当然,不要破环人与人之间的关系或情感,学习为本(说笑了),教程只提供参考。
目录
一. 预备知识点
二. 常见的网络验证
三. 微验验证原理
四. 代码实现
五. 缺陷与优化
一. 预备知识点
我们需要了解MD5和RC4加密原理。MD5是一种常用的哈希算法,它可以将任意长度的数据转换为固定长度的哈希值。通过对比哈希值,我们可以验证数据的完整性和一致性。而RC4是一种流密码算法,它可以根据密钥生成伪随机流,用于对数据进行加密和解密。通过使用MD5和RC4加密算法,我们可以确保在网络传输过程中的数据安全性。
其次,我们需要了解Java反射。Java反射是一种强大的机制,它允许我们在运行时动态地获取类的信息并操作类的成员。在网络验证中,我们可以利用Java反射机制来动态加载和调用验证相关的类和方法(写好了就包装成一个JAR吧),从而实现灵活的验证逻辑。
当然,网络验证的实现涉及到更多的知识和技术,比如数据库的构建和管理,有自己的服务器自己管理数据,自主随时管理数据,但是表数据的增删改查操作会产生更多的工作量。这些都是我们在实际开发中需要考虑的问题,需要具备相应的知识储备和技能。因此,我们不如简单点,用别人写好的验证系统,直接拿来用不就是了,代码越简单越好对吧
在实际的网络验证中,我们可以借助已有的对接API来实现本地的验证。这意味着我们可以选择市面上已有的验证服务或库,并通过网络接口进行数据传输和验证。通过使用这些对接API,我们可以简化验证的实现过程,提高开发效率。
二. 常见的网络验证
在网络验证领域中,可以找到一些常见的验证案例,如T3、BS、易游、微验、大纸片等验证系统。这些系统各有特点,其中T3验证系统相对容易被破解,易游验证系统在外观上更加简单,但内部却更为复杂。考虑到教程的讲解需要,我们选择使用微验验证管理系统来进行讲解。
微验验证管理系统以其独特的特点和功能而备受易语言开发者关注。它提供了一种简单而高效的验证解决方案,适用于各种场景和需求。通过微验验证管理系统,确保验证的安全性和可靠性。
相比于其他验证系统,微验验证管理系统在设计上更加注重用户体验和易用性。它采用了简化的外观设计,使用户能够快速上手并进行验证操作。同时,微验验证管理系统内部的复杂性也为用户提供了更多的灵活性和定制化选项。
本教程将重点介绍如何使用微验验证管理系统进行验证操作。通过详细的讲解和示例,本教程将帮助自身了解验证的基本原理和操作步骤。读者可以学习如何配置和管理微验验证系统,以及如何应用验证功能到项目中。
三. 微验验证原理
先不管加密如何加密,解密如何解密,搞清楚这个验证流程,话不多说,看图。
四. 代码实现
官方提供的Java对接例子只有不能就地运行、同时客户端不具有安卓特定的imei码,否则验证无法过签名,需要实际开发打包成APP才可使用,因此,我二改了官方提供的代码,可以就地运行。
微验官方已经写好的rc4加密解密方法,可直接使用
需要具体看懂吗,不需要,只需要明白每个方法的作用,具体怎么实现的不重要,我还是更喜欢面向对象
RC4加密算法工具类详解
encryRC4String
方法:将给定的字符串数据使用RC4算法进行加密,并返回加密后的十六进制字符串形式。参数包括待加密的数据、密钥和字符集。
encryRC4Byte
方法:将给定的字符串数据使用RC4算法进行加密,并返回加密后的字节数组形式。参数包括待加密的数据、密钥和字符集。
decryRC4
方法:将给定的十六进制字符串数据使用RC4算法进行解密,并返回解密后的字符串形式。参数包括待解密的数据、密钥和字符集。
initKey
方法:根据给定的密钥生成RC4算法所需的初始密钥。返回一个字节数组。
bytesToHex
方法:将字节数组转换为十六进制字符串形式。返回转换后的字符串。
hexToByte
方法:将十六进制字符串转换为字节数组形式。返回转换后的字节数组。
RC4Base
方法:根据给定的输入数据和密钥使用RC4算法进行加密或解密操作。返回加密或解密后的字节数组。
属性KEK: 特定的密钥,不同的密钥产生不同的结果,不可更改
RC4工具类
import java.io.UnsupportedEncodingException;
public class RC4Util {
public static final String KEY = "PZ8KBd4TbE7mjEb";
public RC4Util() {
}
public static String encryRC4String(String data, String key, String chartSet) throws UnsupportedEncodingException {
return data != null && key != null ? bytesToHex(encryRC4Byte(data, key, chartSet)) : null;
}
public static byte[] encryRC4Byte(String data, String key, String chartSet) throws UnsupportedEncodingException {
if (data != null && key != null) {
byte[] bData;
if (chartSet != null && !chartSet.isEmpty()) {
bData = data.getBytes(chartSet);
return RC4Base(bData, key);
} else {
bData = data.getBytes();
return RC4Base(bData, key);
}
} else {
return null;
}
}
public static String decryRC4(String data, String key, String chartSet) throws UnsupportedEncodingException {
return data != null && key != null ? new String(RC4Base(hexToByte(data), key), chartSet) : null;
}
private static byte[] initKey(String aKey) {
byte[] bkey = aKey.getBytes();
byte[] state = new byte[256];
int index1;
for(index1 = 0; index1 < 256; ++index1) {
state[index1] = (byte)index1;
}
index1 = 0;
int index2 = 0;
if (bkey.length == 0) {
return null;
} else {
for(int i = 0; i < 256; ++i) {
index2 = (bkey[index1] & 255) + (state[i] & 255) + index2 & 255;
byte tmp = state[i];
state[i] = state[index2];
state[index2] = tmp;
index1 = (index1 + 1) % bkey.length;
}
return state;
}
}
public static String bytesToHex(byte[] bytes) {
StringBuffer sb = new StringBuffer();
for(int i = 0; i < bytes.length; ++i) {
String hex = Integer.toHexString(bytes[i] & 255);
if (hex.length() < 2) {
sb.append(0);
}
sb.append(hex);
}
return sb.toString();
}
public static byte[] hexToByte(String inHex) {
int hexlen = inHex.length();
byte[] result;
if (hexlen % 2 == 1) {
++hexlen;
result = new byte[hexlen / 2];
inHex = "0" + inHex;
} else {
result = new byte[hexlen / 2];
}
int j = 0;
for(int i = 0; i < hexlen; i += 2) {
result[j] = (byte)Integer.parseInt(inHex.substring(i, i + 2), 16);
++j;
}
return result;
}
private static byte[] RC4Base(byte[] input, String mKkey) {
int x = 0;
int y = 0;
byte[] key = initKey(mKkey);
byte[] result = new byte[input.length];
for(int i = 0; i < input.length; ++i) {
x = x + 1 & 255;
y = (key[x] & 255) + y & 255;
byte tmp = key[x];
key[x] = key[y];
key[y] = tmp;
int xorIndex = (key[x] & 255) + (key[y] & 255) & 255;
result[i] = (byte)(input[i] ^ key[xorIndex]);
}
return result;
}
}
接下来是通过官方提供的加密工具类自主写的对接例子类,可以通过上文中的验证流程图共同参考。
WeiYan对接类
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;
import java.math.BigInteger;
import java.net.NetworkInterface;
import java.net.SocketException;
import java.net.URL;
import java.net.URLConnection;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Enumeration;
import java.util.Random;
//TODO:Benmao
public class WeiYan {
String kami;//卡密
public String markcode;//设备码
public long time;//时间戳//Long time = System.currentTimeMillis()
public String appKey="57012";//不可更改
//"kami="+卡密+"&markcode="+设备码+"&t="+时间戳+"&"+sss.APPKEY
public String data;
public String SIGN;//通过md5加密
public int random;//随机数,范围[1000, 9999]
public String url="https://wy.llua.cn/api/?id=kmlogon"+"&app="+this.appKey+"&data=";
//加密字段MD5
public void Gm(String kami){
this.kami=kami;
this.time=System.currentTimeMillis()/1000;//Long.parseLong(String.valueOf(System.currentTimeMillis()).substring(0 ,10))
this.markcode=getDeviceID();//getDeviceID();
//需要加密的字段
String str="kami="+this.kami+"&markcode="+this.markcode+"&t="+this.time+"&"+this.appKey;
this.SIGN = stringToMD5(str);//encodeMD5(str);
}
//Rce4字段加密
public void GD(){
//随机数
this.random=new Random().nextInt(99999)+1000;//范围1000~9999
//需要加密的字段
String str="kami="+this.kami+"&markcode="+this.markcode+"&t="+this.time+"&sign="+this.SIGN+"&value"+this.time+this.random;
try {
this.data=RC4Util.encryRC4String(str, RC4Util.KEY, "UTF-8");
url+=this.data;
} catch (UnsupportedEncodingException e) {
throw new RuntimeException(e);
}
}
//返回数据解密
public String deRec4(String anUrl){
//String anUrl = "https://wy.llua.cn/api/?id=kmlogon&app=57012&data=4a5311e1cf22abb6c079de314f7f7ca9670658e0d306b87a9ab62f560e75ec31cdb0598abc2df00063003484e229ee81d0e07daf64b6d8a4cd190835bdf0c8bbd329b901888296a9a376c9bd73e62f56bce31982e072a38f8fb1fc261d43ce7231edf7c7a883bfec92c47475f0a07e3045161fcc10e5";
URL url = null;
String res ="";//最终结果
try {
url = new URL(anUrl);
URLConnection conn = url.openConnection();
BufferedReader in = new BufferedReader(new InputStreamReader(conn.getInputStream()));
String inputLine;
StringBuilder result = new StringBuilder();
while ((inputLine = in.readLine()) != null) {
result.append(inputLine);
}
//System.out.println(result);//加密前的字段
res=result.toString();
} catch (Exception e) {
throw new RuntimeException(e);
}
//解码
try {
String json = RC4Util.decryRC4(res, RC4Util.KEY, "UTF-8");
return json;
} catch (UnsupportedEncodingException e) {
throw new RuntimeException(e);
}
}
//MD5加密
public static String encodeMD5(String str)
{
try
{
MessageDigest md5 = MessageDigest.getInstance("MD5");
md5.update(str.getBytes("UTF-8"));
byte messageDigest[] = md5.digest();
StringBuilder hexString = new StringBuilder();
for (byte b : messageDigest)
{
hexString.append(String.format("%02X", b));
}
return hexString.toString().toLowerCase();
}
catch (Exception e)
{
e.printStackTrace();
}
return "";
}
//iApp Md5加密
public static String stringToMD5(String plainText)
{
byte[] secretBytes = new byte[0];
try {
secretBytes = MessageDigest.getInstance("md5").digest(plainText.getBytes());
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException(e);
}
String md5code = new BigInteger(1, secretBytes).toString(16);
for(int i = 0; i < 32 - md5code.length(); i++)
{
md5code = "0" + md5code;
}
return md5code;
}
//获取独立设备码
public static String getDeviceID() {
try {
Enumeration<NetworkInterface> networkInterfaces = NetworkInterface.getNetworkInterfaces();
while (networkInterfaces.hasMoreElements()) {
NetworkInterface networkInterface = networkInterfaces.nextElement();
byte[] mac = networkInterface.getHardwareAddress();
if (mac != null) {
StringBuilder sb = new StringBuilder();
for (byte b : mac) {
sb.append(String.format("%02X", b));
}
return sb.toString().toLowerCase();
}
}
} catch (SocketException e) {
e.printStackTrace();
}
return null;
}
}
RC4加密算法工具类详解
Gm
方法:对卡密进行加密处理,生成设备码和时间戳,并计算签名。参数为卡密。
GD
方法:对需要加密的字段进行RC4加密,并生成随机数。参数为卡密。
deRec4
方法:对返回的数据进行解密处理。参数为需要解密的URL。
encodeMD5
方法:对字符串进行MD5加密处理。参数为待加密的字符串。
stringToMD5
方法:对字符串进行MD5加密处理,并返回加密后的结果。参数为待加密的字符串。
getDeviceID
方法:获取设备的唯一标识码。
kami
(卡密)、markcode
(设备码)、time
(时间戳)、appKey
(应用密钥)、data
(加密后的数据)、SIGN
(签名)、random
(随机数)和url
(请求的URL)。
public String getKami() {
return kami;
}
public void setKami(String kami) {
this.kami = kami;
}
public String getMarkcode() {
return markcode;
}
public void setMarkcode(String markcode) {
this.markcode = markcode;
}
public long getTime() {
return time;
}
public void setTime(long time) {
this.time = time;
}
public String getAppKey() {
return appKey;
}
public void setAppKey(String appKey) {
this.appKey = appKey;
}
public String getData() {
return data;
}
public void setData(String data) {
this.data = data;
}
public String getSIGN() {
return SIGN;
}
public void setSIGN(String SIGN) {
this.SIGN = SIGN;
}
public int getRandom() {
return random;
}
public void setRandom(int random) {
this.random = random;
}
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
接下来就是写一个验证例子。上述代码编译成一个jar包,然后使用反射调用其内部方法
Main类
/**
* @作者:笨猫
* @create: 2024-03-16 14:52
**/
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
public class main {
public static void main(String[] args) {
String kami = "Benmao2127FC";//这个就是卡密
try {
// 反射调用 WeiYan()构造方法,输出this.url
Class<?> weiYanClass = Class.forName("top.benmao.rc4.WeiYan");
Constructor<?> weiYanConstructor = weiYanClass.getConstructor();
Object weiYanObject = weiYanConstructor.newInstance();
Method getUrlMethod = weiYanClass.getDeclaredMethod("getUrl");
getUrlMethod.setAccessible(true);
String url = (String) getUrlMethod.invoke(weiYanObject);
System.out.println("this.url: " + url);
// 反射调用Gm方法,并输出属性this.SIGN
Method gmMethod = weiYanClass.getDeclaredMethod("Gm", String.class);
gmMethod.setAccessible(true);
gmMethod.invoke(weiYanObject, kami);
String sign = (String) weiYanClass.getField("SIGN").get(weiYanObject);
System.out.println("this.SIGN: " + sign);
// 反射调用GD方法,再次输出this.url
Method gdMethod = weiYanClass.getDeclaredMethod("GD");
gdMethod.setAccessible(true);
gdMethod.invoke(weiYanObject);
url = (String) getUrlMethod.invoke(weiYanObject);
System.out.println("this.url: " + url);
// 反射调用deRec4()方法,获取返回值并输出
Method deRec4Method = weiYanClass.getDeclaredMethod("deRec4", String.class);
deRec4Method.setAccessible(true);
String result = (String) deRec4Method.invoke(weiYanObject, url);
System.out.println("Result: " + result);
} catch (Exception e) {
e.printStackTrace();
}
}
}
运行结果
只需要把获取的结果JSON后进入校验即可,剩下的就看自己怎么运用吧。
五. 缺陷与优化
想说的是,比起官方提供的代码,就地运行只需要懂一点点Java语言,有可能解密就只是一个变量的事情,如果需要更加安全、高效、易处理的工作方式,可以继续尝试包装成一个apk并选择各大官方的加固平台进行加固,如360加固。
本次教程结束了,感兴趣的可以关注我,如有与本博客持有不同的看法或评判,请随时留意吧。