爱国的张浩予 发表于 2021-03-06 21:55
Tags:Integer overflow, arithmetic overflow, wrapping arithmetic
这次想聊这个话题是因为我不久前刚刚完成了一个【国密SM4
算法】+【CBC
分组模式】的【对称加/解密】WASM API
。我们选择自己造轮子的原因是:为了达成【WASM
国密API
】与【javascript-npm
国密-依赖包】能够彼此解密由对方加密的密文。为此我们不得不从WASM
端额外地“适配”由javascript
语言特性“怪癖”造成的加密算法“失真”(否则,双方都不认识对方加密的密文的)。其中,包括:
- 在
javascript
中,所有的数字都是有符号的,因为javascript
会使用64
位浮点数类型(大约等效于rust
中的f64
)保存所有形式数字。 javascript XOR
位运算会强制地将64
位数字类型截断为32
位数字类型。想不到吧!没有专门的业务场景,没有精准的踩坑需求。谁会注意到javascript
语言中这个“先天缺陷”。
就上面这两点便把crates.io
上所有【国密crate
】排除在我们的选择范围之外,因为在他们实现中的【密钥参量】【固定参量】【替换盒】都是u32
类型的数字矩阵。其实,解决问题的过程还是很快和很顺利的(相对于我心态蹦了那一瞬间的“失重感” --- 我足足缓了三个小时才从“天塌了”的思绪中恢复过来)。简单地讲,就是:
- 先找一款靠谱的
SM4
开源代码 - 然后改之,让所有
u32
数字向i32
溢出 - 不光是数字字面量溢出,它们之间的加、减、乘、除运算也得能够溢出。
这里的关键是如何处理【溢出】。所以,我才特意探索了一下rust
中的【数字-溢出处理】和引出了今天分享的这个话题。
在rust
概念里,溢出被分成两类:
- 算术溢出
- 常见溢出原因是“除零”。
- 这馁馁是【程序-缺陷】。
- 整数溢出
-
常见溢出场景是“被赋值数字的数值大小超出了其指定数字类型可容纳的范围”。比如,
let a = i32::MAX + 10; // Error let a = i32::MIN - 10; // Error let a: u8 = 257; // Error
-
rustc
的【调试断言debug_assert!
】与linter
皆会将其视作“程序-缺陷”。因此,前者会panic!
终止程序运行;而后者会失败终止程序编译。 -
另一方面,程序员也能够使用【回环算术
wrapping arithmetic
】强制【溢出】同时压制【编译错误】。启用【回环算术】的两种方式:-
用标准库的【
Wrapping<T>
元组结构体】包装基本数据类型的数字。let max = Wrapping(i32::MAX); let two = Wrapping(2); assert_eq!(i32::MIN + 1, (max + two).0);
-
或更简单直观地,调用【数字-数据类型】的
.wrapping_***(...)
成员方法,而不是直接使用【算术运行符】,完成变量之间的算术运算。assert_eq!(i32::MAX.wrapping_add(2), i32::MIN + 1);
-
-
虽然按照【回环算术】修改后的rs
开源代码已经混乱得惨不忍睹了,但是至少把需求搞定了。能够在与rustc
的编译搏斗中侥幸过关已经很难了,【代码优雅与否】应该是在我下一个成长目标里才需要被规划的。哎!这真是一场“虐心之旅”。
评论区
写评论膜