Flutter 匠心千刃 | SHA256 加密

零 Flutter教程评论71字数 3920阅读13分4秒阅读模式

Flutter 匠心千刃 | SHA256 加密

0. 本文目标

本文介绍一下如何在 Flutter 中进行 SHA256加密。并结合TolyUI 在 匠心千刃 中搭建 sha256加密的交互界面 ,本文目标如下所示:

可以在输入框中输入字符串,会自动计算输入内容的sha256值:文章源自灵鲨社区-https://www.0s52.com/bcjc/flutterjc/17109.html

1.gif文章源自灵鲨社区-https://www.0s52.com/bcjc/flutterjc/17109.html

也可以选择文件,计算该文件的sha256值:文章源自灵鲨社区-https://www.0s52.com/bcjc/flutterjc/17109.html

2.gif文章源自灵鲨社区-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

image.png文章源自灵鲨社区-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 可以省略:

image.png

任何文件都是字节数组,可以通过 File 对象读取得到 Uint8List 对象,然后使用 sha256.convert 即可:

dart

复制代码
File file = File(r'D:Filesadd.zip');
Uint8List data = await file.readAsBytes();
Digest digest = sha256.convert(data);

3. 使用Flutter搭建交互界面

命令行毕竟操作不是很方便,交互界面可以有更大的使用场景。对于点点选选的交互,不会编程的人也可以使用。在布局上,界面由两块独立的面板构成,所以可以拆分为两个组件: StringCalcPanelFileCalcPanel ,分别处理字符串和文件的 sha256 计算:

image.png

然后将两者拼装在一起,放入 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 组件。

image.png

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 的方法:

image.png

我们可以定义内存块的大小,来一块块读取计算,如下所示,通过 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
来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

零
  • 转载请务必保留本文链接:https://www.0s52.com/bcjc/flutterjc/17109.html
    本社区资源仅供用于学习和交流,请勿用于商业用途
    未经允许不得进行转载/复制/分享

发表评论