AES加解密的ruby和golang实现


AES是目前最流行的分组对称加密算法,在开发的过程中,无处不在。Intel甚至在他们的芯片中定制了底层指令来让AES更快地执行,所以AES加解密过程算不算是一种系统调用?

在这篇博客中,我们会先简单探究AES加解密的原理,然后对比ruby和golang的实现过程。

产生的疑问

在项目开发的过程中,我们时常会用户会用到AES加解密, 但也仅仅是利用第三方库帮我们实现功能模块,有点不求甚解的意思。下面先列出一下疑问,顺着这些疑问,一步步去探索。

  1. 十六进制字符串是什么?
  2. AES究竟是如何分组的?
  3. CBC与IV是什么?
  4. AES填充(Padding)算法
  5. ruby和golang的代码实现
  6. AES与Base64之间的关系

十六进制字符串

在我们开发的过程中,时常会遇到这种情况:需要将字符串以16进制字符串的形式作为标准输出,方便我们来阅读和校验。大部分的现代系统都是使用ASCII标准来表示文本字符,这种方式实际上就是用一个唯一的单字节大小的整数值来表示每个字符,我们又称之为字符的ASCII码表示。
现在我们分别用golang和ruby生成十六进制字符串:

package main

import (
	"bytes"
	"encoding/hex"
	"fmt"
)
func main() {
    src := []byte("Hello") 
	encodedStr := hex.EncodeToString(src) //[]byte ---> string
	fmt.Println(src) // [72 101 108 108 111], src 为byte数组,Println输出的是‘Hello’的ASCII码表示
	fmt.Println(encodedStr) // 48656c6c6f -> 48(4*16+8=72) 65(6*16+5=101) 6c 6c 6f
   
    key, _ := hex.DecodeString("ef3809cf2d5dc9253e0b4a27e4b67bbb") // 字符串转化为16进制数组,string ---> []bytes
    fmt.Println(key) //[239 56 9 207 45 93 201 37 62 11 74 39 228 182 123 187], key 为byte 数组
    fmt.Printf("[key test]: %x\n", key) //ef3809cf2d5dc9253e0b4a27e4b67bbb,
}

ruby中Array#pack和String#unpack方法使我们可以很轻松地实现数组与二(多)进制字符串的转换。
字符串"ef3809cf2d5dc9253e0b4a27e4b67bbb"的ASCII码数组表示为:[101, 102 ,51, 56, 48, 57, 99, 102, 50, 100, 53, 100, 99, 57, 50, 53, 51, 101, 48, 98, 52, 97, 50, 55, 101, 52, 98, 54, 55, 98, 98, 98]

temp1 = [101, 102 ,51, 56, 48, 57, 99, 102, 50, 100, 53, 100, 99, 57, 50, 53, 51, 101, 48, 98, 52, 97, 50, 55, 101, 52, 98, 54, 55, 98, 98, 98]
temp1.pack("C*")   # ef3809cf2d5dc9253e0b4a27e4b67bbb ,C 代表的是 unsigned char
temp2 = "ef3809cf2d5dc9253e0b4a27e4b67bbb"
[temp2].pack("H*")  # 生成16进制的字符串, 在rails console 显示为乱码
[temp2].pack("H*").unpack("H*")  # ef3809cf2d5dc9253e0b4a27e4b67bbb, 普通字符串

AES是如何分组的?

美国国家标准技术研究所在2001年发布了高级加密标准(AES)。AES是基于数据块的加密方式,即,每次处理的数据是一块(16字节),当数据不是16字节的倍数时填充,这就是所谓的分组密码(区别于基于比特位的流密码),16字节是分组长度。AES分组加密模型
AES根据使用的密码(密钥)位数,AES最常见的有3种方案,用以适应不同的场景要求,分别是AES-128、AES-192和AES-256。目前我们最常用的微信小程序使用的是AES-128。

CBC与IV是什么?

AES是基于数据块的加密方式,也就是说,每次处理的数据是一块(16字节),
当数据不是16字节的倍数时填充(结尾填充),这就是所谓的分组密码(区别于基于比特位的流密码),16字节是分组长度。
AES把看的见的信息(明文),分成很多相同组(明文块),一般为128位(16字节)。对每组进行单独加密,然后再把各加密块拼接成一条密文。下面是两种我们比较常见的分组加密模式,CBC是比ECB安全性更好的加密模式。

ECB:是一种基础的加密方式,密文被分割成分组长度相等的块(不足补齐),然后单独一个个加密,一个个输出组成密文。相同的输入产生相同的输出。

在这里插入图片描述

CBC:是一种循环模式,前一个分组的密文和当前分组的明文异或操作后再加密。相同的输入产生不同的输出,增加破译密文的难度。

在这里插入图片描述
在CBC加密的过程中,初始向量IV就显得很重要。因为这个加密模式是链式的,后一块加密需要前一块密文块作为基础,所以第一块需要一个需要初始化向量IV做基础。

AES填充(Padding)算法

AES支持支持多种填充:PKCS5Padding,ISO10126Padding,PaddingMode.Zeros ,PaddingMode.PKCS7。在这里,主要介绍一下PaddingMode.Zeros ,PaddingMode.PKCS7,因为这两种算法比较重要也比较常常见。
PaddingMode.PKCS7就是16字节为一组,数据少几个就填充几个:
假定块长度为 8,数据长度为 9,
则填充用八位字节数等于 7,数据等于 FF FF FF FF FF FF FF FF FF:
数据: FF FF FF FF FF FF FF FF FF
PKCS7 填充: FF FF FF FF FF FF FF FF FF 07 07 07 07 07 07 07

PaddingMode.Zeros的填充方式更简单,就是在后面补充0。但是这里有一点值得注意,利用这种填充方法,当数据刚好是16字节时候,我们要继续往后填充16个字节:
数据: 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f
Zeros填充:01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00

golang 和 ruby的代码实现

下面的例子分别用golang和ruby实现了AES-128-CBC算法。
golang:

package main

import (
   "bytes"
   "crypto/aes"
   "crypto/cipher"
   "encoding/hex"
   "fmt"
)
func Pad(src []byte)[]byte {
	padding := aes.BlockSize - len(src)%aes.BlockSize
	padtext := bytes.Repeat([]byte{0}, padding)
	return append(src, padtext...)
}
func main(){
    key, _ := hex.DecodeString("ef4809cf2d5dc9253e0b4a27e4b67bbb")  //随机生成的加密key
    plaintext, _ := hex.DecodeString("000102030405060708090a0b0c0d0e0f10111213") // 加密数据
    plaintext = Pad(plaintext) //数据填充, 这里使用的是PaddingMode.Zeros
    if len(plaintext)%aes.BlockSize != 0 {
    	panic("paintext unpacking")
	}
    block, err := aes.NewCipher(key)
    if err != nil{
    	panic(err)
	}
    iv, _ := hex.DecodeString("0a0b0c0d010204060b0a0d0c0f0f0f0f")
    mode := cipher.NewCBCEncrypter(block,iv)	
    cipher := make([]byte, len(plaintext))
    mode.CryptBlocks(cipher, plaintext)
    fmt.Printf("[Cipher]: %x\n", cipher)
}    

ruby实现

require 'open3'

 iv = '0a0b0c0d010204060b0a0d0c0f0f0f0f'
 msg = '000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f'
 key = 'ef4809cf2d5dc9253e0b4a27e4b67bbb'
 aes = OpenSSL::Cipher::Cipher.new("AES-128-CBC")
 aes.encrypt
 aes.padding = 0
 aes.iv = [iv].pack('H*')
 aes.key = [key].pack('H*')
 txt = aes.update([msg].pack('H*')) << aes.final
 res = txt.unpack('H*')

AES与Base64之间的关系

在日常开发中, 常常利用Base64对AES密文进行编码。Base64是网络上最常见的用于字节代码的编码方式之一(一个字母就是一字节byte)。采用Base64编码具有不可读性,即所编码的数据不会被人用肉眼所直接看到,Base64编码非常适合HTTP环境下传递较长的标识信息(传输8Bit字节信息),我们也常常利用Base64对二进制数据进行编码具体的Base64原理请看这这里

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值