28. rust常用加密算法解析与应用实践

1.前言

由于实际项目开发过程中,会遇到相当多的接口对接,而往往接口对接第一步的难点就是验签加解密。

这是一个很麻烦的事情,尤其是对于不熟悉加解密的同学,单单是验签可能就需要花费一两天、甚至都难以搞定。

反过来,对于逆向工作来说,如果你不清楚对方使用的哪种加密算法,你也很难找到相应的思路去逆向解密。

因此本文将带大家过一遍常用的加解密算法,基本能覆盖当前绝大部分的场景。

2.对称加密

首先来看最经典的对称加密算法,也就是最简单的一种加密算法。

正如其名称所示,它是对称的,意思就是加密与解密的过程非常相似,就像镜子的正反面。

2.1 凯撒密码

历史上曾被用于战时信息传递的一个非常典型的算法是凯撒密码,它的加密方式如下:

  • 对字母表中的每个字符用固定偏移量(如+3)进行替换。

比如abc,通过偏移量+3后,得到的密文就是def

而对应的解密方法当然就是将这个密文的偏移量-3了。

虽然如今我们看起来很简单,但如果是第一次接触这样的加密信息,短时间内其实还是不太好想到相应解密方式的。

2.2 替换加密

由于上面这种加密方式过于简单粗暴,一旦被人知道后,就很难再次起到同样的效果,所以而进一步的就有密码本这类东西。

它的加密解密过程同样是对称的,而媒介就是这个密码本。

比如将“你好世界”这四个汉字映射为四个数字4689

那么你将这个数字发送给另一个人,中间无论谁看到了这个数字、在没有这个密码本的情况下,都会将其认为是一串无意义的数字。

而密码本便记录了汉字与加密数据之间的映射关系,解密方式便是通过数字结合密码本反向映射,还原信息。

不过如今这种加密方式已经基本没人用了,至少互联网上很少有人用。

2.3 异或加密

上面两种加密方式之所以在互联网使用的很少,主要还是因为其运行的效率不高、用起来也不够简洁,所以更适合应用于现实。

而异或加密不一样,虽然它同样是对称加密算法,但由于它可以非常高效的对于字节进行加密,所以在如今的互联网上仍然会有人使用。

比如网易云音乐下载到本地的音乐文件,就是通过异或加密算法进行加密的。

它的原理来源于这样一个数学规律:任何两个数字的异或运算结果,只要得到其中一个数字,就能反向运算得到另一个数字。

比如一个简单的例子:

101010 ~ 000010 = 101000

上面就是数字42与数字2的异或运算得到的一个结果,只不过这里是二进制表示形式。

异或的运算规则就是,相同为0、不同为1,按位异或得到最终结果。

但此时你反过来就会发现,这个加密的结果无论异或前方哪一个数字,都能得到另一个数字:

101000 ~ 101010 = 000010
101000 ~ 000010 = 101010

代码示例如下:

image.png

虽然我们常常会发现,我们对于别人加密后的数据,只能得到一个加密结果,前面两个数字都没有,那此时应该怎么解密呢?

这就需要一点技巧与经验了,事实上,大部分数据源文件中,0这个数字在开头位置是最多的,而0与任何数字异或都是不变的。

所以往往一个通过异或加密的文件中,开头出现的最频繁的那个数字就是其中的key,只要将整个文件每个字节都去与这个key异或得到的数据结果保存到一个新文件中,就能得到解密后的文件。

比如当我们用十六进制编辑器打开一个音频文件:

image.png

就可以看到文件头就是一大串的0。

此时让我们对其进行异或加密:

fn main() {
    let mut data = std::fs::read("1.mp3").unwrap();
    let key = 0x12;
    for d in data.iter_mut() {
        *d = *d ^ key;
    }
    std::fs::write("1.mp3", data).unwrap();
}

这里的加密key用的是0x12

然后你就会发现,此时文件头0x12出现的频率非常高:

image.png

那么加密后的数据已经有了,这里key我们也猜到了,加密方式自然就是将上面的代码再运行一次,就能得到原始数据了。

3.对称分组加密(AES)

上述加密方式最致命的弱点便是,加密的key几乎可以靠猜出来,然后直接反推即可得到结果。

显然这是极为不安全的。

所以如今使用的更多的还是对称分组加密算法,虽然它的发展历程很长、中途历经了很多种算法的迭代,但目前用的最多的便是AES这个算法,当你对接某个企业的接口文档,大概率其中就用到了AES算法进行验签。

之所以它的应用非常广泛,就是它从原理上杜绝了仅靠猜就能拿到key、解密所有数据的行为。

其基本原理是,随机生成一串字节作为密钥(一般为256位),对数据进行加密计算,得到一个加密结果。

其内在的算法过程这里不再深究,因为那完全是数学上的证明,对于我们开发者来说,只要会用、理解它的过程即可。

AES算法有多种模式:CBC、ECB、CFB、CTR、GCM、XTS、CCM。

基本所有编程语言都有对应实现的库,但由于rust强安全性的限制,导致算法库内部存在一些让rust编译器难以直接解析的类型,导致很多人第一次使用的时候会有些不适应。

这里以我实际接触过的两种AES算法模式为例,简单讲解一下它的使用方法。

3.1 ECB模式

首先第一种是ECB模式,也是其中最直接简单的模式,可以说是前面的对称加密算法的直接升级版。

它的工作原理是随机生成一串字节作为密钥,将需要加密的数据分组、分别加密,最后组合起来,就是加密后的数据。

一个简单的示例如下,首先需要三个依赖项:

aes = "0.8"
ecb = "0.1.2"
rand = "0.9.1"

将一段字符串加密、解密的代码如下:

use aes::{
    cipher::{block_padding::Pkcs7, BlockDecrypt, BlockEncrypt, KeyInit},
    Aes256Dec, Aes256Enc,
};
use rand::{rngs::OsRng, TryRngCore};

fn main() {
    let plain = "hello world";
    let (key, cipher) = encrypt_ecb(plain.as_bytes());
    let data = decrypt_ecb(&key, &cipher);
    println!("plain: {}", plain);
    println!("data: {}", String::from_utf8(data).unwrap());
}

pub fn encrypt_ecb(plaintext: &[u8]) -> (Vec<u8>, Vec<u8>) {
    let mut key = [0u8; 32];
    OsRng.try_fill_bytes(&mut key).unwrap();
    let aes = Aes256Enc::new_from_slice(&key).unwrap();
    let mut block = Vec::new();
    block.resize(plaintext.len() + 16, 0);
    let ret = aes
        .encrypt_padded_b2b::<Pkcs7>(plaintext, &mut block)
        .unwrap();
    (key.to_vec(), ret.to_vec())
}

pub fn decrypt_ecb(key: &[u8], ciphertext: &[u8]) -> Vec<u8> {
    let cipher = Aes256Dec::new_from_slice(key).unwrap();
作者:余识
全部文章:0
会员文章:0
总阅读量:0
c/c++pythonrustJavaScriptwindowslinux