前言:数据安全
数据的加密解密操作在 日常网络交互中经常会用到,现在密码的安全主要在于 秘钥的安全**,如论 DES 3DES AES 还是 RSA, 秘钥的算法(计算秘钥不固定) 和 保存,都决定了你的数据安全;但是常见的逆向操作** 比如 hook 加密算法 都很容易拿到 秘钥; 这个时候我们可以 回溯到 之前的 古典密码学(依赖算法本身),基本思路 置换 移位 编码 等等手段 来配合 加密算法一起使用,提高我们应用的安全;
flutter 数据加密
如果是 android 原生应用,可以 hook 系统加密代码,来获取加密秘钥,而在 flutter 中,dart 是被直接编译为 .so 文件,也就是汇编,对于低级汇编语言 可读性大大降低。当然也是可以下断点动态调试的;但是相对于难度 大大增加,我们可以结合 古典密码学 主要特点 数据安全基于算法的保密,算法不公开, 设计繁琐的算法过程,增加汇编可读性难度;
有个业务需求是:相当于是一个签到功能,每天可以领取,这里防止脚本调用使用两层 基于算法加密 和 AES加密 相结合的方式;(当然还用到了第三方的的安全软件)
以下是我上个版本加密算法设计:整体思路就是:
- 按照规则 移动字符 替换字符
- 阶段一主要是 按规则打乱字符(密文)位置
- 阶段二主要是 指定位置(密文)插入无关字符
match_request_data.dart
import 'dart:convert'; import 'package:crypto/crypto.dart'; import 'package:encrypt/encrypt.dart' as encrypt; ///匹配接口加密工具类 ///整体加密思路:按照规则 移动字符 替换字符 ///阶段一主要是 按规则打乱字符位置 ///阶段二主要是 指定位置插入无关字符 class MatchRequestData { final String gameId; final String chatSign; final String nickName; late List<int> _gameIdSort; MatchRequestData( {required this.gameId, required this.chatSign, required this.nickName}); String generateCode(String datum) { String idStr = '${int.parse(gameId)}'; List<int> searchKeywords = List<int>.generate(idStr.length, (index) => int.parse(idStr[index])); searchKeywords.sort(); _gameIdSort = searchKeywords; String base64 = _encodeBase64(datum); String base64Step0 = base64.substring(0, _gameIdSort[_gameIdSort.length - 1]) + _stepOne((base64.substring( _gameIdSort[_gameIdSort.length - 1], base64.length - _gameIdSort[3] - _gameIdSort[_gameIdSort.length - 1]))) + base64.substring(base64.length - _gameIdSort[3] - _gameIdSort[_gameIdSort.length - 1]); String _strHex = _strToHex(base64Step0); final key = encrypt.Key.fromUtf8(_generateMd5(chatSign + nickName)); final iv = encrypt.IV.fromUtf8(_ivStepOne().substring(4, 20).toUpperCase()); final encrypter = encrypt.Encrypter(encrypt.AES(key, mode: encrypt.AESMode.cbc)); final encrypted = encrypter.encrypt(_strHex, iv: iv); final encryptCode = _stepTwo(encrypted.base64); idStr = _strHex = base64 = base64Step0 = ''; return encryptCode; } String _stepOne(String str) { String res = ''; str = _inverse(str); int lastPosition = _gameIdSort[_gameIdSort.length - 1]; int count = 0; if (lastPosition % 2 == 0) { count = _gameIdSort[_gameIdSort.length - 2]; } else { count = _gameIdSort[_gameIdSort.length - 3]; } if (count == 0) { count = lastPosition; } int step = str.length ~/ count; List<String> base64Parts = []; for (int i = 0; i < count; i++) { if (i % 2 == 1) { base64Parts.add(_inverse(str.substring(step * i, step * (i + 1)))); } else { base64Parts.add(str.substring(step * i, step * (i + 1))); } } if (step * count < str.length) { if (lastPosition % 2 == 0) { base64Parts.insert(0, str.substring(step * count)); } else { base64Parts.insert(base64Parts.length, str.substring(step * count)); } } if (lastPosition % 2 == 1) { for (int i = 0; i < base64Parts.length; i++) { res = res + base64Parts[i]; } } else { for (int i = base64Parts.length - 1; i >= 0; i--) { res = res + base64Parts[i]; } } str = ''; lastPosition = count = step = -1; return res; } String _stepTwo(String data) { String res = ""; String _strHex = _strToHex(data); String _code = _inverse(_strHex); final key = encrypt.Key.fromUtf8(_generateMd5(gameId + chatSign)); final iv = encrypt.IV.fromUtf8(_ivStepTwo().substring(14, 30)); final encrypter = encrypt.Encrypter(encrypt.AES(key, mode: encrypt.AESMode.cbc)); final encrypted = encrypter.encrypt(_code, iv: iv); res = encrypted.base64; int maxLength = res.length; int indexSub = 0; int insertPos = 0; String insertStr = ''; for (int i = 1; i < _gameIdSort.length; i++) { indexSub = _gameIdSort[i] + 1; insertPos = _magic(indexSub + i) + i * 11 + i - 1; // insertStr = chatSign.substring(1,indexSub); insertStr = chatSign[indexSub]; //前面插入 if (insertPos > res.length) { insertPos = maxLength; } res = '${res.substring(0, insertPos)}$insertStr${res.substring(insertPos)}'; } _strHex = _code = ''; return res; } String _stepThree(String str) { return str; } String _inverse(String tag) { String res = ''; List<String> searchKeywords = List<String>.generate(tag.length, (index) => tag[index]); Iterable<String> array = searchKeywords.reversed; for (var e in array) { res = '$res$e'; } return res; } String _ivStepOne() { String res = ''; String map = _generateMd5(chatSign) + _generateMd5(nickName); int index = _gameIdSort[_gameIdSort.length - 2]; while (res.length < 50) { res += map[index]; index++; } index = 0; return res; } String _ivStepTwo() { String res = ''; String map = _generateMd5(_inverse(chatSign)) + _generateMd5(chatSign); int index = _gameIdSort[_gameIdSort.length - 1]; while (res.length < 50) { res += map[index]; index++; } index = 0; return _inverse(res); } /// 字符串转 十六进制 String _strToHex(String str) { List<int> charCodes = str.runes.toList(); return charCodes.map((code) => code.toRadixString(16)).join(''); } /// 字符串转 base64 String _encodeBase64(String data) { return base64Encode(utf8.encode(data)); } /// base64转 普通字符 String _decodeBase64(String data) { return String.fromCharCodes(base64Decode(data)); } String _generateMd5(String str) { return md5.convert(utf8.encode(str)).toString(); } int _magic(int num) { if (num < 3) { return 1; } else { return _magic(num - 1) + _magic(num - 2); } } }
调用的地方:
MatchRequestData data = MatchRequestData ( gameId: userArray[i]['gameID'], chatSign: userArray[i]['chatSign'], nickName: userArray[i]['nickName'], ); //需要传递给后台的 内容 Map datum = { 'inTrust': 'TRUE', 'time': DateTime.now().millisecondsSinceEpoch, 'GameID': userArray[i]['gameID'], 'nickName': userArray[i]['nickName'], 'MachineCode': md5.convert(utf8.encode(userArray[i]['gameID'])).toString(), 'sign': md5 .convert( utf8.encode(userArray[i]['gameID'] + userArray[i]['chatSign'])) .toString(), }; String res = data.generateCode(jsonEncode(datum));
服务端的数据解密:
服务端为 .net 框架:
对应于加密算法写的解密算法:
using System; using System.Collections.Generic; using System.Security.Cryptography; using System.Text; namespace ToMatch { public class MatchEncrypt { private string gameID; private string chatSign; private string nickName; private List<int> idSort; /// <summary> /// 匹配构造函数 /// </summary> /// <param name="gameId">GameID</param> /// <param name="chatSign">签名</param> /// <param name="nickName">昵称</param> public MatchEncrypt(string gameId, string chatSign, string nickName) { this.gameID = gameId; this.chatSign = chatSign; this.nickName = nickName; this.idSort = new List<int>(); string idStr = int.Parse(this.gameID).ToString(); for (int i = 0; i < idStr.Length; i++) { this.idSort.Add((int)Char.GetNumericValue(idStr[i])); } this.idSort.Sort(); } private String IvStepOne { get { String res = ""; String map = Md5Hash(chatSign) + Md5Hash(nickName); int index = idSort[idSort.Count - 2]; while (res.Length < 50) { res += map[index]; index++; } return res; } } private String IvStepTwo { get { String res = ""; String map = Md5Hash(AESHelper.Inverse(chatSign),false) + Md5Hash(chatSign,false); int index = idSort[idSort.Count - 1]; while (res.Length < 50) { res += map[index]; index++; } return AESHelper.Inverse(res); } } /// <summary> /// 解密客户端内容 /// </summary> /// <param name="code">密文</param> /// <returns></returns> public string Resolver(string code) { //第一阶段解密内容 string resStepOne = StepOne(code); if (resStepOne.Length > 0) { //Console.WriteLine("第一解密 result:" + resStepOne); //第二阶段解密 string resSteptwo = Steptwo(resStepOne); //Console.WriteLine("第二解密 result:" + resSteptwo); //Console.WriteLine(AESHelper.FromBase64(resSteptwo)); if (resSteptwo.Length > 0) { return AESHelper.FromBase64(resSteptwo); } else { return "解密失败——请记录日志.Step-2"; } } else { return "解密失败——请记录日志.Step-1"; } } private string StepOne(string code) { // 1.先移除插入的字符 // 2.再进行解密操作 int maxlength = code.Length - idSort.Count - 1; int indexSub = 0; int insertPos = 0; for (int i = 1; i < idSort.Count; i++) { indexSub = idSort[i] + 1; insertPos = magic(indexSub + i) + i * 11; //前面插入 //Console.WriteLine("前面 索引:" + i); //Console.WriteLine("前面插入位置:" + insertPos); //Console.WriteLine("前面插入字符:" + insertStr + ""); if (insertPos > code.Length) { //Console.WriteLine("修正Length:" + code.Length); Console.WriteLine("修正insertPos:" + insertPos); //Console.WriteLine("----code.Length:" + (code.Length -maxlength )); //Console.WriteLine("----code.Length:" + ( idSort.Count-1 - i)); //Console.WriteLine("----code.Length:" + ((code.Length - maxlength - (idSort.Count - 1 - i))+1)); insertPos = maxlength -4; insertPos = maxlength - ((code.Length - maxlength - (idSort.Count - 1 - i)) + 1); //Console.WriteLine("*******code.Length:" + ((code.Length - maxlength - (idSort.Count - 1 - i)) + 1)); //Console.WriteLine("*******code.Length:" + (code.Length - maxlength - (idSort.Count - i)) ); //Console.WriteLine("*******code.Length:" + (code.Length - maxlength - idSort.Count - i )); //Console.WriteLine("code.Length:" + code.Length); //Console.WriteLine("maxlength:" + maxlength); //Console.WriteLine("idSort.Count:" + idSort.Count); //Console.WriteLine("idSort.Count - i:" + (idSort.Count - i)); //Console.WriteLine("修正插入位置i:" + i); //Console.WriteLine("修正插入位置:" + insertPos); insertPos = maxlength - ((code.Length - maxlength - (idSort.Count - 1 - i)) + 1); } code = code.Substring(0, insertPos) + code.Substring(insertPos + 1); } //Console.WriteLine("整理后的:" + code); //Console.WriteLine("整理后的Length:" + code.Length); string key = Md5Hash(this.gameID + this.chatSign, false); string iv = IvStepTwo.Substring(14, 16); //第一次解密是 16进制字符串 string result = AESHelper.Decrypt(code, key, iv); return AESHelper.HexStringToString(AESHelper.Inverse(result), Encoding.UTF8); } private string Steptwo(string code) { string key = Md5Hash(this.chatSign + this.nickName, false); string iv = IvStepOne.Substring(4, 16); string base64 = AESHelper.HexStringToString(AESHelper.Decrypt(code, key, iv), Encoding.UTF8); string source = base64.Substring(0, idSort[idSort.Count - 1]) + generateMid(base64.Substring(idSort[idSort.Count - 1], base64.Length - idSort[3] - idSort[idSort.Count - 1] * 2)) + base64.Substring(base64.Length - idSort[3] - idSort[idSort.Count - 1]); //第二次解密是 base64 return source ; } private string generateMid(string str) { string res = ""; List<String> base64Parts = new List<string>(); int lastPosition = this.idSort[this.idSort.Count - 1]; string subBefore = ""; int count = 0; if (lastPosition % 2 == 0) { count = idSort[idSort.Count - 2];
e64
return source ; } private string generateMid(string str) { string res = ""; List<String> base64Parts = new List<string>(); int lastPosition = this.idSort[this.idSort.Count - 1]; string subBefore = ""; int count = 0; if (lastPosition % 2 == 0) { count = idSort[idSort.Count - 2];