Android签名问题
Android平台的游戏包大多数都遇到过被破解和二次打包的问题,android平台本身的开放性,对于这种破解和二次打包的问题并没有提过多少有效的防范措施。
大多数游戏加密和防破解功能只能由开发者自行解决,apk签名验证就是一个比较有效的方案。在客户端获取到apk的签名指纹,提交到服务器进行比对,如果不是自己的签名,可以对客户端做各种功能限制。
命令行获取签名指纹
使用keytool 命令获取apk文件(app.apk)的签名指纹,输出的信息总包括md5指纹和ras指纹。
1 | keytool -list -printcert -jarfile app.apk |
keytool工具,使用keytool工具获取签名文件(.keystone文件) 的签名指纹。
1 | keytool -list -v -keystore key.keystone |
Android平台javad代码获取apk签名md5指纹
把获取签名指纹的代码放入到UnityActivity中。游戏启动后,GetSignMd5Str() 获得签名指纹之后,通过Unity的接口传入到Unity中进行验证。
但是Java层的代码很容易反编译,破解者容易修改Java代码中的签名获得函数部分的代码,或者修改签名指纹传入处的代码。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 | /** * 获取签名的MD5 指纹 * @return */ public String GetSignMd5Str() { try { PackageInfo packageInfo = mActivity.getPackageManager().getPackageInfo(mActivity.getPackageName(), PackageManager.GET_SIGNATURES); Signature[] signs = packageInfo.signatures; Signature sign = signs[0]; String signStr = EncryptionMD5(sign.toByteArray()); signStr=signStr.toUpperCase(); StringBuilder sb = new StringBuilder(); for(int i=0;i<signStr.length();++i) { if(i>0 && i %2 ==0) { sb.append(':'); } sb.append(signStr.charAt(i)); } return sb.toString(); } catch (PackageManager.NameNotFoundException e) { e.printStackTrace(); } return ""; } /** * MD5 加密 * @param byteStr * @return */ public static String EncryptionMD5(byte[] byteStr) { MessageDigest messageDigest = null; StringBuffer md5StrBuff = new StringBuffer(); try { messageDigest = MessageDigest.getInstance("MD5"); messageDigest.reset(); messageDigest.update(byteStr); byte[] byteArray = messageDigest.digest(); for (int i = 0; i < byteArray.length; i++) { if (Integer.toHexString(0xFF & byteArray[i]).length() == 1) { md5StrBuff.append("0").append(Integer.toHexString(0xFF & byteArray[i])); } else { md5StrBuff.append(Integer.toHexString(0xFF & byteArray[i])); } } } catch (NoSuchAlgorithmException e) { e.printStackTrace(); } return md5StrBuff.toString(); } |
在Unity C# 代码里面获取APK签名
相比在java代码里面获取签名指纹,在unity里面获取签名指纹安全度更高。在Unity中直接调用android 的api,对其android的签名信息,生成签名签名指纹。Unity项目尽量使用IL2CPP的方式编译,所有的C#代码都会编译成C++代码,最后生成.o文件在android端运行。很大程度上加大破解的难度。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 | /// <summary> /// 获取Android APK 的MD5 签名指纹 /// </summary> /// <returns>The signature M d5 hash.</returns> private string GetSignatureMD5Hash() { //Debug.Log ("GetSignatureMD5Hash"); var player = new AndroidJavaClass("com.unity3d.player.UnityPlayer"); var activity = player.GetStatic<AndroidJavaObject>("currentActivity"); var PackageManager = new AndroidJavaClass("android.content.pm.PackageManager"); var packageName = activity.Call<string>("getPackageName"); var GET_SIGNATURES = PackageManager.GetStatic<int>("GET_SIGNATURES"); var packageManager = activity.Call<AndroidJavaObject>("getPackageManager"); var packageInfo = packageManager.Call<AndroidJavaObject>("getPackageInfo", packageName, GET_SIGNATURES); var signatures = packageInfo.Get<AndroidJavaObject[]>("signatures"); if(signatures != null && signatures.Length > 0) { byte[] bytes = signatures[0].Call<byte[]>("toByteArray"); var md5String = GetMD5Hash(bytes); md5String = md5String.ToUpper (); StringBuilder sb = new StringBuilder(); for (int i = 0; i < md5String.Length; ++i) { if(i>0 && i %2 ==0) { sb.Append(':'); } sb.Append (md5String[i]); } return sb.ToString (); } return null; } private string GetMD5Hash(byte[] bytedata) { //Debug.Log ("GetMD5Hash"); try { System.Security.Cryptography.MD5 md5 = new System.Security.Cryptography.MD5CryptoServiceProvider(); byte[] retVal = md5.ComputeHash(bytedata); StringBuilder sb = new StringBuilder(); for (int i = 0; i < retVal.Length; i++) { sb.Append(retVal[i].ToString("x2")); } return sb.ToString(); } catch (Exception ex) { throw new Exception("GetMD5Hash() fail,error:" + ex.Message); } } |
THE END
签名验证很大程度解决了二次打包破解的问题,但是还有很多破击方式没法处理,比如钩子hook破解,内存修改....