- 文档
- 知识库
- 教程
- OpenHarmony
【免费下载链接】CangjieCorpus
本仓库为仓颉编程语言语料库,聚焦 仓颉编程语言 核心知识体系,整合官方开发指南、API 文档、典型代码示例及语法规范等多维度资源,经结构化处理与专业标注,构建为支持 RAG(检索增强生成)技术 的高质量知识基座,适配智能编码助手、学习辅助工具等应用开发需求。 欢迎开发者基于本语料库构建仓颉语言智能应用,推动鸿蒙开发生态智能化升级!
本文围绕仓颉标准库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| 参数 | 类型 | 说明 |
|---|---|---|
algorithm | T(受Digest约束) | 具体的摘要算法实例 |
data | Array<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, 2, 3, 4, 5],调用算法实例的write(buffer),将整段数据一次性"喂"给摘要算法 —— 因此输出了buffer = [1, 2, 3, 4, 5]; - 数据写入完毕后,函数调用
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| 参数 | 类型 | 说明 |
|---|---|---|
algorithm | T(受Digest约束) | 具体的摘要算法实例 |
input | InputStream | 待进行摘要运算的输入流 |
返回值: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)增量更新摘要状态,直到流被读完。整个流程可概括为:
- 依据
blockSize从InputStream分块read数据; - 每块数据调用算法实例的
write(buffer)增量更新; - 流读取完毕(
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) |
|---|---|
| MD5 | fc5e038d38a57032085441e7fe7010b0 |
| SHA1 | 6adfb183a4a2c94a2f92dab5ade762a47889a5a1 |
| SHA224 | b033d770602994efa135c5248af300d81567ad5b59cec4bccbf15bcc |
| SHA256 | 936a185caaa266bb9cbe981e9e05cb78cd732b0b3280eb944412bb6f8f8f07af |
| SHA384 | 97982a5b1414b9078103a1c008c4e3526c27b41cdbcf80790560a40f2a9bf2ed4427ab1428789915ed4b3dc07c454bd9 |
| SHA512 | 1594244d52f2d8c12b142bb61f47bc2eaf503d6d9ca8480cae9fcf112f66e4967dc5e8fa98285e36db8af1b8ffa8b84cb15e0fbcf836c3deb803c13f37659a60 |
| SM3 | c70c5f73da4e8b8b73478af54241469566f6497e16c053a03a0170fa00078283 |
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字符串构造两种方式。
七、使用要点小结
- 数据源决定重载选择:一次性字节数据用
digest<T>(T, Array<Byte>);大体积或流式数据用digest<T>(T, InputStream),可边读边算避免整段载入内存;digest<T>(T, String)已废弃,字符串先toArray()再走字节数组重载; - 算法对象必须实现 Digest:
where T <: Digest在编译期强约束,传入stdx.crypto.digest的 MD5、SHA 系列、SM3、HMAC 实例均可; - 注意摘要对象状态机:
finish()之后对象不能再继续计算,必须reset()重置上下文;digest泛型函数内部已封装好write → finish的调用流程; - 输入流重载按 blockSize 分块:
Digest的blockSize属性决定分块粒度,流式处理时write会被多次调用; - 环境前提:具体算法类依赖 OpenSSL 3 动态库,部署环境需按平台预先配置。
如需进一步阅读,可继续查阅 Digest 接口定义、stdx 算法类全集 以及 算法综合示例。
- 文档
- 知识库
- 教程
- OpenHarmony
【免费下载链接】CangjieCorpus
本仓库为仓颉编程语言语料库,聚焦 仓颉编程语言 核心知识体系,整合官方开发指南、API 文档、典型代码示例及语法规范等多维度资源,经结构化处理与专业标注,构建为支持 RAG(检索增强生成)技术 的高质量知识基座,适配智能编码助手、学习辅助工具等应用开发需求。 欢迎开发者基于本语料库构建仓颉语言智能应用,推动鸿蒙开发生态智能化升级!
相关推荐
仓颉 std.crypto.digest 详解:digest 泛型函数与 Digest 接口的消息摘要运算
仓颉 std.crypto.digest 详解:digest 泛型函数与 Digest 接口的消息摘要运算 本文聚焦仓颉标准库 std.crypto.diges
编程语言语言运行时标准库垃圾回收并发编程仓颉标准库 Digest 接口详解:std.crypto.digest 中摘要算法的通用契约与实现剖析
仓颉标准库 Digest 接口详解:std.crypto.digest 中摘要算法的通用契约与实现剖析 在仓颉(Cangjie)标准库中, std.crypto
编程语言语言运行时标准库垃圾回收并发编程仓颉标准库 StringWriter 详解:向输出流写入字符串与数值的完整实践
仓颉标准库 StringWriter 详解:向输出流写入字符串与数值的完整实践 StringWriter 是仓颉标准库 std.io 包提供的一个输出流包装类,
编程语言语言运行时标准库垃圾回收并发编程
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考