(资料图片)
不知不觉一个多小时干了小三千字,允许广泛传播的东西就发专栏吧()现在专栏展示方式也优化了,不像以前人们会不愿意点进去()
最近在写一个类似于SSH/TLS,带有用户/访客的密钥登录以及类似RPC的消息支持,可运行在TCP/WS/RTCDataChannel多种底层协议的加密协议,叫做BCSP(Berylsoft Common Security Protocol或Berylsoft Client/Server Protocol),用于之后的多种项目。现代密码学非常严谨和奇妙,千万不要自己发明和实现用于生产的密码学算法,即使是依据现代密码学的原则组合这些算法形成自己的协议或者应用也要千小心万小心,乖孩子还是尽量不要学我搞协议了。
而我这个协议除了登录和类RPC消息支持外,还有一个核心特点是:使用的算法少,有一个算法替代了很多算法。了解到这个算法的优良特性也是我搞这个协议的重要原因。大家可能知道在SHA2之后NIST举办了SHA3算法选拔大赛,最终Keccak算法胜出并且成为了SHA3算法。Keccak的核心是一个“海绵函数”,这使得它其实可以输出任意长度的hash值,所以NIST除了SHA3还标准化了两个叫做SHAKE的算法,和相同位数的SHA3只是初始值不同,但却拥有任意长度的输出,来利用海绵函数的这个特点。那么一个任意长度输出的hash函数(也就是XOF,Extend Output Function,以下沿用此称)比一般的hash函数多出了什么用途呢?大家都知道一个优良的hash函数,其输出是尽可能无法预测下一位的,也就是随机的,那么首先一个XOF就是一个密码学安全的伪随机数生成器(CSPRNG),种子就是其输入。其次大家应该都听说过一次性密码本,这是唯一可以做到完全安全的加密方式(相对于现代密码学安全是指可接受时间内破译的安全),也就是用与明文等长的一次性真随机熵源对明文进行异或进行加密,接收端再用相同的熵源再异或一次进行解密。现实中交换与明文等长的真随机熵源对于日常通信来说太过于困难了,于是人们退而求其次将这个真随机熵源变成伪随机熵源,以种子作为对称密钥,这就是流密码,两类主流对称加密算法中除了AES这样的块密码之外的另一种。流密码具有优良的性质,其性质决定了它不像块密码那样加密相同明文块的密文相同,而且有些流密码不需要硬件支持就能达成很快的速度。目前常见的有ChaCha20,移动端常用,它的速度就很快。而回忆一下刚才对XOF以及对它用于伪随机数生成器的描述,大家应该能很快明白实际上XOF也能作为流密码使用,至少对于SHAKE函数,Keccak团队认可了这个用例。传统hash函数也能完成的用例,如MAC(消息验证算法),SHAKE函数当然也能胜任。实际上这样一个接受任意长度输入并且可以获得任意长度的随机输出的函数还可以有很多有意思的用途,对信息论和密码学敏感的应该能有这个感觉。正是因为SHAKE函数具有如此丰富的用例,NIST还定义了一个扩展规范,其中包括了cSHAKE,允许添加一个NIST定义的name和一个用户定义的custom string,改变海绵函数的初始状态,使得不同用途的SHAKE函数对于相同的输入得到的输出不同,因为如果不做到这一点,是有可能造成攻击面的,有兴趣的可以自己查一下。基于cSHAKE,又定义了KMAC、TupleHash等不同用途的衍生函数。而我的协议正是通过尽可能多地利用cSHAKE函数,减少了算法的数量。除了cSHAKE背后的Keccak以外,核心的密码学算法就只有用于证书签名和密钥交换的Curve25519了,甚至我还不规范地将与Ed25519签名过程绑定的SHA2-512函数,也替换为cSHAKE256(有说法认为SHA3-512被NIST加入了不必要的复杂性,对于SHA2-512的替代,SHAKE256输出512位的强度已经足够),上一条动态的依赖上的麻烦就是始于这个替换的实现。这样做使得协议的依赖轻便,并且我个人很喜欢,觉得有一种大道归一的美。但是也不是没有问题的,首先虽然Keccak团队认可了这些用例,但是也不能说明它确实是合适的,尤其是和专为这些领域设计的算法相比,这么用很可能会引入一些作为常规hash函数碰不到的隐藏缺陷(我似乎已经看到一篇SHAKE函数可能会生成潜在重复的模式的文章了),不过实际设计协议的时候我会注意对于一个状态少取一些长度的输出的。还有一个问题是鸡蛋放在了同一个篮子里面,只要Keccak一个算法被发现缺陷,那么相比使用各种算法的一般加密协议,协议的更多部分就会陷入危险之中。(最近以SHAKE为切入点,为了实现这个协议,看密码学的东西快看吐了,能这么流畅一气呵成倒出这一千多字也证明确实学进去点东西,虽然核心算法原理没怎么碰,以概念性的理论和实践为主,毕竟你碰了难不成要自己发明或者实现或者修改吗,这可是很危险的哦)
上面的是比较重大的预告,也算是一个背景介绍。下面是一个不太重大的发表。
昨天传文件的时候,发现某网盘会限制分享文件的格式,而且似乎会检测文件结构。(之后搞WebRTC相关的东西也会顺便搞一个端到端文件传输的小工具,到时候就用不着网盘了。)利用压缩包的加密,纵然使用AES256,也只能实现对内容物加密,从外面还是可以看出是一个压缩包。正好最近在为上面的事情焦头烂额,就决定顺手写一个利用(c)SHAKE来简单加密整个文件的小工具。对整个文件加密之后,外表看就是一堆随机数,看不出任何文件结构,所以能实现避开文件结构检测。由于hash函数的输入天然就是任意长的,密码也可以是任意长,而简单加密不需要那么严谨,所以就没用专用密码hash算法(如果要做密码登录千万要注意用专用密码hash算法,比如argon2),密码就是cSHAKE的输入。一开始的版本很简单,没有缓冲区设计,把整个文件读到内存里,直接用SHAKE函数生成hash流并异或之后写入整个文件,整个程序就二十多行。后来先是加入了缓冲区;然后是可以动态设置缓冲区大小;然后是引入可以交互式输入密码的库(防止密码出现在shell历史里);然后是把简单的SHAKE改成了cSHAKE(Berylsoft的cSHAKE custom string范式是"__appname__use-purpose",有可能没有appname,比如替换掉Ed25519的SHA2-512的时候用了一个"ed25519-cshake256");然后是想起对称加密尤其是流密码必须得校验(完整协议的话就直接用MAC了,密钥内容一块校验),不然密码不对发现不了,但是为了强调流密码的特性,想做到输入输出长度完全相等,不想把校验hash放在文件里,想不指定是加密还是解密,就得把输入和输出的hash都算一遍,正好满足更广泛的需求,于是加上了输入和输出的hash计算并在最后显示出来;因为Keccak的特点决定了计算相同长度的输入(absorb,海绵吸水)和输出(squeeze,从海绵里挤水)耗时基本相等,而Keccak又基本没有硬件加速,就导致速度直接慢了三倍,于是又费了半天劲跟Option作对写了可选hash,并且重新设计了抽象。这个时候已经膨胀到一百多行了,然而我还想再加并行计算流密码和两个hash(因为有context一说所以不能并行计算,这也是流密码不用担心块密码加密相同明文块的密文相同的问题的原因,但是三个context之间可以并行计算),这又得加一堆channel之类的东西;还想加一个单独直接输出cSHAKE的输出的选项,这样作为一个CSPRNG;分离一下bin和lib;可能还有别的想法但是写了半天文章现在想到的就这些了。经典十几行临时的小工具越写想法越多。不过现在以及即使上面那些都加上去也不会很大的,得益于tiny-keccak实现小巧的体积,Windows release可执行文件只有200KB左右。丢一个GitHub地址:
https://github.com/stackinspector/shakenc
有Rust环境的直接cargo install --git就可以装上了,没有的可以配一个(x
总的来说密码学真好玩,尤其是不需要考虑生产环境安全的时候()不过我那个协议是需要考虑的,瑟瑟发抖和纠结中()
关键词: