theme: cyanosis
0. 本文目标
本文介绍一下如何在 Flutter 中进行 SHA256加密。并结合TolyUI 在 匠心千刃 中搭建 sha256加密的交互界面
,本文目标如下所示:
可以在输入框中输入字符串,会自动计算输入内容的sha256值:
也可以选择文件,计算该文件的sha256值:
1. SHA256能干什么
每个人的指纹是独一无二的,采集的指纹可以和犯罪嫌疑人对比是否一致,来检验凶手的身份。而不必对比案发现场的凶手和嫌疑人的每个细胞是否一致。
那对于一个文件,或说字节数组来说,有什么可以像指纹一样,能标识它的身份。让数据的唯一性既可以简洁地表达,又可以跨越时间与空间呢?sha256 加密就可以做到,当然也可以选取其他的加密方式。
理解了这一点,sha256的价值就非常明显了。比如下载文件的网站,一般都会提供该文件对应的sha值。这样在现在后,就可以根据sha值校验下载的文件是否完整。编程开发中文件上传、下载涉及前后端通信,两者可以通过sha值,来检验数据传输过程中的完整性。
2.Dart中如何使用 SHA256加密
像这样的通用算法,一般都会有三方库的支持。可以使用 crypto 库非常方便地计算一个字节数组的 sha256值。目前我的 Dart 命令行工具 toly 已经支持了 SHA256 的命令,如下所示:toly sha256 -s 可以加密若干个字符串:
toly sha256 -s 张风捷特烈 TolyUI 'Flutter Unit'
sha256.convert 方法即可加密字符数组,所以需要将文本通过 utf8
进行编码,得到 Unit8List ,然后将其作为入参进行转换。代码如下:
dart String arg = '张风捷特烈'; Uint8List data = utf8.encode(arg); Digest digest = sha256.convert(data);
toly sha256 -f 可以计算若干个文件的 sha256 值,其中 -f 可以省略:
任何文件都是字节数组,可以通过 File 对象读取得到 Uint8List 对象,然后使用 sha256.convert
即可:
dart File file = File(r'D:\Files\add.zip'); Uint8List data = await file.readAsBytes(); Digest digest = sha256.convert(data);
3. 使用Flutter搭建交互界面
命令行毕竟操作不是很方便,交互界面可以有更大的使用场景。对于点点选选的交互,不会编程的人也可以使用。在布局上,界面由两块独立的面板构成,所以可以拆分为两个组件: StringCalcPanel 和 FileCalcPanel ,分别处理字符串和文件的 sha256 计算:
然后将两者拼装在一起,放入 Sha256Page 中,作为局部路由的展示组件:
```dart class Sha256Page extends StatelessWidget { const Sha256Page({super.key});
@override Widget build(BuildContext context) { return const Scaffold( body: SingleChildScrollView( padding: EdgeInsets.symmetric(vertical: 12), child: Column( children: [ StringCalcPanel(), Divider(), FileCalcPanel(), ], ), ), ); } } ```
另外,可以注意到两个面板中的右上角图标按钮是相同的视图,可以进行封装以便复用。这里定义了 BladeToolBar 组件对动作按钮进行构建,按钮的具体交互事件是由外界决定的,需要通过回调处理。我将按键行为有枚举进行管理,如下所示:
```dart enum ToolAction { copy(TolyIcon.iconcopy), clear(TolyIcon.iconclear), ; final IconData icon; const ToolAction(this.icon);
String tooltip(BuildContext context) { return switch (this) { ToolAction.copy => '复制', ToolAction.clear => '清空', }; } } ```
这样 BladeToolBar 点击了那个按钮,就可以通过回调函数访问到对应的 ToolAction 对象,关键代码如下,通过 Wrap 组件包裹图标,并将 ToolAction 元素列表映射为 TolyAction 组件:
```dart class BladeToolBar extends StatelessWidget { final ValueChanged onTap;
const BladeToolBar({super.key, required this.onTap});
@override Widget build(BuildContext context) { return Wrap( spacing: 8, children: ToolAction.values .map((action) => TolyAction( tooltip: action.tooltip(context), child: Icon(action.icon, size: 20), onTap: () => onTap(action))) .toList(), ); } } ```
面板是上中下的竖向布局,由于面板内容需要随输入更新加密结果:
- 也就是说需要主动改变界面状态,派生 StatefulWidget 组件。
- 其中状态量有加密结果
_result
和输入框控制器_input
。 - 状态类中监听
_input
的变化,触发解析和更新_result
结果。 - 可拉伸的输入面板使用 TolyUI 中的 TolyInputArea 组件。
```dart class StringCalcPanel extends StatefulWidget { const StringCalcPanel({super.key});
@override State createState() => _StringCalcPanelState(); }
class _StringCalcPanelState extends State { final TextEditingController _input = TextEditingController();
String _result = '';
@override void initState() { input.addListener(parser); super.initState(); }
@override void dispose() { _input.dispose(); super.dispose(); }
void parser() { if(input.text.isEmpty) return; Uint8List bytes = utf8.encode(_input.text); var digest = sha256.convert(bytes); _result = digest.toString(); setState(() {}); } ```
文件计算的面板也是类似,这里就不赘述了,通过 file_picker 插件选择文件,读取数据,计算即可。
4. 大文件文件的异步计算
仔细看一下会发现,sha256.convert 是一个同步方法,这对于大文件来说是灾难性的。这就表示 1G 的文件将会被一次性读取加入内存,一方面会阻塞程序,另一方面会造成很大的内存负担。对于大文件来说,一点点地 蚕食 是非常必要的。通过源码可以看出处理 convert 方法,还有一个 startChunkedConversion 的方法:
我们可以定义内存块的大小,来一块块读取计算,如下所示,通过 async 包中的 ChunkedStreamReader 来一块块读取文件内容:
```dart import 'dart:convert'; import 'dart:io'; import 'package:async/async.dart'; import 'package:convert/convert.dart'; import 'package:crypto/crypto.dart';
Future getFileSha256(String path) async { final reader = ChunkedStreamReader(File(path).openRead()); const chunkSize = 1024 * 1024 * 2; // 2MB AccumulatorSink output = AccumulatorSink (); ByteConversionSink input = sha256.startChunkedConversion(output); try { while (true) { List chunk = await reader.readChunk(chunkSize); if (chunk.isEmpty) { break; } input.add(chunk); } } finally { reader.cancel(); } input.close(); return output.events.single; } ```
本文介绍了 Flutter 中计算 SHA256 的方式,并通过 匠心千刃 提供了交互界面。那本文就到这里,后期匠心千刃还会带来更多有趣的实用工具,敬请期待~