维护真实时间:应对系统时间篡改的技巧

引言

在App使用中,由于系统时间用户可以随意更改,在某些特殊情况下会导致获取到的系统时间不正确问题。本篇代码使用dart语言进行相关描述。

1.问题分析:
手机系统时间 ≠ 真实时间,当我们做一些需要对时间精度和准确性要求较高的软件时,如果只通过调用系统API,获取到的时间不一定是真实的,那么就需要我们单独去维护一个真实的时间,下面主要分析了连网情况下和断网情况下两种时间维护方案。

2.方案一:连网情况下获取网络时间:
既然连网了,从网络获取时间就行了,获取到的时间与本地保存的时间同步,当然响应数据有一定的延时,如果想要特别精确再单独加上响应时间就行了。
Dart代码示例如下:

///获取网络时间
  Future<DateTime?> _getNetworkTime() async {
    try {
      final response = await HttpUtil.getInstance().get(apiUrl);

      if (response.statusCode == 200) {
        final Map<String, dynamic> responseData = json.decode(response.data);
        final String dateTimeString = responseData['datetime'];
        final DateTime networkTime = DateTime.parse(dateTimeString).toLocal();
        return networkTime;
      } else {
        print('Handle the response error here');
        return null;
      }
    } catch (e) {
      print('Handle any exceptions that may occur  $e');
      return null;
    }
  }

3.方案二:断网情况下本地时间维护:(重点)
既然断网了,方案一就一点用没了,此时有两种方法获取时间,一种是调用系统api,一种是获取本地维护的时间,我们知道系统时间是可以修改的,所以你获取系统时间的话,得到的不一定是正确的时间,那么只能从本地我们自己维护的时间去拿,那么问题来了本地时间要怎么去维护呢?
我们可以在应用启动的时候起个定时器1秒钟循环一次,对本地时间进行累加,不受系统时间影响,前提是本地第一次保存是时间必须是真实的时间系统时间不可靠,那么就把第一次从网络上获取的时间去保存下来供后续使用。需要注意的是这种情况下难免会有误差!

time2 = Timer.periodic(const Duration(seconds: 1), (timer) {
      if (!isSpite) {
        CacheUtil.saveRealTime(CacheUtil.getLastRealTime() + 1000);
      }
    });

上面的代码是定时器一秒钟循环一次,每次对上一次保存的时间进行累加,也就是我们自己维护的时间,这里的1000写死了,细微误差暂不考虑在内。
当然这只是开始,如果在你应用使用中突然断网了,并且系统时间还被修改了,上面方法还可以维护一个真实的时间,那么如果断网后,你的程序退出了,而且系统时间还被修改了,该去怎么去保证时间的真实性呢?

4.逻辑设计:
这里我使用的方法是,在上面saveRealTime 中我们每次保存的不单是真实时间,还保存的一个系统时间的毫秒数。
///保存一次真实时间及一次系统时间(两次时间不一定相同)

  static void saveRealTime(int timeMillis) {
    SpUtil.putInt('timeMillis', timeMillis);
    SpUtil.putInt('systemTimeMillis', DateTime.now().millisecondsSinceEpoch);
  }

当程序退出时,systemTimeMillis 保存了上次退出时的系统时间,当下次启动应用时,获取当前系统时间,与上次保存的系统时间之差就是这段空缺的时间段,然后继续与本地的真实时间进行累加以达到获取维护真实时间的目的。

if (currTime - CacheUtil.getLastSystemTime() > 2000) {
          //当前系统时间 大于上次保存的时间(断网退出app时 保存的一次时间)
          //此处有个问题,用于修改时间大于当前时间(此时不用考虑)
          isSpite = false;
          var timeDiff = currTime - CacheUtil.getLastSystemTime();
          CacheUtil.saveRealTime(CacheUtil.getLastRealTime() + timeDiff);
        }

上面方法只适用 系统时间被修改一次的情况,至于为什么要大于2000毫秒,是因为上面还有个一秒循环一次的定时器在维护时间,只有程序退出了这两数之差才可能大于2000,因为这两个定时器是并行的,为了避免小概率事件大于1000有时可能会有问题!
当用户在程序退出后再断网,然后修改系统时间,此时上述方法就无力回天了,只能禁用与时间相关的所有功能,待时间恢复正常(连网…)再恢复相关功能使用。

if (currTime - CacheUtil.getLastSystemTime() < 0) {
          //当前时间小于上次保存的时间,代表时间被恶意修改,禁用所有与时间相关的功能,
          // 直到连网或者修改系统时间大于当前时间
          isSpite = true;
        }

下面是完整代码示例:

import 'dart:async';
import 'dart:convert';

import '../../http/httpUtil.dart';
import '../cache.dart';

///
/// 此类内部维护一个真实时间,由于系统时间可被修改,所以系统时间仅供参考
///
/// 用来获取当前真实时间
///
const String apiUrl = 'https://worldtimeapi.org/api/ip';

class RealTimeUtil {
  late Timer time;
  late Timer time2;

  //是否可以使用系统时间
  bool canUseSystem = false;

  //时间是否被恶意篡改
  bool isSpite = false;

  // 使用静态变量_instance来存储单例实例
  static RealTimeUtil? _instance;

  // 私有构造函数,防止外部实例化
  RealTimeUtil._() {
    init();
  }

  // 工厂构造函数,用于返回单例实例
  factory RealTimeUtil() {
    // 如果实例尚未初始化,则创建一个新实例
    _instance ??= RealTimeUtil._();
    return _instance!;
  }

  void init() async {
    _task();
    time = Timer.periodic(const Duration(seconds: 10), (timer) {
      _task();
    });
    time2 = Timer.periodic(const Duration(seconds: 1), (timer) {
      if (!isSpite) {
        CacheUtil.saveRealTime(CacheUtil.getLastRealTime() + 1000);
      }
    });
  }

  _task() {
    _getNetworkTime().then((DateTime? dateTime) {
      if (dateTime != null) {
        //获取网络时间成功
        isSpite = false;
        if ((dateTime.millisecondsSinceEpoch - DateTime.now().millisecondsSinceEpoch).abs() > 5000) {
          //网络时间大于系统时间5s 代表本地时间不正确, 5s是个阈值,视实际情况而定
          canUseSystem = false;
          CacheUtil.saveRealTime(dateTime.millisecondsSinceEpoch);
        } else {
          canUseSystem = true;
          CacheUtil.saveRealTime(DateTime.now().millisecondsSinceEpoch);
        }
      } else {
        //获取网络时间失败
        var currTime = DateTime.now().millisecondsSinceEpoch;

        print(
            'test 获取网络时间失败  currTime  = ${currTime - CacheUtil.getLastSystemTime()}  isSpite = $isSpite');

        if (currTime - CacheUtil.getLastSystemTime() > 2000) {
          //当前系统时间 大于上次保存的时间(断网退出app时 保存的一次时间)
          //此处有个问题,用于修改时间大于当前时间(此时不用考虑)
          isSpite = false;
          var timeDiff = currTime - CacheUtil.getLastSystemTime();
          CacheUtil.saveRealTime(CacheUtil.getLastRealTime() + timeDiff);
        } else if (currTime - CacheUtil.getLastSystemTime() < 0) {
          //当前时间小于上次保存的时间,代表时间被恶意修改,禁用所有与时间相关的功能,
          // 直到连网或者修改系统时间大于当前时间
          isSpite = true;
        }
      }
    });
  }

  ///获取真实时间的唯一路径
  DateTime getRealTime() {
    int lastRealTimeMillis = CacheUtil.getLastRealTime();

    // 创建一个DateTime对象,需要将时间戳转换为DateTime
    DateTime realTime = DateTime.fromMillisecondsSinceEpoch(lastRealTimeMillis);
    return realTime;
  }

  ///获取网络时间
  Future<DateTime?> _getNetworkTime() async {
    try {
      final response = await HttpUtil.getInstance().get(apiUrl);

      if (response.statusCode == 200) {
        final Map<String, dynamic> responseData = json.decode(response.data);
        final String dateTimeString = responseData['datetime'];
        final DateTime networkTime = DateTime.parse(dateTimeString).toLocal();
        return networkTime;
      } else {
        print('Handle the response error here');
        return null;
      }
    } catch (e) {
      print('Handle any exceptions that may occur  $e');
      return null;
    }
  }
}

5.总结:
当然上述方法,是以系统时间为参考进行的补救措施,如果使用的是系统启动时间,而不是系统时间为参考,上面问题就迎刃而解了。这里没有尝试该方法,我主要是给大家提供一个思路,而且博主能力有限,很多细节可能未考虑在内,大家有好的方法也可以提出来,共同学习,一起进步。

  • 6
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
可以使用 JavaScript 中的 `Date` 对象来获取当前的系统时间,然后与服务器的时间做比较,如果差值超过一个阈值,就认为系统时间篡改。以下是一个示例函数代码: ```javascript function checkSystemTime() { // 获取当前的系统时间 var currentTime = new Date().getTime(); // 向服务器请求服务器时间 wx.request({ url: 'https://your.server.com/getTime', success: function(res) { // 得到服务器时间 var serverTime = res.data.time; // 计算与服务器时间的差值 var diff = Math.abs(serverTime - currentTime); // 如果差值超过一个阈值,就认为系统时间篡改 if (diff > 10000) { wx.showModal({ title: '系统时间篡改', content: '请设置正确的系统时间后再运行小程序', showCancel: false }); } else { // 系统时间正常,允许运行小程序 // TODO: 在这里写你的小程序代码 } }, fail: function() { // 请求服务器时间失败,无法判断系统时间是否被篡改 wx.showModal({ title: '无法判断系统时间', content: '请检查网络连接后再运行小程序', showCancel: false }); } }); } ``` 你可以把这个函数放在小程序的入口文件中(一般是 `app.js` 或 `index.js`),然后在小程序启动时调用它,例如: ```javascript App({ onLaunch: function() { checkSystemTime(); } }); ``` 当用户启动小程序时,该函数会向服务器请求当前的时间,并进行比较。如果系统时间篡改,就会弹出提示框,阻止用户继续运行小程序;否则,就会继续执行你的小程序代码。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值