加密算法笔记

saltyx_

记录下写加密文件脚本时候查阅的相关加密算法总结

本文涉及的加密算法

对称加密算法

DES, AES

密码模式

密码本模式
密码块链模式
密码反馈模式

对称加密算法

使用相同的密钥来加密和解密的算法成为对称加密算法

1.DES

DES计算方法如下图。

DES.png
DES(b).png

每次计算都是讲64bit明文拆分为左半部和右半部。下一轮的迭代输入左侧为上一轮的右半部,下一轮的右半部通过计算 $$ L_{i-1}\oplus f(R_{i-1}, K_i) $$ 其中 $K_i$ 表示当前所使用的密钥,而 $f(R_{i-1}, K_i)$ 的函数表示的是以下步骤

  • 根据一个固定的替代和复制规则,将32位的 $R_{i-1}$扩展成一个一个48位的数字$E$

  • $E$和$K_i$被异或在一起

  • 异或的结果被分成8个6位组,每个6位组被输入到不同S盒(置换)中。对于每个S盒共有$2^6$种可能输入,每种输入被映射到4位输出上。最后将这$8 X 4$位通过一个P盒(排列)

DES 附加材料wiki
例如映射出的4位的S盒的S5表格。S盒wiki
例如输入 0 1101 1那么寻找行01列1101的部分即为1001

S5 Middle 4 bits of input
0000 0001 0010 0011 0100 0101 0110 0111 1000 1001 1010 1011 1100 1101 1110 1111
Outer bits 00 0010 1100 0100 0001 0111 1010 1011 0110 1000 0101 0011 1111 1101 0000 1110 1001
01 1110 1011 0010 1100 0100 0111 1101 0001 0101 0000 1111 1010 0011 1001 1000 0110
10 0100 0010 0001 1011 1010 1101 0111 1000 1111 1001 1100 0101 0110 0011 0000 1110
11 1011 1000 1100 0111 0001 1110 0010 1101 0110 1111 0000 1001 1010 0100 0101 0011

其中16次迭代中使用了不同的密钥。算法开始前会对56位的密钥执行56位替换操作,每次迭代开始前也会将密钥分成两个28位,每部分向左循环移位,移位位数取决于当前迭代次数,移位之后,执行56位替代。

其中S盒和P盒操作代表的是密码置换和替换。

2.AES

随着DES安全性下降,慢慢被淘汰。

SSL 1.2版本以及TLS中已经使用了更加安全的AES算法来加密。

AES.png

AES加密相对DES更加复杂,其过程是在一个4X4的字节矩阵中完成的,这个矩阵称为state。
算法的过程,参考《计算机网络》第八章的代码结构

#define LENGTH 16
#define NROWS 4
#define NCOLS 4
#define ROUNDS 10
typedef unsigned char byte;
rijndeal(byte plaintext[LENGTH], byte ciphertext[LENGTH], byte key[LENGTH]) {
    int r; /*记录当前处理到第几轮*/
    byte state[NROWS][NCOLS]; /*主要操作的矩阵*/
    struct { 
        byte k[NROWS][NCOLS];
    } rk[ROUNDS+1];
    expand_keys(key, rk); 
    /*算法开始前会进行扩展key,扩展结果放在11个rk数组中。其中一个会被用在计算最开始处,其余被用在10轮计算中*/
    xor_roundkey_into_state(state, rk[0]);
    /*算法开始前,rk[0]和state先执行一个异或操作,然后存入state中*/
    for(r=1; r<= ROUNDS; r++) {
        substitute(state);
        /*替代过程,这其中会有个一S盒,每个字节都会有对应得一个字节输出。
        不同于DES存在多个S盒,这个过程仅有一个S盒*/
        rotate_rows(state);
        /*左循环移动,0行不移动,1行移动一位,2行移动两位,3行移动三位*/
        if (r<ROUNDS) {
            mix_columns(state);
            /*混淆操作,这过程中包含了一个在有限域GF(Galois field)(2^8)上的乘法*/
            xor_roundkey_into_state(state, rk[r]);   
            /*异或密钥,供下一轮加密使用*/
        }
    }
    copy_state_to_ciphertext(state, ciphertext)
}

Rijndael的加密结构即以上的部分。上面的加密过程是对于128位密钥的情况。输入的明文是16字节的,密钥也是16字节的。Rijndael的加密支持128,192,256位的密钥。

密码模式

注:以下公式中$P$代表明文(plaintext) $E$代表加密,$D$代表解密,$C$代表密文(cipher)

1.密码本模式

比如使用DES加密一段文本,如果使用密码本模式(ECB),则将明文分割成连续8字节的数据段来加密。这种模式成为密码本模式。这种模式带来的问题也显而易见,同样一份明文经过同一个密钥加密出来的结果是一样的,这样我们可以替换一个片段来达到不可告人的目的。《计算机网络》中举的例子很有意思,如果某个雇员知道公司年终奖文件的格式,比如说每个雇员的记录为32字节长的记录,16字节名字,8字节职位,8字节奖金,那么这个雇员可以通过替换记录的低8字节来达到替换奖金的目的,而且很有可能不被检测出来!

2.密码块链模式(CBC)

为了对抗加密段的攻击,那么就需要将每段加密段联系起来,这就有了密码块链模式。这种模式需要一个初始向量IV(Initialization Vector)来执行异或操作。

其加密过程公式为$$C_i = E_k(P_i \oplus C_{i-1})$$
其中初始 $C_0 = IV$

以AES-128-CBC为例。对于每一个16字节明文,首先和16字节的向量执行异或操作,然后再传入加密模块加密,最终加密的结果继续用于下一个16字节加密前的异或。

其解密过程公式为$$P_i = D_k(C_i) \oplus C_{i-1}$$
其中初始 $C_0 = IV$

这样的加密方式,对于相同的明文块不会导致相同的密文块。这也是其安全的部分。
下面是我之前写的一段脚本,用到了就是OpenSSL的AES-256-CBC的算法

require 'openssl'
require 'digest/sha2'
require 'securerandom'

class OpenSLLAESDemo
  ALG = 'AES-256-CBC'
  def initialize

  end

  def self.encrypt (file, password=nil)

    cipher = OpenSSL::Cipher.new(ALG)
    cipher.encrypt
    if password.nil?
      key =  cipher.random_key
    else
      key = Digest::SHA256.hexdigest(password)
    end

    iv = SecureRandom.hex(32)
    cipher.key =key
    cipher.iv = iv
    File.new("#{file}.enc",File::CREAT)

    File.open("#{file}.enc", 'wb') do |enc|
      File.open(file, 'rb') do |f|
        loop do
          r = f.read(4096)
          break unless r
          temp = cipher.update(r)
          enc << temp
        end
      end
      temp = cipher.final
      enc << temp
    end
    [key, iv]
  end

  def self.decrypt (key,iv,file)
    cipher = OpenSSL::Cipher.new(ALG)
    cipher.decrypt
    cipher.key = key
    cipher.iv = iv
    
    File.new("#{file}.dec",File::CREAT)
    File.open("#{file}.dec", 'wb') do |dec|
      File.open("#{file}.enc", 'rb') do |enc|
        loop do
          r = enc.read(4096)
          break unless r
          temp = cipher.update(r)
          dec << temp
        end
      end
      temp = cipher.final
      dec << temp
    end
  end
  def self.hash(file)
    Digest::SHA256.hexdigest(File.open(file, 'r'){|f| f.read})
  end
end
file = 'this is my file path'
temp = OpenSLLAESDemo.encrypt(file, 'iampassword')
puts temp[0]
puts temp[1]
OpenSLLAESDemo.decrypt(temp[0], temp[1], file)
puts 'yes' if OpenSLLAESDemo.hash(file) ==OpenSLLAESDemo.hash("#{file}.dec")

3.密文反馈模式

CBC可以说是最常用的模式,还以AES-128来说,它必须等待64字节的数据块到达后才能开始加密,这对于流式文件可以说是很不友好了。那么就有了密文反馈模式,这种模式可以改进这一点

先看它的公式吧

其加密公式为$$C_i = E_k(C_{i-1})\oplus P_i$$

解密为 $$P_i =E_k(C_{i-1})\oplus C_i$$

其中 $C_0 = IV$

看到这里需要注意解密过程,解密过程不是执行解密模块,而是一直加密。由于在解密过程中生成$P_i$的时候与$C_i$做异或的字节和生成$P_i$的时候与$C_i$做异或的是同一个字节,就可以保证解密的正确性。

对比CBC模式,发现这样其实就不用$E_k$中就不用等待数据块的到来就可以执行。这还是最简单的,一般实现过程中,会使用移位寄存器,来保存多个加密过数据段。比如说下面这张图

128bitCFM.png

因此其加密公式也就变为$$C_i = head(E_k(S_{i-1}),x)\oplus P_i$$

解密公式变为 $$P_i = head(E_K(S_{i-1}), x)\oplus C_i$$

其中$S_0 = IV$, $S_i = ((S_{i-1}<<x) + C_i) mod 2^n$

$S_i$的公式就是128位的移位寄存器移位结果了。果然看公式理解起来更快了。

用途

这些对称加密算法的使用场景一般是哪些呢?

我觉得只要适合文件加密或者信息加密的都适合。但由于是对称加密算法,所以加密解密需要同一个密钥,因此密钥管理是个大麻烦。这时候公钥加密RSA就派上用途了,但是RSA计算比较耗时,所以非对称加密和对称加密结合起来就是一个很完美的解决方案了。通过RSA传递AES的密钥,之后的对话就可以用AES来加密解密了。

下面展示一个简易版的HTTPS 通过SSL建立子协议的过程

图片描述

之后的过程就可以使用对称加密来进行传输数据了。

最后

在做笔记看wiki的时候发现了个很有意思的词条,时序攻击。那再来说说时序攻击吧。

至于什么是时序攻击呢?举个匹配过程的例子吧

当验证密码的时候,需要将外界输入的密码和真实密码对比,如果我们在一遇到不同的字符就返回比对失败,那么对于不同的输入这个时间往往是不同的,根据返回结果的时间就可以大致猜测到哪一位是不同的。

例如下面的代码

for (int i=0; i<length; i++) {
    if (unchecked[i] != password[i]) return i;
}

如果第一位就返回和最后一位返回,这个计算的时间肯定是不一样的。通过比对这个时间就可以判断失败位置。这样就讲攻击难度降下去很多。

对于上面的例子,应对方案也很简单,如果发现字符匹配失败,不要急着返回结果,做个标记,全部匹配完毕再返回即可。

参考

阅读 2.3k

Android developer

7 声望
0 粉丝
0 条评论
你知道吗?

Android developer

7 声望
0 粉丝
文章目录
宣传栏