< 返回我的博客

爱国的张浩予 发表于 2021-03-06 21:55

Tags:Integer overflow, arithmetic overflow, wrapping arithmetic

这次想聊这个话题是因为我不久前刚刚完成了一个【国密SM4算法】+【CBC分组模式】的【对称加/解密】WASM API。我们选择自己造轮子的原因是:为了达成【WASM国密API】与【javascript-npm国密-依赖包】能够彼此解密由对方加密的密文。为此我们不得不从WASM端额外地“适配”由javascript语言特性“怪癖”造成的加密算法“失真”(否则,双方都不认识对方加密的密文的)。其中,包括:

就上面这两点便把crates.io上所有【国密crate】排除在我们的选择范围之外,因为在他们实现中的【密钥参量】【固定参量】【替换盒】都是u32类型的数字矩阵。其实,解决问题的过程还是很快和很顺利的(相对于我心态蹦了那一瞬间的“失重感” --- 我足足缓了三个小时才从“天塌了”的思绪中恢复过来)。简单地讲,就是:

  1. 先找一款靠谱的SM4开源代码
  2. 然后改之,让所有u32数字向i32溢出
  3. 不光是数字字面量溢出,它们之间的加、减、乘、除运算也得能够溢出。

这里的关键是如何处理【溢出】。所以,我才特意探索了一下rust中的【数字-溢出处理】和引出了今天分享的这个话题。

rust概念里,溢出被分成两类:

  1. 算术溢出
    1. 常见溢出原因是“除零”。
    2. 这馁馁是【程序-缺陷】。
  2. 整数溢出
    1. 常见溢出场景是“被赋值数字的数值大小超出了其指定数字类型可容纳的范围”。比如,

      let a = i32::MAX + 10; // Error
      let a = i32::MIN - 10; // Error
      let a: u8 = 257; // Error
      
    2. rustc的【调试断言debug_assert!】与linter皆会将其视作“程序-缺陷”。因此,前者会panic!终止程序运行;而后者会失败终止程序编译。

    3. 另一方面,程序员也能够使用【回环算术wrapping arithmetic】强制【溢出】同时压制【编译错误】。启用【回环算术】的两种方式:

      1. 用标准库的【Wrapping<T> 元组结构体】包装基本数据类型的数字。

        let max = Wrapping(i32::MAX);
        let two = Wrapping(2);
        assert_eq!(i32::MIN + 1, (max + two).0);
        
      2. 或更简单直观地,调用【数字-数据类型】的.wrapping_***(...)成员方法,而不是直接使用【算术运行符】,完成变量之间的算术运算。

        assert_eq!(i32::MAX.wrapping_add(2), i32::MIN + 1);
        

虽然按照【回环算术】修改后的rs开源代码已经混乱得惨不忍睹了,但是至少把需求搞定了。能够在与rustc的编译搏斗中侥幸过关已经很难了,【代码优雅与否】应该是在我下一个成长目标里才需要被规划的。哎!这真是一场“虐心之旅”。

评论区

写评论
Mike Tang 2021-03-07 10:58

1 共 1 条评论, 1 页