• Posts tagged "digest"

Blog Archives

R语言进行AES对称加密

R的极客理想系列文章,涵盖了R的思想,使用,工具,创新等的一系列要点,以我个人的学习和体验去诠释R的强大。

R语言作为统计学一门语言,一直在小众领域闪耀着光芒。直到大数据的爆发,R语言变成了一门炙手可热的数据分析的利器。随着越来越多的工程背景的人的加入,R语言的社区在迅速扩大成长。现在已不仅仅是统计领域,教育,银行,电商,互联网….都在使用R语言。

要成为有理想的极客,我们不能停留在语法上,要掌握牢固的数学,概率,统计知识,同时还要有创新精神,把R语言发挥到各个领域。让我们一起动起来吧,开始R的极客理想。

关于作者:

  • 张丹,分析师/程序员/Quant: R,Java,Nodejs
  • blog: http://blog.fens.me
  • email: bsspirit@gmail.com

转载请注明出处:
http://blog.fens.me/r-crypto-aes/

前言

本文是介绍openssl包使用的第二篇文章,主要介绍AES算法的使用,还搭配了digest包做对比。虽然R语言不是专门做密码研究的工具,但实现个算法还是很方便的。作为数据分析师又学到了一些冷门知识,说不定哪天就会有用呢。openssl的文章分别是,R语言进行非对称加密RSAR语言进行AES对称加密R语言配合openssl生成管理应用x509证书用R语言实现RSA+AES混合加密

加密算法涉及到密码学的很多深入的知识,我并没有深入研究,本文只针对于openssl的使用,如果有任何与专业教材不符合的描述,请以专业教材为准。

目录

  1. AES算法介绍
  2. 用digest包进行AES加密解密
  3. 用openssl包进行AES加密解密

1. AES算法介绍

AES高级加密标准(Advanced Encryption Standard)为最常见的对称加密算法,是美国联邦政府采用的一种区块加密标准。这个标准用来替代原先的DES(Data Encryption Standard)。

AES的区块长度固定为128位,密钥长度则可以是128 bit,192 bit 或256位 bit 。换算成字节长度,就是密码必须是 16个字节,24个字节,32个字节。AES密码的长度更长,破解难度就增大了,所以就更安全。

AES 是对称加密算法,优点:加密速度快;缺点:如果秘钥丢失,就容易解密密文,安全性相对比较差。RSA 是非对称加密算法 , 优点:安全 ;缺点:加密速度慢。RSA算法介绍,请参见文章用openssl生成RSA私钥和公钥

AES算法的使用场景,发送方将要发送的明文数据X使用秘钥K进行AES加密后会得到密文Y,将密文进行网络传输,接受方在收到密文Y后使用秘钥K进行AES解密后技能得到明文X,这样即使密文Y在网络上传输时被截获了,没有秘钥也难以破解其真实意思。

AES的加密模式有以下几种

  • 电码本模式(Electronic codebook,ECB):需要加密的消息按照块密码的块大小被分为数个块,并对每个块进行独立加密。
  • 密码分组链接模式(CBC):将整段明文切成若干小段,然后每一小段与初始块或者上一段的密文段进行异或运算后,再与密钥进行加密。
  • 计算器模式(CTR):每个分组对应一个逐次累加的计数器,并通过对计数器进行加密来生成密钥流。
  • 密码反馈模式(CFB):前一个密文分组会被送入密码算法的输入端,再将输出的结果与明文做异或。与ECB和CBC模式只能够加密块数据不同,CFB能够将块密文(Block Cipher)转换为流密文。
  • 输出反馈模式(OFB):前一组密码算法输出会输入到下一组密码算法输入。先用块加密器生成密钥流,然后再将密钥流与明文流异或得到密文流,解密是先用块加密器生成密钥流,再将密钥流与密文流异或得到明文,由于异或操作的对称性所以加密和解密的流程是完全一样的。

在这五种模式里,只有ECB和CBC模式明文数据要求填充至长度为分组长度(16)的整数倍,因为ECB,CBC的加密运算会影响结果,而OFB,CFB,CTR只是最后一步的异或明文,所以不会影响结果,所以我们需要填充。

2. 用digest包进行AES加密解密

我们可以使用digest包的AES函数,进行AES的加密和解密操作,digest包的详细介绍,请参考文章R语言创建哈希摘要digest

使用AES函数时,需要输入3个参数:key, mode, IV。

  • key, 分别作为AES-128、AES-192或AES-256,对应为16、24或32字节的原始向量的密钥。
  • mode, 要使用的加密模式。目前只支持 “电子密码本”(ECB)、”密码块链”(CBC)、”密码反馈”(CFB)和 “计数器”(CTR)模式。
  • IV,偏移量,在CBC和CFB模式的初始向量或CTR模式的初始计数器

2.1 ECB模式
使用ECB模式进行加密,可以使用同一个AES实例进行加密和解密。要求输入的原始数据为16的整数倍。


> library(digest)

# 把明文的转成二进制数据
> msg<- as.raw(c(1:16,1:32));msg
 [1] 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f 10 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f 10 11 12 13
[36] 14 15 16 17 18 19 1a 1b 1c 1d 1e 1f 20

# key转成二进制数据
> key <- as.raw(1:16)#;key

# 建立ECB的AES实例
> aes <- AES(key, mode="ECB")

# 加密
> a<-aes$encrypt(msg)

# 解密
> b<-aes$decrypt(a, raw=TRUE);b
 [1] 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f 10 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f 10 11 12 13
[36] 14 15 16 17 18 19 1a 1b 1c 1d 1e 1f 20

2.2 CBC模式
使用CBC模式进行加密时,不能使用同一个AES实例进行加密和解密,需要一个新的AES实例进行解密。


# 把明文的转成二进制数据
> msg<- as.raw(c(1:16,1:32));msg
 [1] 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f 10 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f 10 11 12 13
[36] 14 15 16 17 18 19 1a 1b 1c 1d 1e 1f 20

# key转成二进制数据
> key <- as.raw(1:16)#;key

# 偏移量转成二进制数据
> iv <- rand_bytes(16)#;iv

# 建立CBC的AES实例
> aes <- AES(key, mode="CBC",iv)

# 加密
> a<-aes$encrypt(msg)

建立另一个实例,进行解密。


> aes2 <- AES(key, mode="CBC",iv)

# 解密
> b<-aes2$decrypt(a, raw=TRUE);b
 [1] 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f 10 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f 10 11 12 13
[36] 14 15 16 17 18 19 1a 1b 1c 1d 1e 1f 20

2.3 CFB模式
使用CFB模式进行加密时,不能使用同一个AES实例进行加密和解密,需要一个新的AES实例进行解密,同时要求IV的偏移量长度,与块的大小一致。


# 原始数据
> msg<- as.raw(c(1:16,1:32));msg
 [1] 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f 10 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f 10 11 12 13
[36] 14 15 16 17 18 19 1a 1b 1c 1d 1e 1f 20

# 创建IV偏移量,16字节
> iv <- rand_bytes(16)#;iv

# 创建AES实例
> aes <- AES(key, mode="CFB", iv)

# 查看块大小
> aes$block_size()
[1] 16

# 加密
> code <- aes$encrypt(msg)

# 新建实例
> aes2 <-  AES(key, mode="CFB", iv)

# 解密
> aes2$decrypt(code,raw=TRUE)
 [1] 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f 10 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f 10 11 12 13
[36] 14 15 16 17 18 19 1a 1b 1c 1d 1e 1f 20

2.4 CTR模式


> msg<- as.raw(c(1:16,1:16));msg
 [1] 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f 10 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f 10
> key <- as.raw(1:16)
> iv <- rand_bytes(16)
> aes <- AES(key, mode="CTR", iv)
> code<-aes$encrypt(msg)
> aes2 <-  AES(key, mode="CTR", iv)
> aes2$block_size()
[1] 16
> aes2$decrypt(code,raw=TRUE)
 [1] 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f 10 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f 10

2.5 报错:Text length must be a multiple of 16 bytes
在加密过程中,aes$encrypt(msg),msg文本可以是一个单元素的字符向量或一个原始向量,被要求字节长度是16字节长度的倍数。如果原始文本的二进制为非16字节长度,则会有 Text length must be a multiple of 16 bytes 错误。


# 任意原始文字
> msg<- charToRaw("ABCDj@*(;dj! 测试一下中文");msg
 [1] 41 42 43 44 6a 40 2a a3 a8 3b 64 6a 21 20 b2 e2 ca d4 d2 bb cf c2 d6 d0 ce c4
> key <- as.raw(1:16)
> aes <- AES(key, mode="ECB")

# 非16长度倍数
> a<-aes$encrypt(msg)
Error in aes$encrypt(msg) : Text length must be a multiple of 16 bytes

2.6 PKCS5和PKCS7填充
当出现上面的错误的时候,我们需要用PKCS5和PKCS7进行填充补值。加密算法要求明文需要按一定长度对齐,叫做块大小(BlockSize),比如16字节,那么对于一段任意的数据,加密前需要对最后一个块填充到16 字节,解密后需要删除掉填充的数据。

  • PKCS5,PKCS7Padding的子集,块大小固定为8字节。。
  • PKCS7,块大小固定为1-256字节的长度进行分组,最后分剩下那一组,不够长度,就需要进行补齐。

由于digest包,没有提供实现,我自己写了3个函数,用于加密时填充补值和解密是移除补值。

原始字符转二进制补齐16的倍数长度
A4141 0f 0f 0f 0f 0f 0f 0f 0f 0f 0f 0f 0f 0f 0f 0f
AB41 4241 42 0e 0e 0e 0e 0e 0e 0e 0e 0e 0e 0e 0e 0e 0e
ABC41 42 4341 42 43 0d 0d 0d 0d 0d 0d 0d 0d 0d 0d 0d 0d 0d
ABCD41 42 43 4441 42 43 44 0c 0c 0c 0c 0c 0c 0c 0c 0c 0c 0c 0c
ABCDE41 42 43 44 4541 42 43 44 45 0b 0b 0b 0b 0b 0b 0b 0b 0b 0b 0b
ABCDEF41 42 43 44 45 4641 42 43 44 45 46 0a 0a 0a 0a 0a 0a 0a 0a 0a 0a
ABCDEFG41 42 43 44 45 46 4741 42 43 44 45 46 47 09 09 09 09 09 09 09 09 09
ABCDEFGH41 42 43 44 45 46 47 4841 42 43 44 45 46 47 48 08 08 08 08 08 08 08 08
ABCDEFGHI41 42 43 44 45 46 47 48 4941 42 43 44 45 46 47 48 49 07 07 07 07 07 07 07
ABCDEFGHIJ41 42 43 44 45 46 47 48 49 4a41 42 43 44 45 46 47 48 49 4a 06 06 06 06 06 06
ABCDEFGHIJK41 42 43 44 45 46 47 48 49 4a 4b41 42 43 44 45 46 47 48 49 4a 4b 05 05 05 05 05
ABCDEFGHIJKL41 42 43 44 45 46 47 48 49 4a 4b 4c41 42 43 44 45 46 47 48 49 4a 4b 4c 04 04 04 04
ABCDEFGHIJKLM41 42 43 44 45 46 47 48 49 4a 4b 4c 4d41 42 43 44 45 46 47 48 49 4a 4b 4c 4d 03 03 03
ABCDEFGHIJKLMN41 42 43 44 45 46 47 48 49 4a 4b 4c 4d 4e41 42 43 44 45 46 47 48 49 4a 4b 4c 4d 4e 02 02
ABCDEFGHIJKLMNO41 42 43 44 45 46 47 48 49 4a 4b 4c 4d 4e 4f41 42 43 44 45 46 47 48 49 4a 4b 4c 4d 4e 4f 01
ABCDEFGHIJKLMNOP41 42 43 44 45 46 47 48 49 4a 4b 4c 4d 4e 4f 5041 42 43 44 45 46 47 48 49 4a 4b 4c 4d 4e 4f 50 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10
ABCDEFGHIJKLMNOPQ41 42 43 44 45 46 47 48 49 4a 4b 4c 4d 4e 4f 50 5141 42 43 44 45 46 47 48 49 4a 4b 4c 4d 4e 4f 50 51 0f 0f 0f 0f 0f 0f 0f 0f 0f 0f 0f 0f 0f 0f 0f

接下来,自己写个函数进行实现


# PKCS5填充
> pkcs5_padding<-function(text){
+   bit<-8
+   if(!is.raw(text)){
+     text<-charToRaw(text)
+   }
+   b<- bit - (length(text)+bit) %% bit
+   c(text,as.raw(rep(as.hexmode(b),b)))
+ }

# PKCS7填充
> pkcs7_padding<-function(text,bit=16){
+   if(bit>256 | bit<1){
+     stop("bit is not in 1-256")
+   }
+   if(!is.raw(text)){
+     text<-charToRaw(text)
+   }
+   b<- bit - (length(text)+bit) %% bit
+   c(text,as.raw(rep(as.hexmode(b),b)))
+ }

# PKCS7移除
> pkcs_strip<-function(rtext){
+   n<-length(rtext)
+   pos<-as.integer(rtext[n])
+   rtext[1:c(n-pos)]
+ }

输出自定的字符串,使用pkcs7_padding()来自动补齐数据。


# 原始明文转二进制
> plaintext<-charToRaw("ABCDj@*(;dj! 测试一下中文");plaintext
 [1] 41 42 43 44 6a 40 2a a3 a8 3b 64 6a 21 20 b2 e2 ca d4 d2 bb cf c2 d6 d0 ce c4

# pkcs7_padding填充
> ptext<-pkcs7_padding(plaintext)

# 创建AES对象
> aes <- AES(key, mode="ECB")

# 加密
> a <- aes$encrypt(ptext);a
 [1] 63 e6 d4 e5 74 c1 13 b3 d5 be 1f 0f a6 db 75 50 d7 d8 3d fb 53 b0 4e 61 67 d1 91 a6 db fe b8 e0

# 解密
> b<-aes$decrypt(aes128, raw=TRUE);b
 [1] 41 42 43 44 6a 40 2a a3 a8 3b 64 6a 21 20 b2 e2 ca d4 d2 bb cf c2 d6 d0 ce c4 06 06 06 06 06 06

# 移除增加的补齐数据
> pkcs7_strip(b)
 [1] 41 42 43 44 6a 40 2a a3 a8 3b 64 6a 21 20 b2 e2 ca d4 d2 bb cf c2 d6 d0 ce c4

# 把二进制转明文
> rawToChar(pkcs7_strip(b))
[1] "ABCDj@*(;dj! 测试一下中文"

3. 用openssl包进行AES加密解密

我们可以使用openssl包的AES函数,进行AES的加密和解密操作,openssl包的详细介绍,请参考文章用openssl生成RSA私钥和公钥

在openssl包中,支持CBC,CTR,GCM三种模式,分别对应aes_cbc_encrypt(), aes_ctr_encrypt(), aes_gcm_encrypt()的三种函数。

在CBC模式下使用AES块状密码进行低级别的对称加密/解密。密钥是一个原始向量,例如一些秘密的散列。当没有共享秘密时,可以使用随机密钥,通过非对称协议(如RSA)进行交换。

从API的使用上,openssl包提供的API函数,要比digest包提供的API函数,看起来更容易一点。

3.1 CBC模式


> library(openssl)

# 设置key 和 iv
> key <- aes_keygen();key
aes e2:40:0a:12:33:9e:83:e9:04:d5:5c:aa:6f:40:d2:cc 
> iv <- rand_bytes(16);iv
 [1] 9a a7 24 f9 1d eb da 25 30 f9 ba b3 1b 69 12 e4

# 原始字符转字节码
> msg<-charToRaw("ABCDj@*(;dj! 测试一下中文");msg
 [1] 41 42 43 44 6a 40 2a a3 a8 3b 64 6a 21 20 b2 e2 ca d4 d2 bb cf c2 d6 d0 ce c4

# 加密
> blob <-    aes_cbc_encrypt(msg, key, iv = iv)

# 解密
> message <- aes_cbc_decrypt(blob, key, iv)

# 字节码转字符串
> out <- rawToChar(message);out
[1] "ABCDj@*(;dj! 测试一下中文"

3.2 CTR模式


> key <- aes_keygen();key
aes 11:65:5a:17:2d:28:f4:92:38:44:90:b1:45:cb:d2:5f 
> iv <- rand_bytes(16);iv
 [1] 94 e0 f6 b1 6a 64 e6 0a a0 cf c1 13 ea be 65 dd
> msg<-charToRaw("ABCDj@*(;dj! 测试一下中文");msg
 [1] 41 42 43 44 6a 40 2a a3 a8 3b 64 6a 21 20 b2 e2 ca d4 d2 bb cf c2 d6 d0 ce c4
> blob <-    aes_ctr_encrypt(msg, key, iv = iv)
> message <- aes_ctr_decrypt(blob, key, iv)
> out <- rawToChar(message);out
[1] "ABCDj@*(;dj! 测试一下中文"

3.3 GCM模式


> key <- aes_keygen();key
aes 38:31:8a:ea:66:96:c7:7d:6d:72:d5:bd:75:62:92:db 
> iv <- rand_bytes(12);iv
 [1] 99 ca b1 36 bf b9 af 78 c5 22 a4 06
> msg<-charToRaw("ABCDj@*(;dj! 测试一下中文");msg
 [1] 41 42 43 44 6a 40 2a a3 a8 3b 64 6a 21 20 b2 e2 ca d4 d2 bb cf c2 d6 d0 ce c4
> blob <-    aes_gcm_encrypt(msg, key, iv = iv)
> message <- aes_gcm_decrypt(blob, key, iv)
> out <- rawToChar(message);out
[1] "ABCDj@*(;dj! 测试一下中文"

本文介绍对称加密算法AES在digest包和openssl包的使用,虽然加密和解密不是R语言的主业,但是真的实现起来,没想到还是挺方便的。哈哈哈,又学到一招。

本文代码已上传到github: https://github.com/bsspirit/encrypt/blob/master/aes.r

转载请注明出处:
http://blog.fens.me/r-crypto-aes/

打赏作者

R语言创建哈希摘要digest

R的极客理想系列文章,涵盖了R的思想,使用,工具,创新等的一系列要点,以我个人的学习和体验去诠释R的强大。

R语言作为统计学一门语言,一直在小众领域闪耀着光芒。直到大数据的爆发,R语言变成了一门炙手可热的数据分析的利器。随着越来越多的工程背景的人的加入,R语言的社区在迅速扩大成长。现在已不仅仅是统计领域,教育,银行,电商,互联网….都在使用R语言。

要成为有理想的极客,我们不能停留在语法上,要掌握牢固的数学,概率,统计知识,同时还要有创新精神,把R语言发挥到各个领域。让我们一起动起来吧,开始R的极客理想。

关于作者:

  • 张丹,分析师/程序员/Quant: R,Java,Nodejs
  • blog: http://blog.fens.me
  • email: bsspirit@gmail.com

转载请注明出处:
http://blog.fens.me/r-crypto-hash-digest/

前言

哈希算法,被用于对任意一组输入数据进行计算,得到一个固定长度的输出摘要,为了验证原始数据是否被篡改。哈希算法如md5,sha1,sha2等算法普遍被应用于各种数据传输和数据交互的场景。

digest包,提供了R语言在哈希计算上的能力,好用,方便,高效。

目录

  1. digest包介绍
  2. md5哈希摘要
  3. sha1哈希摘要
  4. sha256哈希摘要
  5. 其他算法哈希摘要
  6. 场景1:基于哈希的消息身份验证码hmac
  7. 场景2:文件验证checksum

1. digest包介绍

在digest包,包括了多种的hash加密算法的计算函数,包括了md5, sha1, crc32, sha256, sha512, xxhash32, xxhash64, murmur32, spookyhash, blake3,我们可以通过digest()函数直接调用。

安装digest非常简单,digest包没有底层依赖,一条命令就搞定了。


# 安装digest
> install.pacakges(digest)

# 加载digest
> library(digest)

# 初始化的字符值
> value<-"abc=!@#$%^&*()_+abc试试中文"

2. md5哈希摘要

MD5信息摘要算法(英语:MD5 Message-Digest Algorithm),一种被广泛使用的密码散列函数,可以产生出一个128位(16字节)的散列值(hash value),用于确保信息传输完整一致。MD5由MD4、MD3、MD2改进而来,主要增强算法复杂度和不可逆性。MD5算法因其普遍、稳定、快速的特点,仍广泛应用于普通数据的加密保护领域。

MD5算法的原理可简要的叙述为:MD5码以512位分组来处理输入的信息,且每一分组又被划分为16个32位子分组,经过了一系列的处理后,算法的输出由四个32位分组组成,将这四个32位分组级联后将生成一个128位散列值

直接使用digest函数,计算md5。我们可以考虑是否需要先对原数据做序列化(serialize),默认是支持序列化的。关于序列化介绍见文章,请参见 R语言中的编码和解码


# 带序列化
> d1<-digest(value, algo="md5", serialize=TRUE);d1
[1] "ac4dea3cc2f21f37a5d9f89fd911e9c2"

# 不带序列化
> d2<-digest(value, algo="md5", serialize=FALSE);d2
[1] "2979b0983cb7e1031fb149d4da902a99"

另化一种写法,如果我想单独获得一个md5函数,可以获取算法函数的句柄。


# 定义md5()函数
> md5<-getVDigest()

# 用自定义函数进行计算
> md5(value)
[1] "ac4dea3cc2f21f37a5d9f89fd911e9c2"

3. sha1哈希摘要

SHA-1(英语:Secure Hash Algorithm 1,中文名:安全散列算法1)是一种密码散列函数,美国国家安全局设计。SHA-1可以生成一个被称为消息摘要的160位(20字节)散列值,散列值通常的呈现形式为40个十六进制数。

安全哈希算法(Secure Hash Algorithm)主要适用于数字签名标准(Digital Signature Standard DSS)里面定义的数字签名算法(Digital Signature Algorithm DSA)。对于长度小于2^64位的消息,SHA1会产生一个160位的消息摘要。当接收到消息的时候,这个消息摘要可以用来验证数据的完整性。在传输的过程中,数据很可能会发生变化,那么这时候就会产生不同的消息摘要。

SHA1特性支持,不可以从消息摘要中复原信息,两个不同的消息不会产生同样的消息摘要。

 
# 计算sha1
> digest(value, algo="sha1")
[1] "a6a04474e0f670b78b18c9cbaff3c7296befb172"

# 同digest
> sha1_digest(value)
[1] "a6a04474e0f670b78b18c9cbaff3c7296befb172"

另化一种写法,如果我想单独获得一个mysha1的函数,可以获取算法函数的句柄。


# 用自定义函数进行计算
> mysha1<-getVDigest("sha1")
> mysha1(value)
[1] "a6a04474e0f670b78b18c9cbaff3c7296befb172"

为老版本遗留的函数sha1.xxx()一组函数,sha1.xxx()函数用于支持digest包的0.6.14之前的版本,我当前使用为0.6.29版本,所以计算结果是不同。


> sha1(value,algo = "sha1") # digest <= 0.6.14
[1] "b62567b5a0dc60103ff91f31ec08980e0f53e695"

4. sha256哈希摘要

SHA-2,名称来自于安全散列算法2(英语:Secure Hash Algorithm 2)的缩写,一种密码散列函数算法标准,由美国国家安全局研发,由美国国家标准与技术研究院(NIST)在2001年发布。属于SHA算法之一,是SHA-1的后继者。其下又可再分为六个不同的算法标准,包括了:SHA-224、SHA-256、SHA-384、SHA-512、SHA-512/224、SHA-512/256。

对于任意长度的消息,SHA256都会产生一个256位的哈希值,称作消息摘要。这个摘要相当于是个长度为32个字节的数组,通常有一个长度为64的十六进制字符串来表示,其中1个字节=8位,一个十六进制的字符的长度为4位。

SHA256的压缩函数主要对512位的消息区块和256位的中间哈希值进行操作,本质上,它是一个通过将消息区块为密钥对中间哈希值进行加密的256位加密算法。


# sha256
> digest(value, algo="sha256")
[1] "4135d8f5fa9027f05a6b927ccc1501d73f9f30189830886f3d287a0606cc2e2b"

一个惊艳的sha256的计算动画,https://sha256algorithm.com/。最上为字符串输入,最下面为sha256的输出,中间为计算过程。

5. 其他算法哈希摘要

digest支持的其他的哈希算法,由于我也不太清晰,就不具体解释了,附上R语言的调用代码,大家随用随取。(偷懒一下)


# sha512
> digest(value, algo="sha512")
[1] "32f736716db2ae7d93b8df2f21e50d3f9bbf94f07c77ea829ab001f97a74f4d25d086d1c7a1e9e9c577c9e5765c7f5ce9c4a3c3010d83551922e1c6c9641922c"

# crc32
> digest(value, algo="crc32")
[1] "5da20b48"

# xxhash32
> digest(value, algo="xxhash32")
[1] "8104f39e"

# xxhash64
> digest(value, algo="xxhash64")
[1] "9a1c77be269ddf8d"

# murmur32
> digest(value, algo="murmur32")
[1] "9ea85aec"

# spookyhash
> digest(value, algo="spookyhash")
[1] "1bd5a355dde8fbbd4af6948d84ddab36"

# blake3
> digest(value, algo="blake3")
[1] "be8b5792da47d27c039325430fab72eb160b7887b7b34a0f9e01d7eb6030e52e"

6. 场景1:基于哈希的消息身份验证码

HMAC是密钥相关的哈希运算消息认证码(Hash-based Message Authentication Code)的缩写,为了确保接收方所接收到的报文数据的完整性,人们采用消息认证来验证。

HMAC运算利用hash算法,以一个消息M和一个密钥K作为输入,生成一个定长的消息摘要作为输出。HMAC算法更象是一种加密算法,它引入了密钥,其安全性已经不完全依赖于所使用的Hash算法,安全性主要有以下几点保证。
(1)使用的密钥是双方事先约定的,第三方不可能知道。作为非法截获信息的第三方,能够得到的信息只有作为“挑战”的随机数和作为“响应”的HMAC 结果,无法根据这两个数据推算出密钥。由于不知道密钥,所以无法仿造出一致的响应。
(2)HMAC与一般的加密重要的区别在于它具有“瞬时”性,即认证只在当时有效,而加密算法被破解后,以前的加密结果就可能被解密。

使用hmac函数进行实现,第一参数key,第二个参数为消息数据Object。


# 使用md5散列
> hmac('fens.me', value, "md5")
[1] "fe969197e99e311c8cf0c08418c25861"

# 使用sha1散列
> hmac('fens.me', value, "sha1")
[1] "acc2c9dae75272b5fa301020697f06ff8a7588c0"

7. 场景2:文件验证checksum

通过md5用来生成文件摘要,来验证文件是否是原始文件,防止文件在传输过程中被恶意篡改,是一个典型的应用场景。

我们下载软件的时候,下载的网页上会提示checksum码,用于告知这个软件的哈希摘要是什么,由于算法不可逆所以一个程序包只能匹配唯一码,类似于防伪验证码。我们把软件下载后可以比对这个验证码,当不一样时可能是软件包被改动过,加了病毒或者木马等恶意程序,特别在第三方下载站下载软件时,验证真实性就显得尤为重要。

以GO语言的下载举例:https://golang.google.cn/dl/ 。go语言软件包,提供了对应的摘要,是用的sha256来生成的哈希摘要。

在我们下载R的依赖的包时,安装过程会对每个文件做checksum检验,里面会包括一个文件名为MD5的文件,这个文件记录当前包的所有文件的md5值。我们可以使用tools包的md5sum()和checkMD5sums()函数进行检查。

以我本地安装的digest包为例。


# 加载tools包
> library(tools)

# 获得本地安装包的路径
> path<-paste0(.libPaths()[1],"/digest");path
[1] "C:/Users/bsspi/Documents/R/win-library/4.0/digest"

# 打印路径下载的文件
> dir(path)
 [1] "demo"        "DESCRIPTION" "doc"         "GPL-2"       "help"        "html"        "include"     "INDEX"      
 [9] "libs"        "MD5"         "Meta"        "NAMESPACE"   "R"           "tinytest"   

# 找到MD5文件
> file_md5<-paste0(path,"/MD5")

# 查看文件内容
> file_md5<-paste0(path,"/MD5");file_md5
[1] "C:/Users/bsspi/Documents/R/win-library/4.0/digest/MD5"
> readLines(file_md5)
 [1] "4f4d87b5b82aae40bdca115168d2f489 *DESCRIPTION"                         
 [2] "b234ee4d69f5fce4486a80fdaf4a4263 *GPL-2"                               
 [3] "d3b2d6e126889679a20971bb40faf95b *INDEX"                               
 [4] "1ef110b7a4e87edf0d4ce7e884e25c1c *Meta/Rd.rds"                         
 [5] "d90f7534e5fee11d3afe7aea19da26eb *Meta/demo.rds"                       
 [6] "4d1492de7968466564e971368d24d147 *Meta/features.rds"                   
 [7] "5023c75285b93023b6ff55d53314dd43 *Meta/hsearch.rds"                    
 [8] "4999aeefa92e6fdefc0b1648ec4dc7a0 *Meta/links.rds"                      
 [9] "d7db2c4bb971cec5af4d6357a6d3b75b *Meta/nsInfo.rds"                     
[10] "ad9806ce7b382276603401f495b1b33d *Meta/package.rds"                    
[11] "44e7d30ab5a91d849e5fee71c6e03370 *Meta/vignette.rds"                   
[12] "14402ee218da75987f233d61fa84ab8d *NAMESPACE"                           
[13] "d6c68f1fe41ced6e98a766a3757313da *R/digest"                            
[14] "55c37b8148f9e69a8b76329fe3cf5ba0 *R/digest.rdb"                        
[15] "a9352f140fdd099efdb9d2cc1633064b *R/digest.rdx"                        
[16] "247a7f1a523cb39feb2941b00b82906a *demo/vectorised.R"                   
[17] "99f7ecc6dd6613e9ff2e27341242366e *doc/index.html"                      
[18] "0473d4bde070176922b60166512ee4e7 *doc/sha1.R"                          
[19] "2bafc9dc174d4c0c7a4f997c4e38ab22 *doc/sha1.html"                       
[20] "6b37c59ba0b533a23e48589c49be6851 *doc/sha1.md"                         
[21] "c7a5267ecd60c94cac5e63ffbfcf7c85 *help/AnIndex"                        
[22] "9206818c55699be0e654cb71d8846766 *help/aliases.rds"                    
[23] "b62adb06d160285a19664895c2a49732 *help/digest.rdb"                     
[24] "76643649e20b6e2733b143b479773dce *help/digest.rdx"                     
[25] "d636efb2f2b15c9b8904502c3ab1db6b *help/paths.rds"                      
[26] "7a3b8c4700297a80815dd58b11fe1102 *html/00Index.html"                   
[27] "b6763e6916890c631fdc3f2643803b1a *html/R.css"                          
[28] "82a62fb6337a2be9ae069fee3d50b614 *include/pmurhashAPI.h"               
[29] "9978920e2d0e2ee72edeb5b5c08a647c *libs/i386/digest.dll"                
[30] "8c56528c9759b547c278fed635eb3d96 *libs/i386/symbols.rds"               
[31] "38981a746981dc9ed975e55b0e9e57d4 *libs/x64/digest.dll"                 
[32] "431ee5ad2c9f14b5725275ba7e0b8eab *libs/x64/symbols.rds"                
[33] "4d75623e0ffe3c04eac1752b7d3d8f6b *tinytest/test_aes.R"                 
[34] "f930ce5c1f9bbcd3b6fe97fd7b5b27f8 *tinytest/test_blake3.R"              
[35] "052184be0e884920438e1779a03f8759 *tinytest/test_crc32.R"               
[36] "8ec7f892f2bcb4b146f78078f987237b *tinytest/test_digest.R"              
[37] "6681e580fd409258ed4fbdeb77a14cd4 *tinytest/test_digest2int.R"          
[38] "8e8415ddde36eb03eae83e0ae0e5bb0c *tinytest/test_encoding.R"            
[39] "2b3efdc9712ac7f956f79a80da4d0d00 *tinytest/test_hmac.R"                
[40] "557fbd734cbf96e7946b9dca3eaa9afe *tinytest/test_misc.R"                
[41] "40f6e8975785e79091b3a9dffd430e2b *tinytest/test_new_matrix_behaviour.R"
[42] "1d71bffa7dc302062c44ed92b57bdbcc *tinytest/test_num2hex.R"             
[43] "612e0a4832b86b69c8c04f2916f3b16a *tinytest/test_raw.R"                 
[44] "6e3548a6e9bdf2681f6bdc173ee0a8d2 *tinytest/test_sha1.R"  

手动验证DESCRIPTION文件md5摘要,与MD5文件中的摘要是一致的。

 
# 计算文件 DESCRIPTION 
> md5sum(paste0(path,"/DESCRIPTION"))
C:/Users/bsspi/Documents/R/win-library/4.0/digest/DESCRIPTION 
                           "4f4d87b5b82aae40bdca115168d2f489" 

# 对整个digest包的每个文件进行md5摘要验证
> checkMD5sums("digest",path)
[1] TRUE

digest能够支持我们常用的,多种哈希散列算法,真的是非常方便。最后留一个小尾巴,关于digest包的AES加密算法,我会在后面一篇加密算法的文章中进行介绍。

本文代码已上传到github: https://github.com/bsspirit/encrypt/blob/master/digest.r

转载请注明出处:
http://blog.fens.me/r-crypto-hash-digest/

打赏作者