0. 本文目标
本文介绍一下如何在 Flutter 中进行 SHA256加密。并结合TolyUI 在 匠心千刃 中搭建 sha256加密的交互界面
,本文目标如下所示:
可以在输入框中输入字符串,会自动计算输入内容的sha256值:文章源自灵鲨社区-https://www.0s52.com/bcjc/flutterjc/17109.html
文章源自灵鲨社区-https://www.0s52.com/bcjc/flutterjc/17109.html
也可以选择文件,计算该文件的sha256值:文章源自灵鲨社区-https://www.0s52.com/bcjc/flutterjc/17109.html
文章源自灵鲨社区-https://www.0s52.com/bcjc/flutterjc/17109.html
1. SHA256能干什么
每个人的指纹是独一无二的,采集的指纹可以和犯罪嫌疑人对比是否一致,来检验凶手的身份。而不必对比案发现场的凶手和嫌疑人的每个细胞是否一致。文章源自灵鲨社区-https://www.0s52.com/bcjc/flutterjc/17109.html
那对于一个文件,或说字节数组来说,有什么可以像指纹一样,能标识它的身份。让数据的唯一性既可以简洁地表达,又可以跨越时间与空间呢?sha256 加密就可以做到,当然也可以选取其他的加密方式。文章源自灵鲨社区-https://www.0s52.com/bcjc/flutterjc/17109.html
理解了这一点,sha256的价值就非常明显了。比如下载文件的网站,一般都会提供该文件对应的sha值。这样在现在后,就可以根据sha值校验下载的文件是否完整。编程开发中文件上传、下载涉及前后端通信,两者可以通过sha值,来检验数据传输过程中的完整性。文章源自灵鲨社区-https://www.0s52.com/bcjc/flutterjc/17109.html
2.Dart中如何使用 SHA256加密
像这样的通用算法,一般都会有三方库的支持。可以使用 crypto 库非常方便地计算一个字节数组的 sha256值。目前我的 Dart 命令行工具 toly 已经支持了 SHA256 的命令,如下所示:toly sha256 -s 可以加密若干个字符串:文章源自灵鲨社区-https://www.0s52.com/bcjc/flutterjc/17109.html
toly sha256 -s 张风捷特烈 TolyUI 'Flutter Unit'文章源自灵鲨社区-https://www.0s52.com/bcjc/flutterjc/17109.html
文章源自灵鲨社区-https://www.0s52.com/bcjc/flutterjc/17109.html
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:Filesadd.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.icon_copy),
clear(TolyIcon.icon_clear),
;
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<ToolAction> 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<StringCalcPanel> createState() => _StringCalcPanelState();
}
class _StringCalcPanelState extends State<StringCalcPanel> {
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<Digest> getFileSha256(String path) async {
final reader = ChunkedStreamReader(File(path).openRead());
const chunkSize = 1024 * 1024 * 2; // 2MB
AccumulatorSink<Digest> output = AccumulatorSink<Digest>();
ByteConversionSink input = sha256.startChunkedConversion(output);
try {
while (true) {
List<int> chunk = await reader.readChunk(chunkSize);
if (chunk.isEmpty) {
break;
}
input.add(chunk);
}
} finally {
reader.cancel();
}
input.close();
return output.events.single;
}
链接:https://juejin.cn/post/7390319395833282597
来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
评论