news 2026/10/15 16:09:01

仓颉标准库 std.crypto.digest 摘要运算函数 digest 使用指南:字节数组、字符串与输入流三种重载详解

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
仓颉标准库 std.crypto.digest 摘要运算函数 digest 使用指南:字节数组、字符串与输入流三种重载详解
  • 文档
  • 知识库
  • 教程
  • OpenHarmony

【免费下载链接】CangjieCorpus

本仓库为仓颉编程语言语料库,聚焦 仓颉编程语言 核心知识体系,整合官方开发指南、API 文档、典型代码示例及语法规范等多维度资源,经结构化处理与专业标注,构建为支持 RAG(检索增强生成)技术 的高质量知识基座,适配智能编码助手、学习辅助工具等应用开发需求。 欢迎开发者基于本语料库构建仓颉语言智能应用,推动鸿蒙开发生态智能化升级!

项目地址:https://gitcode.com/Cangjie/CangjieCorpus
点击查看免费下载

本文围绕仓颉标准库std.crypto.digest包中三个重载的digest<T>泛型函数展开,系统讲解如何用统一的泛型入口对Array<Byte>、String与InputStream三种数据源执行摘要运算。读者将掌握where T <: Digest泛型约束的含义、Digest接口成员契约、三种重载的差异与适用场景,并能结合stdx.crypto.digest提供的 MD5、SHA 系列、SM3 等真实算法类编写可运行的摘要计算代码。

一、digest 泛型函数在包中的定位

std.crypto.digest包为仓颉标准库中的摘要算法能力包,提供常用摘要算法的通用接口,包括 MD5、SHA1、SHA224、SHA256、SHA384、SHA512、HMAC、SM3 等(见 digest_package_overview.md)。

该包由两部分组成:

  • 接口层:Digest接口,是所有摘要算法实现必须遵守的通用契约;
  • 函数层:digest<T>泛型函数,提供三种重载,屏蔽算法细节,让调用方以同样的写法对不同数据源做摘要运算。

函数层的三个重载在 digest_package_funcs.md 中定义:

函数签名功能
digest<T>(T, Array<Byte>) where T <: Digest对字节数组进行摘要运算
digest<T>(T, String) where T <: Digest对字符串进行摘要运算(已标注 deprecated)
digest<T>(T, InputStream) where T <: Digest对InputStream输入流中的数据进行摘要运算

三者共同点是第一个参数均为"具体的摘要算法"实例(泛型参数T,受Digest接口约束),区别在于第二个参数的数据来源形态。理解这一点后,后文可以逐个展开。

二、前置约束:Digest 接口契约

digest<T>的泛型约束where T <: Digest意味着:传入的算法对象必须实现Digest接口。该接口定义于 digest_package_interfaces.md,源码如下:

public interface Digest { prop size: Int64 prop blockSize: Int64 prop algorithm: String func write(buffer: Array<Byte>): Unit func finish(to!: Array<Byte>): Unit func finish(): Array<Byte> func reset(): Unit }

各成员的契约含义:

成员功能说明
prop algorithm: String获取摘要算法的算法名称
prop blockSize: Int64返回信息块(Block)长度,单位字节
prop size: Int64返回生成的摘要信息长度,单位字节
func write(buffer: Array<Byte>): Unit使用给定的 buffer 更新 digest 对象,在finish前可多次调用以追加数据
func finish(): Array<Byte>返回生成的摘要值
func finish(to!: Array<Byte>): Unit将摘要值写入目标数组to!
func reset(): Unit重置 digest 对象到初始状态

需要注意:接口文档明确提醒,调用finish后不可以再进行摘要计算,如需要重新计算必须先调用reset()重置上下文。这是摘要对象状态的硬性约束,也是后续示例中reset()出现的原因。

三、重载一:对字节数组做摘要 digest<T>(T, Array<Byte>)

函数签名与参数说明

public func digest<T>(algorithm: T, data: Array<Byte>): Array<Byte> where T <: Digest
参数类型说明
algorithmT(受Digest约束)具体的摘要算法实例
dataArray<Byte>待进行摘要运算的数据

返回值:Array<Byte>,即摘要运算结果。其中Byte是内置类型 UInt8 的类型别名,一个字节对应一个元素。

官方示例与运行结果

原文档提供了完整可验证的示例(文中<!-- verify -->标记表示该示例经过校验):

import std.crypto.digest.* main() { let data: Array<Byte> = [1, 2, 3, 4, 5] let mydigest = MyDigest() let digestBytes = digest<MyDigest>(mydigest, data) println(digestBytes) } // 自定义 Digest class MyDigest <: Digest { public prop size: Int64 { get() { 0 } } public prop blockSize: Int64 { get() { 0 } } public prop algorithm: String { get() { "" } } public func write(buffer: Array<Byte>): Unit { println("buffer = ${buffer}") } public func finish(to!: Array<Byte>): Unit { println("to = ${to}") } public func finish(): Array<Byte> { [3,2,1] } public func reset(): Unit {} }

运行结果:

buffer = [1, 2, 3, 4, 5] [3, 2, 1]

运行流程解读

从运行结果可以清晰还原digest<T>(T, Array<Byte>)的内部调用链:

  1. 函数收到字节数组[1, 2, 3, 4, 5],调用算法实例的write(buffer),将整段数据一次性"喂"给摘要算法 —— 因此输出了buffer = [1, 2, 3, 4, 5];
  2. 数据写入完毕后,函数调用finish()收尾生成摘要值 —— 示例中自定义MyDigest返回[3, 2, 1],该数组即为函数返回值。

这也解释了示例中MyDigest为何必须补齐Digest接口的全部成员:泛型约束where T <: Digest在编译期就要求T具备write、finish、reset等能力。实际开发中,调用方无需关心write/finish的调用时机,digest函数已将这一过程封装完毕。

四、重载二:对字符串做摘要 digest<T>(T, String)(已废弃)

public func digest<T>(algorithm: T, data: String): Array<Byte> where T <: Digest

功能与字节数组版本一致,仅将data类型换为 String。

注意:

该重载在文档中标注为deprecated,未来版本即将废弃不再使用,官方建议改用 digest<T>(T, Array<Byte>) where T <: Digest 替代。

也就是说,新代码中处理字符串时,应先将字符串转为字节数组(如调用字符串的toArray()方法)再传入字节数组重载。这一做法在后续 stdx 算法示例中也能看到:var str: String = "helloworld"后通过str.toArray()得到Array<Byte>再调用write。迁移成本很低,且语义更统一。

五、重载三:对输入流做摘要 digest<T>(T, InputStream)

函数签名与参数说明

public func digest<T>(algorithm: T, input: InputStream): Array<Byte> where T <: Digest
参数类型说明
algorithmT(受Digest约束)具体的摘要算法实例
inputInputStream待进行摘要运算的输入流

返回值:Array<Byte>,摘要运算结果。

这一重载的意义在于:当待摘要数据不是一次性可得(例如大文件、网络数据流),可以借助InputStream的分块读取能力边读边算,避免把全部数据加载进内存。InputStream是std.io包的核心接口,其契约仅要求实现read(buffer: Array<Byte>): Int64,即"从输入流读取数据放入 buffer,返回读取的字节数"(见 io_package_interfaces.md)。

官方示例与运行结果

import std.crypto.digest.* import std.io.* main() { /* 将原始的字节数组 data 转换为一个输入流 BufferedInputStream */ let data: Array<Byte> = [1, 2, 3, 4, 5] let byteBuffer = ByteBuffer(data) let bufferedInputStream = BufferedInputStream(byteBuffer) let mydigest = MyDigest() let digestBytes = digest<MyDigest>(mydigest, bufferedInputStream) println(digestBytes) } // 自定义 Digest class MyDigest <: Digest { public prop size: Int64 { get() { 0 } } public prop blockSize: Int64 { get() { 2 } } public prop algorithm: String { get() { "" } } public func write(buffer: Array<Byte>): Unit { println("buffer = ${buffer}") } public func finish(to!: Array<Byte>): Unit { println("to = ${to}") } public func finish(): Array<Byte> { [3,2,1] } public func reset(): Unit {} }

运行结果:

buffer = [1, 2] buffer = [3, 4] buffer = [5] [3, 2, 1]

分块读取原理解读

对比字节数组重载的输出可以发现关键差异:字节数组版本write只被调用一次(buffer = [1, 2, 3, 4, 5]),而输入流版本write被调用了三次,依次是[1, 2]、[3, 4]、[5]。

这正是由MyDigest.blockSize的值2决定的:digest<T>(T, InputStream)实现内部会按照算法对象暴露的blockSize(信息块长度,单位字节)从输入流中分块读取数据,每读到一块就调用一次write(buffer)增量更新摘要状态,直到流被读完。整个流程可概括为:

  1. 依据blockSize从InputStream分块read数据;
  2. 每块数据调用算法实例的write(buffer)增量更新;
  3. 流读取完毕(read返回 0)后调用finish()生成最终摘要。

示例中的流构造链路也值得说明:

  • ByteBuffer(data):基于 Array<Byte> 构建的内存字节流,父类型为IOStream & Seekable(见 io_package_classes.md),将原始字节数组包装为可读的流;
  • BufferedInputStream(byteBuffer):带缓冲区的输入流包装器,可将其他InputStream绑定进来并暂存到缓冲区(默认缓冲容量 4096),同样实现了InputStream接口,因此可以作为digest的第二个参数传入。

若实际使用真实算法(如 SHA256),其blockSize通常为 64 或 128 字节,输入流数据会被自动切成对应的信息块依次处理,调用方完全无需感知分块细节。

六、落到实战:与 stdx.crypto.digest 具体算法的配合

std.crypto.digest只提供抽象接口与泛型入口,具体算法实现位于stdx.crypto.digest包(见 crypto_digest_package_overview.md),包含 MD5、SHA1、SHA224、SHA256、SHA384、SHA512、HMAC、SM3 共 8 个算法类,它们全部声明为<: Digest,因此天然满足digest<T>的泛型约束。例如:

public class SHA256 <: Digest { public init() }

真实算法使用示例(SHA256)

以 sample_digest.md 中校验过的 SHA256 示例为参照,实际项目中可这样计算字符串的摘要:

import stdx.crypto.digest.* import std.convert.* import std.crypto.digest.* import stdx.encoding.hex.* main() { var str: String = "helloworld" var sha256Instance = SHA256() sha256Instance.write(str.toArray()) var md: Array<Byte> = sha256Instance.finish() var result: String = toHexString(md) println(result) return 0 }

运行结果(对"helloworld"):

936a185caaa266bb9cbe981e9e05cb78cd732b0b3280eb944412bb6f8f8f07af

这里采用的是write + finish的成员函数调用方式;若改用本文介绍的digest泛型函数,只需将上述手动两步封装成一次调用:

import std.crypto.digest.* import stdx.crypto.digest.* main() { var str: String = "helloworld" let sha256Instance = SHA256() let md = digest<SHA256>(sha256Instance, str.toArray()) // md 即摘要字节序列,可用 stdx.encoding.hex 的 toHexString 转成十六进制字符串 }

摘要结果默认是Array<Byte>,展示时通常借助stdx.encoding.hex包的toHexString(data: Array ): String转换为十六进制字符串,与各类常见摘要工具的展示形式一致。

各算法对同一输入"helloworld"的校验结果如下,可作为自测参照:

算法十六进制摘要(helloworld)
MD5fc5e038d38a57032085441e7fe7010b0
SHA16adfb183a4a2c94a2f92dab5ade762a47889a5a1
SHA224b033d770602994efa135c5248af300d81567ad5b59cec4bccbf15bcc
SHA256936a185caaa266bb9cbe981e9e05cb78cd732b0b3280eb944412bb6f8f8f07af
SHA38497982a5b1414b9078103a1c008c4e3526c27b41cdbcf80790560a40f2a9bf2ed4427ab1428789915ed4b3dc07c454bd9
SHA5121594244d52f2d8c12b142bb61f47bc2eaf503d6d9ca8480cae9fcf112f66e4967dc5e8fa98285e36db8af1b8ffa8b84cb15e0fbcf836c3deb803c13f37659a60
SM3c70c5f73da4e8b8b73478af54241469566f6497e16c053a03a0170fa00078283

HMAC 的补充说明

HMAC类同样实现Digest接口,构造时需传入密钥与算法类型(HashType),如HMAC(key, HashType.SHA512)。其静态方法HMAC.equal(mac1, mac2)比较两个摘要时不采用短路比较,从而防止 timing attack 类型的时间侧信道攻击(见 digest_package_classes.md)。此外,与普通算法一样,HMAC在调用finish()后也必须先reset()才能继续计算 —— 这与本文第三、五节强调的状态约束完全一致。

运行环境依赖:OpenSSL 3

stdx.crypto.digest的算法实现基于 OpenSSL 3 的 crypto 动态库,使用前必须安装相应依赖(详见 crypto_digest_package_overview.md):

  • Linux:安装 OpenSSL 3 开发包,确保存在libcrypto.so与libcrypto.so.3(如 Ubuntu 22.04 执行sudo apt install libssl-dev);自编译安装时需将库目录加入LD_LIBRARY_PATH与LIBRARY_PATH;
  • Windows:确保存在libcrypto.dll.a(或libcrypto.lib)与libcrypto-3-x64.dll,并分别配置LIBRARY_PATH与PATH环境变量;
  • macOS:brew install openssl@3,确保存在libcrypto.dylib与libcrypto.3.dylib。

若未安装或版本过低,程序运行时会抛出异常CryptoException:Can not load openssl library or function xxx。CryptoException是 crypto 包的通用异常类,定义于 digest_package_exceptions.md,支持无参构造与携带message字符串构造两种方式。

七、使用要点小结

  1. 数据源决定重载选择:一次性字节数据用digest<T>(T, Array<Byte>);大体积或流式数据用digest<T>(T, InputStream),可边读边算避免整段载入内存;digest<T>(T, String)已废弃,字符串先toArray()再走字节数组重载;
  2. 算法对象必须实现 Digest:where T <: Digest在编译期强约束,传入stdx.crypto.digest的 MD5、SHA 系列、SM3、HMAC 实例均可;
  3. 注意摘要对象状态机:finish()之后对象不能再继续计算,必须reset()重置上下文;digest泛型函数内部已封装好write → finish的调用流程;
  4. 输入流重载按 blockSize 分块:Digest的blockSize属性决定分块粒度,流式处理时write会被多次调用;
  5. 环境前提:具体算法类依赖 OpenSSL 3 动态库,部署环境需按平台预先配置。

如需进一步阅读,可继续查阅 Digest 接口定义、stdx 算法类全集 以及 算法综合示例。

  • 文档
  • 知识库
  • 教程
  • OpenHarmony

【免费下载链接】CangjieCorpus

本仓库为仓颉编程语言语料库,聚焦 仓颉编程语言 核心知识体系,整合官方开发指南、API 文档、典型代码示例及语法规范等多维度资源,经结构化处理与专业标注,构建为支持 RAG(检索增强生成)技术 的高质量知识基座,适配智能编码助手、学习辅助工具等应用开发需求。 欢迎开发者基于本语料库构建仓颉语言智能应用,推动鸿蒙开发生态智能化升级!

项目地址:https://gitcode.com/Cangjie/CangjieCorpus
点击查看免费下载

相关推荐

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/15 16:08:42

EHome设备接入后录像回看功能实现与故障排查实战

1. 从通道配置到录像回看&#xff1a;EHome设备接入后最容易忽略的几件事设备接进来了&#xff0c;通道也上线了&#xff0c;点开实时预览画面正常——很多人到这一步就觉得集成工作已经完成。但真正交付给用户的时候&#xff0c;对方第一个问的往往是&#xff1a;"我要看…

作者头像 李华
网站建设 2026/10/15 16:08:30

交互点检测:重新定义人体-物体交互检测的空间焦点

1. 从"点"出发&#xff1a;为什么交互点检测值得单独拿出来讲第一次读到这篇论文的标题时&#xff0c;我脑子里冒出的第一个疑问是&#xff1a;人体-物体交互检测&#xff08;Human-Object Interaction Detection&#xff0c;简称HOI检测&#xff09;已经有一堆方法了…

作者头像 李华
网站建设 2026/10/15 16:08:08

Swift数组遍历深度解析:forEach、for in、enumerated与indices选型指南

1. 从一次线上崩溃说起&#xff1a;为什么数组遍历值得单独拎出来讲去年帮一个朋友排查他们App的线上崩溃&#xff0c;问题出在一个很不起眼的地方&#xff1a;一个商品列表页&#xff0c;用户快速滑动时偶发数组越界。代码逻辑看起来没问题&#xff0c;就是最普通的for i in 0…

作者头像 李华
网站建设 2026/10/15 16:07:51

高精度室内定位框架find:从指纹采集到参数调优的工程实践

简介&#xff1a;下载包提供名为 find 的高精度室内定位框架&#xff0c;属于开源项目&#xff0c;面向 Android 开发者、物联网工程人员与室内导航研究者&#xff0c;可解决建筑物内 GPS 信号弱、卫星定位不可用时的位置感知与导航需求。框架突出“适用于大多数无线”这一特点…

作者头像 李华
网站建设 2026/10/15 16:07:48

源码编译与定制实战:从环境准备到性能优化的完整指南

1. 源码编译与定制到底在解决什么问题很多人第一次接触“源码编译与定制”这个概念&#xff0c;是在某个开源项目的 README 里看到一行make && make install&#xff0c;然后照着敲了一遍&#xff0c;报了一屏红字&#xff0c;就再也没碰过。但如果你真的在团队里负责过…

作者头像 李华
网站建设 2026/10/15 16:07:46

华硕800系主板雷电4/5驱动安装与固件更新排查指南

很多刚入手800系主板的兄弟会踩同一个坑&#xff1a;板载的雷电4/雷电5接口插上设备没反应&#xff0c;或者设备管理器里一堆带感叹号的未知设备&#xff0c;第一反应是主板坏了或者买到了残次品&#xff0c;其实绝大多数情况只是雷电控制器驱动没装对、没装全。这篇文章就是专…

作者头像 李华