在移动应用开发中,Flutter已经成为一种非常流行的技术选项,可以同时在Android和iOS平台上构建高性能、高质量的移动应用程序。但是,由于其跨平台特性,Flutter应用程序也面临着一些安全风险,例如反编译、代码泄露、数据泄露等问题。为了保护Flutter应用程序的安全性,开发者需要进行加固,提供更加安全的应用程序给用户使用。
引言
Flutter作为一种新兴的移动应用程序开发技术,其应用越来越广泛,对于保障移动应用的安全性至关重要。针对Flutter应用程序的安全问题,本文介绍了Flutter应用程序的加固原理,包括代码混淆、资源加密、安全存储、防止动态调试和Hook、漏洞修复等方面的技术和方法。通过学习本文,开发者可以更好地保护Flutter应用程序的安全性,提供更加安全的应用程序给用户使用。
正文
1. 代码混淆
Flutter应用程序的核心代码是用Dart编写的,并且在构建过程中会被转化为机器码。为了防止反编译和代码泄露,开发者可以使用代码混淆工具对代码进行加密。代码混淆通过改变代码结构、篡改命名奖、方法和变量的名称,以及添加无用的代码和控制流程混淆等方式来使得代码难以理解和分析,从而提高反编译的难度。
以下是一段示例代码:
dartCopy Code
class Person { String name; int age; void sayHello() { print("Hello, my name is $name, I'm $age years old."); } } void main() { Person p = Person(); p.name = "Tom"; p.age = 18; p.sayHello(); }
通过代码混淆,上面的代码可能会被转化为如下所示的混淆代码:
dartCopy Code
可以发现,代码混淆后的代码难以理解和分析,从而提高了反编译的难度。
2. 资源加密
Flutter应用程序的资源文件包括图片、音视频等,这些文件是开放的,容易被恶意攻击者获取和利用。为了保护这些资源文件,开发者可以使用资源加密技术对文件进行加密,并在运行时动态解密使用。资源加密可以使用对称或非对称加密算法,通过密钥对文件进行加密和解密,以保护文件的完整性和机密性。
以下是一段示例代码:
dartCopy Code
3. 安全存储
Flutter应用程序可能需要存储用户的敏感数据,例如用户的个人信息、账号密码等。为了保护这些敏感数据的安全性,开发者可以使用安全存储技术对数据进行加密和存储。安全存储可以使用加密算法对数据进行加密,并将加密后的数据存储在本地存储或云端,以防止数据泄露和被恶意攻击者获取。
以下是一段示例代码:
- import 'dart:convert';
- import 'package:crypto/crypto.dart';
-
- String key = "1234567890";
- String plaintext = "Hello, world!";
- String ciphertext = "";
-
- void main() {
- // 对称加密
- var bytes = utf8.encode(key);
- var digest = sha256.convert(bytes);
- String encrypted = AesCrypt.encrypt(plaintext, digest.toString());
- print("encrypted: $encrypted");
-
- // 对称解密
- String decrypted = AesCrypt.decrypt(encrypted, digest.toString());
- print("decrypted: $decrypted");
- }
dartCopy Code
import 'dart:convert'; import 'package:crypto/crypto.dart'; String key = "1234567890"; String plaintext = "Hello, world!"; String ciphertext = ""; void main() { // 加密 var bytes = utf8.encode(key); var digest = sha256.convert(bytes); ciphertext = AesCrypt.encrypt(plaintext, digest.toString()); print("ciphertext: $ciphertext"); // 存储 SharedPreferences prefs = await SharedPreferences.getInstance(); await prefs.setString("data", ciphertext); // 解密 String data = prefs.getString("data"); String decrypted = AesCrypt.decrypt(data, digest.toString()); print("decrypted: $decrypted"); }
在上述案例中,使用AES对称加密算法对敏感数据进行了加密,并输出了加密后的结果。
我们加密的时候可以使用ipaguard工具进行加密处理,Ipa Guard是一款功能强大的ipa混淆工具,不需要ios app源码,直接对ipa文件进行混淆加密。可对IOS ipa 文件的代码,代码库,资源文件等进行混淆保护。 可以根据设置对函数名、变量名、类名等关键代码进行重命名和混淆处理,降低代码的可读性,增加ipa破解反编译难度。可以对图片,资源,配置等进行修改名称,修改md5。只要是ipa都可以,不限制OC,Swift,Flutter,React Native,H5类app。
4. 防止动态调试和Hook
在运行时,Flutter应用程序可能会被反编译、调试甚至被攻击者进行Hook操作,修改应用程序的行为。为了防止这些攻击,开发者可以使用动态调试和Hook检测技术进行防御。在应用程序中集成代码检测库,可以检测运行时的调试和Hook操作,并采取相应的防御措施,例如直接退出应用程序或者修改应用程序的行为。
以下是一段示例代码:
dartCopy Code
import 'package:flutter/foundation.dart'; import 'package:flutter/services.dart'; void main() { // 检测调试 if (kDebugMode) { SystemChannels.platform.invokeMethod('SystemNavigator.pop'); } // 检测Hook final isHooked = await FlutterNativeDetector.detectHook(); if (isHooked) { SystemChannels.platform.invokeMethod('SystemNavigator.pop'); } }
5. 漏洞修复
Flutter应用程序的安全性还与所使用的框架和库有关,这些框架和库可能存在安全漏洞,被攻击者利用来进行攻击。为了防止这种攻击,开发者需要及时更新使用的框架和库,并修复其中的漏洞。在应用程序中使用最新版本的框架和库,可以大幅度降低应用程序被攻击的风险。
总结
Flutter应用程序的加固原理主要包括代码混淆、资源加密、安全存储、防止动态调试和Hook、漏洞修复等方面。通过应用这些原理和技术,开发者可以提高Flutter应用程序的安全性,保护用户的敏感数据和应用程序的完整性,提供更加安全的应用程序给用户使用。
参考资料
-
Flutter官方网站:Flutter - Build apps for any screen
-
ipaguard:ipaguard
-
flutter_secure_storage插件文档:flutter_secure_storage | Flutter Package
希望本篇博客对理解Flutter加固原理有所帮助。如果有任何问题或疑问,请随时提出。
- Swift Combine 学习(一):Combine 初印象
- Swift Combine 学习(二):发布者 Publisher
- Swift Combine 学习(三):Subscription和 Subscriber
- Swift Combine 学习(四):操作符 Operator
- Swift Combine 学习(五):Backpressure和 Scheduler
- Swift Combine 学习(六):自定义 Publisher 和 Subscriber
- Swift Combine 学习(七):实践应用场景举例
引言
在上一篇文章中,初步简单的介绍了 Combine 框架的基本概念,大概有了一个初印象。本文将开始介绍 Combine 中的发布者(Publisher)。Publisher 是 Combine 框架的核心组件之一,负责生成和传递数据流。通过理解 Publisher 的类型、特性和使用方法,可以更好地在 Combine 中生成和管理数据流。
发布者 (Publisher
)
Declares that a type can transmit a sequence of values over time.
声明一个类型可以随着时间推移传输一系列的值。
发布者(Publisher
)是 Combine 框架中的核心概念之一,它定义如何生成并传递一系列值。像是观察者模式中的 Observable。它可以使用 operator
来组合变换,生成新的 Publisher
。发布者会随时间的推移将一系列值发送给一个或多个订阅者 Subscriber
。发布者遵循 Publisher
协议,该协议定义了两个关联类型:
Output
:发布者发送出的值。Failure
:发布者可能产生的错误,遵循Error
协议。
swift 代码解读复制代码public protocol Publisher<Output, Failure> {
/// The kind of values published by this publisher.
/// 此发布者发布的值的类型
associatedtype Output
/// The kind of errors this publisher might publish.
/// 此发布者可能发布的错误类型
/// Use `Never` if this `Publisher` does not publish errors.
/// 如果这个发布者不会发错误,用 Never
associatedtype Failure : Error
/// Attaches the specified subscriber to this publisher.
/// 将指定的订阅者附加给此发布者
/// Implementations of ``Publisher`` must implement this method.
///
/// The provided implementation of ``Publisher/subscribe(_:)-4u8kn``calls this method.
///
/// - Parameter subscriber: The subscriber to attach to this ``Publisher``, after which it can receive values.
func receive<S>(subscriber: S) where S : Subscriber, Self.Failure == S.Failure, Self.Output == S.Input
}
extension Publisher {
/// Attaches the specified subject to this publisher.
/// 将指定的 Subject 订阅到一个 Publisher
/// - Parameter subject: The subject to attach to this publisher.
public func subscribe<S>(_ subject: S) -> AnyCancellable where S : Subject, Self.Failure == S.Failure, Self.Output == S.Output
}
extension Publisher {
/// Specifies the scheduler on which to receive elements from the publisher.指定用于接收发布者元素的调度器。
///
/// You use the ``Publisher/receive(on:options:)`` operator to receive results and completion on a specific scheduler, such as performing UI work on the main run loop.你可以使用 ``Publisher/receive(on:options:)`` 操作符在特定的调度器上接收结果和完成信号,比如在主运行循环上执行 UI 工作。
///
/// In contrast with ``Publisher/subscribe(on:options:)``, which affects upstream messages, ``Publisher/receive(on:options:)`` changes the execution context of downstream messages. 与影响上游消息的 ``Publisher/subscribe(on:options:)`` 相比,``Publisher/receive(on:options:)`` 改变的是下游消息的执行上下文。
///
/// - Parameters:
/// - scheduler: The scheduler the publisher uses for element delivery.发布者用于传递元素的调度器。
/// - options: Scheduler options used to customize element delivery.用于自定义元素传递的调度器选项。
/// - Returns: A publisher that delivers elements using the specified scheduler.返回一个使用指定调度器传递元素的发布者。
public func receive<S>(on scheduler: S, options: S.SchedulerOptions? = nil) -> Publishers.ReceiveOn<Self, S> where S : Scheduler
}
... 其他很多 Publisher 的 extension ...
Publisher
通过 receive
来接受订阅。(subscriber: S)
一个发布者可以发布多个值,可以有两种可能的状态:成功或失败。在成功状态下,发布者会发送 Output
类型的值;在失败状态下,发布者则会发送 Failure
类型的错误。如果发布者不会失败,Failure
类型通常会被设置为 Never
,表示不会产生错误。
Combine 框架内置了很多发布者,包括 Just
、Future
、Deferred
、Empty
、Fail
、Record
以及 PassthroughSubject
和 curValueSubject
。
一些 Publisher
举例:
-
Future
:-
用于表示一个异步操作,该操作最终会产生一个值或一个错误。
-
在以下例子中模拟一个异步操作,随机决定成功或失败。
less代码解读复制代码let futureP = Future<Int, Error> { promise in DispatchQueue.main.asyncAfter(deadline: .now() + 1) { let success = Bool.random() if success { promise(.success(2)) } else { promise(.failure(NSError(domain: "ExampleError", code: 0, userInfo: nil))) } } }
-
-
Just
:-
创建一个只发出单个值然后立即完成的 Publisher。
ini代码解读复制代码let justP = Just("Hello, World!")
-
-
Deferred
:-
延迟创建 Publisher 直到有订阅者订阅。
bash代码解读复制代码let deferredPublisher = Deferred { Just("Hello, World!") }
-
ConnectablePublisher
ConnectablePublisher
可以让开发者控制数据流的开始发送时机。通常情况下,Publisher
在有订阅者时会立即开始发送数据,但 ConnectablePublisher
可以通过调用 connect()
来显式启动数据流。这在需要同步多个订阅者的场景中非常有用,例如网络请求或数据库查询。
比如当多个订阅者订阅了同一个非 ConnectablePublisher
的 Publisher
,有可能会出现其中一个订阅者收到了订阅内容,而另外一个订阅者却没收到的情况。这时候就可以使用 使用 makeConnectable()
和 connect()
控制发布。
scss 代码解读复制代码import Combine
class PublisherExample {
private var cancellables = Set()
func demonstratePublishers() {
// 1. Publisher
print("🍎普通 Publisher🍎")
let numbers = PassthroughSubject()
// 第一个订阅者
numbers.map { value -> Int in
print("处理数据: (value)")
return value * 2
}.sink { value in
print("订阅者A: (value)")
}.store(in: &cancellables)
// 第二个订阅者
numbers.map { value -> Int in
print("处理数据: (value)")
return value * 2
}.sink { value in
print("订阅者B: (value)")
}.store(in: &cancellables)
// 发送数据
numbers.send(1)
numbers.send(2)
// 2. ConnectablePublisher
print("\n🍎ConnectablePublisher🍎")
let subject = PassthroughSubject()
let connectablePublisher = subject.map { value -> Int in
print("处理数据: (value)")
return value * 2
}.makeConnectable()
// 第一个订阅者
connectablePublisher.sink { value in
print("订阅者A: (value)")
}.store(in: &cancellables)
// 第二个订阅者
connectablePublisher.sink { value in
print("订阅者B: (value)")
}.store(in: &cancellables)
// 连接发布者
connectablePublisher.connect().store(in: &cancellables)
// 发送数据
subject.send(1)
subject.send(2)
}
}
let exp = PublisherExample()
exp.demonstratePublishers()
/* 输出:
🍎普通 Publisher🍎
处理数据: 1
订阅者A: 2
处理数据: 1
订阅者B: 2
处理数据: 2
订阅者A: 4
处理数据: 2
订阅者B: 4
🍎ConnectablePublisher🍎
处理数据: 1
订阅者B: 2
订阅者A: 2
处理数据: 2
订阅者B: 4
订阅者A: 4
*/
-
普通 Publisher:
- 每个订阅者订阅时都会触发一次完整的数据流
- 耗时操作会被执行多次
- 订阅者获得的是独立的数据流
-
ConnectablePublisher:
- 在调用 connect() 之前不会开始发送数据
- 所有订阅者共享同一个数据流
- 耗时操作只执行一次
- 适合需要等待所有订阅者准备就绪才开始发送数据的场景
引用共享
在 Combine 中,引用共享通常是指多个订阅者(Subscriber
)共享同一个发布者(Publisher
)的输出,而不是每个订阅者都触发一次数据生成。这可以通过 .share()
操作符实现。
关于 ConnectablePublisher
和引用共享所使用的 share
操作符号,在此先不展开讲解,后面讲到操作符的时候再举例说明。
Subject
A publisher that exposes a method for outside callers to publish elements. 一个公开了方法供外部调用者发布元素的发布者。
Subject
是一个特殊的 Publisher
。它的特殊之处在于:
-
主动发送能力:
- 普通的 Publisher 只能在创建时定义数据
- Subject 通过 send(_:) 方法可以在外部主动发送值到数据流中
-
控制权的不同:
- 普通 Publisher 的数据流是由内部逻辑控制的(比如 Just、Future)
- Subject 允许外部代码通过 send 方法控制数据流
scss代码解读复制代码// 普通 Publisher:只能在创建时定义数据 let publisher = [1, 2, 3].publisher // 数据流固定 // Subject:可以随时发送新数据 let subject = PassthroughSubject
() subject.send(1) subject.send(2) subject.send(completion: .finished) // 还可以主动结束
swift 代码解读复制代码public protocol Subject<Output, Failure> : AnyObject, Publisher {
/// Sends a value to the subscriber.
///
/// - Parameter value: The value to send.
func send(_ value: Self.Output)
/// Sends a completion signal to the subscriber.
///
/// - Parameter completion: A `Completion` instance which indicates whether publishing has finished normally or failed with an error.
func send(completion: Subscribers.Completion<Self.Failure>)
/// Sends a subscription to the subscriber.
///
/// This call provides the ``Subject`` an opportunity to establish demand for any new upstream subscriptions.
///
/// - Parameter subscription: The subscription instance through which the subscriber can request elements.
func send(subscription: any Subscription)
}
Combine 内置了两种 Subject
,分别是 PassthrougSubject
和 CurrentValueSubject
。
PassthroughSubject
:
-
无初始值或当前值存储
-
只转发接收到的值给订阅者
-
适用于:
- 按钮点击等事件触发的场景
- 不需要保存状态的场景
- 一次性通知
CurrentValueSubject
:
-
必须提供初始值
-
维护一个当前值
-
新订阅者立即收到当前值
-
可通过
.value
属性读写当前值 -
适用于:
- 状态管理(如开关状态)
- 需要缓存最新值的场景
- 需要立即知道当前状态的场景
特性 | PassthroughSubject | CurrentValueSubject |
---|---|---|
初始化 | 不需要初始值 | 必须提供初始值 |
值存储 | 不存储值 | 存储最新值 |
新订阅 | 只接收订阅后的新值 | 立即接收当前值 |
值访问 | 无法访问当前值 | 可通过 value 属性访问 |
设置值 | 只能通过 send() 发送值 | 可用 send() 或 value 属性 |
swift 代码解读复制代码import Combine
import Foundation
// PassthroughSubject
let passthroughSubject = PassthroughSubject<String, Never>()
// curValueSubject
let curValueSubject = CurrentValueSubject<String, Never>("初始值")
// 订阅 PassthroughSubject
passthroughSubject
.sink { value in
print("PassthroughSubject 接收到: (value)")
}
// 发送俩值
passthroughSubject.send("第一个值")
passthroughSubject.send("第二个值")
// 订阅 curValueSubject
curValueSubject
.sink { value in
print("curValueSubject 接收到: (value)")
}
// 发送新值
curValueSubject.send("更新后的值")
//输出当前值
print("curValueSubject 当前值: (curValueSubject.value)")
// 发送另一个新值
curValueSubject.send("再次更新的值")
// 打印当前值
print("curValueSubject 当前值: (curValueSubject.value)")
/*
输出
PassthroughSubject 接收到: 第一个值
PassthroughSubject 接收到: 第二个值
curValueSubject 接收到: 初始值
curValueSubject 接收到: 更新后的值
curValueSubject 当前值: 更新后的值
curValueSubject 接收到: 再次更新的值
curValueSubject 当前值: 再次更新的值
*/
结语
Publisher 是 Combine 框架的基础组件之一,它为数据流的生成和传递提供了灵活而强大的工具。通过学习 Publisher 的工作原理和使用方式,开发者可以更有效地管理应用中的数据流。在下一篇文章中,将继续探讨介绍 Combine 中的订阅者(Subscriber)机制,进一步完善 Combine 知识体系。
评论记录:
回复评论: