維護真實時間:應對系統時間篡改的技巧

引言

在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對象,需要將時間戳轉換為DateTimeDateTime 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.總結:
當然上述方法,是以系統時間為參考進行的補救措施,如果使用的是系統啟動時間,而不是系統時間為參考,上面問題就迎刃而解了。這里沒有嘗試該方法,我主要是給大家提供一個思路,而且博主能力有限,很多細節可能未考慮在內,大家有好的方法也可以提出來,共同學習,一起進步。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/209687.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/209687.shtml
英文地址,請注明出處:http://en.pswp.cn/news/209687.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

SQL命令---修改數據庫的編碼

介紹 使用sql命令修改數據庫的編碼&#xff0c;修改為utf8mb4編碼。 命令 alter database 數據庫名稱 default character set utf8mb4;

垃圾收集算法和各種垃圾收集器的實現

深入理解Jvm虛擬機第三章 二、對象已死&#xff1f;3.2.1 引用計數算法3.2.2 可達性分析算法3.2.3 再談引用3.2.4 生存還是死亡3.2.5 回收方法區 三、垃圾收集算法3.3.1 分代收集理論3.3.2 標記-清除算法3.3.3 標記-復制算法3.3.4 標記-整理算法 四、HotSpot的算法細節實現3.4.…

單片機中的printf思考

問題: 1. printf自帶的庫編譯出來的大小比較大(flash吃緊) 2. printf是一個不定長參數, 意味著函數無法知道傳入的長度. 解決這個問題有2中方法:1.設置足夠大小的數組作為參數存儲; 2. 使用動態內存分配的方式來做(應該使用的是這個方式).(內存吃緊) 問題解釋: 1. 之前寫裸…

C# WPF上位機開發(串口界面設計)

【 聲明&#xff1a;版權所有&#xff0c;歡迎轉載&#xff0c;請勿用于商業用途。 聯系信箱&#xff1a;feixiaoxing 163.com】 如果只是把上位機看成是純軟件開發&#xff0c;本身不和硬件打交道的話&#xff0c;那么這就把上位機的操作范圍給限定死了。事實上&#xff0c;上…

多線程的使用

進程與線程 進程&#xff1a; 1、進程是指運行中的程序&#xff0c;比如我們使用QQ&#xff0c;就啟動了一個進程&#xff0c;操作系統就會為該進程分配內存空間。當我們使用微信&#xff0c;又啟動了一個進程,操作系統將為其分配新的內存空間。 2、進程是程序的一次執行過程…

數據庫系統概論期末經典大題講解(范式提升、求閉包、求主碼)

上一次我們介紹了數據庫中關系代數查詢&#xff0c;從選擇、投影到連接等操作符&#xff0c;探索了數據庫查詢 大家可以移步我的文章&#xff1a;數據庫系統概論期末經典大題講解&#xff08;用關系代數進行查詢&#xff09;-CSDN博客 今天&#xff0c;我們將繼續沿著數據庫系統…

《python每天一小段》--12 數據可視化《1》

歡迎閱讀《Python每天一小段》系列&#xff01;在本篇中&#xff0c;將使用Python Matplotlib實現數據可視化的簡單圖形。 一、概念 Matplotlib是一個流行的Python數據可視化庫&#xff0c;它提供了豐富的繪圖功能&#xff0c;可以創建各種類型的圖表&#xff0c;包括折線圖、…

Spring框架學習:Bean生命周期

目錄 SpringBean的生命周期 Bean實例屬性填充 三級緩存 常用的Aware接口 Spring IoC容器實例化Bean總結 SpringBean的生命周期 Spring Bean的生命周期是從 Bean 實例化之后&#xff0c;即通過反射創建出對象之后&#xff0c;到Bean成為一個完整對象&#xff0c;最終存儲到…

【MyBatis系列】MyBatis字符串問題

&#x1f49d;&#x1f49d;&#x1f49d;歡迎來到我的博客&#xff0c;很高興能夠在這里和您見面&#xff01;希望您在這里可以感受到一份輕松愉快的氛圍&#xff0c;不僅可以獲得有趣的內容和知識&#xff0c;也可以暢所欲言、分享您的想法和見解。 推薦:kwan 的首頁,持續學…

SpringBoot + Spring Cloud Alibaba + Nacos實現服務管理

1、參考文檔 Spring Cloud Alibaba參考文檔 https://spring-cloud-alibaba-group.github.io/github-pages/hoxton/zh-cn/index.html Spring Cloud Alibaba官方文檔 https://github.com/alibaba/spring-cloud-alibaba/wiki/ 2、引入 Alibaba 依賴 每個 SpringBoot 都有對應的…

css中2D和3D的區別

CSS中2D和3D的主要區別在于&#xff1a; 維度不同&#xff1a;2D是二維平面&#xff0c;3D是三維空間。可視角度不同&#xff1a;2D只能從一個平面角度看&#xff0c;而3D可以在多個角度上觀察。技術難度不同&#xff1a;3D效果需要更復雜的技術支持&#xff0c;如矩陣變換&am…

javascript實現Stack(棧)數據結構

上一篇文章我們理解了List這種數據結構&#xff0c;知道了它的特點和一些使用場景&#xff0c;這篇文章我們就來看一下棧這種數據結構&#xff0c;這里的棧可不是客棧哦&#xff0c;哈哈 棧其實和List非常像&#xff0c;使用javascript實現都是基于數組來實現 嘗試理解Stack …

6種常見的JS模塊打包器

前言 JS模塊打包器是一種工具&#xff0c;它可以將多個JS文件或模塊合并成一個或多個輸出文件&#xff0c;以便在瀏覽器或其他環境中使用。 JS模塊打包器的作用有&#xff1a; 優化代碼&#xff1a;通過壓縮、混淆、刪除無用代碼等方式&#xff0c;減少代碼的體積和復雜度&…

windows系統和虛擬機上ubuntu系統通過虛擬串口進行通信

本文的目的是實現windows系統和虛擬機上安裝的ubuntu通過串口進行通信。為了直觀觀測串口收發數據的內容&#xff0c;需要在windows系統和ubuntu系統使用串口助手來進行監聽。windows系統端用的監聽工具是串口助手SSCOM&#xff0c;ubuntu系統端使用的串口助手是CuteCom。 ubu…

OpenCL學習筆記(一)開發環境搭建(win10+vs2019)

前言 異構編程開發&#xff0c;在高性能編程中有重要的&#xff0c;筆者本次只簡單介紹下&#xff0c;如何搭建簡單的開發環境&#xff0c;可以供有需要的小伙伴們開發測試使用 一、獲取opencl的sdk庫 1.使用cuda庫 若本機有Nvidia的顯卡&#xff0c;在安裝cuda庫后&#x…

如何提高大模型在超長上下文的表現?Claude實驗表明加一句prompt立即提升效果~

本文來自DataLearnerAI官方網站&#xff1a;如何提高大模型在超長上下文的表現&#xff1f;Claude實驗表明加一句prompt立即提升效果~ | 數據學習者官方網站(Datalearner)https://www.datalearner.com/blog/1051701947131881 Claude 2.1版本的模型上下文長度最高拓展到200K&am…

【Flink系列四】Window及Watermark

3.1、window 在 Flink 中 Window 可以將無限流切分成有限流&#xff0c;是處理有限流的核心組件&#xff0c;現在 Flink 中 Window 可以是時間驅動的&#xff08;Time Window&#xff09;&#xff0c;也可以是數據驅動的&#xff08;Count Window&#xff09;。 Flink中的窗口…

c jpeg YUV圖片幀分割成 8*8 塊 ,與逆向把8*8還原為幀

1. 正向分割為若干8*8 塊 下面的程序為通用程序&#xff0c;可以分割任意塊 #include <stdlib.h> #include <string.h> #include <stdio.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <stdlib.h>…

如果微軟20年前開發.net core,JAVA會不會和IE一樣倒下了

可以跨平臺&#xff0c;大量類庫&#xff0c;微軟親自操刀&#xff0c;性能一流&#xff0c;因為沒有做跨平臺&#xff0c;.NET被 python,javascript等搶了一半以上市場。 如果微軟早早的推出類似.net core這樣的跨平臺語言&#xff0c;.net程序猿還會出在這樣的尷尬局面嗎眾所…

Java基礎-開發流程以及HelloWorld程序

目錄 1. Java的開發流程2. HelloWorld 1. Java的開發流程 開發Java程序&#xff0c;需要三個步驟&#xff1a;編寫代碼&#xff0c;編譯代碼&#xff0c;運行代碼 2. HelloWorld 編寫代碼 public class HelloWorld {public static void main(String[] args) {System.out.pri…