• Posts tagged "sha1"

Blog Archives

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/

打赏作者

Node.js加密算法库Crypto

从零开始nodejs系列文章,将介绍如何利Javascript做为服务端脚本,通过Nodejs框架web开发。Nodejs框架是基于V8的引擎,是目前速度最快的Javascript引擎。chrome浏览器就基于V8,同时打开20-30个网页都很流畅。Nodejs标准的web开发框架Express,可以帮助我们迅速建立web站点,比起PHP的开发效率更高,而且学习曲线更低。非常适合小型网站,个性化网站,我们自己的Geek网站!!

关于作者

  • 张丹(Conan), 创业者,程序员(Java,R,Javascript)
  • weibo:@Conan_Z
  • blog: http://blog.fens.me
  • email: bsspirit@gmail.com

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

crypto

前言

密码技术是互联网应用的一项最基本的技术之一,主要保证了数据的安全。安全定义是多维度的,通过不可逆的hash算法可以保证登陆密码的安全;通过非对称的加密算法,可以保证数据存储的安全性;通过数字签名,可以验证数据在传输过程中是否被篡改。

我们要做互联网应用,数据安全性一个是不容忽视的问题。不然可能会造成,如CSDN的100万用户明文密码被泄露事情;携程网,100万用户个人信息泄露事情等。

Node.js的Crypto库就提供各种加密算法,可以非常方便地让我们使用密码技术,解决应用开发中的问题。

目录

  1. Crypto介绍
  2. Hash算法
  3. Hmac算法
  4. 加密和解密算法
  5. 签名和验证算法
  6. salt算法
  7. 程序代码

1. Crypto介绍

Crypto库是随Nodejs内核一起打包发布的,主要提供了加密、解密、签名、验证等功能。Crypto利用OpenSSL库来实现它的加密技术,它提供OpenSSL中的一系列哈希方法,包括hmac、cipher、decipher、签名和验证等方法的封装。

Crypto官方文档:http://nodejs.org/api/crypto.html

2. Hash算法

哈希算法,是指将任意长度的二进制值映射为较短的固定长度的二进制值,这个小的二进制值称为哈希值。哈希值是一段数据唯一且极其紧凑的数值表示形式。如果散列一段明文而且哪怕只更改该段落的一个字母,随后的哈希都将产生不同的值。要找到散列为同一个值的两个不同的输入,在计算上是不可能的,所以数据的哈希值可以检验数据的完整性。一般用于快速查找和加密算法。

通常我们对 登陆密码,都是使用Hash算法进行加密,典型的哈希算法包括 ‘md5′,’sha’,’sha1′,’sha256′,’sha512′,’RSA-SHA’。下面我们就做一下算法的测试。

系统环境

  • Win7 64bit
  • Nodejs:v0.10.31
  • Npm:1.4.23

创建项目


~ cd D:\workspace\javascript>
~ D:\workspace\javascript>mkdir nodejs-crypto && cd nodejs-crypto

新建文件hash.js,打印出所支持的hash算法。


~ vi hash.js

var crypto = require('crypto');  # 加载crypto库
console.log(crypto.getHashes()); # 打印支持的hash算法

运行程序


~ node hash.js

[ 'DSA',
  'DSA-SHA',
  'DSA-SHA1',
  'DSA-SHA1-old',
  'RSA-MD4',
  'RSA-MD5',
  'RSA-MDC2',
  'RSA-RIPEMD160',
  'RSA-SHA',
  'RSA-SHA1',
  'RSA-SHA1-2',
  'RSA-SHA224',
  'RSA-SHA256',
  'RSA-SHA384',
  'RSA-SHA512',
  'dsaEncryption',
  'dsaWithSHA',
  'dsaWithSHA1',
  'dss1',
  'ecdsa-with-SHA1',
  'md4',
  'md4WithRSAEncryption',
  'md5',
  'md5WithRSAEncryption',
  'mdc2',
  'mdc2WithRSA',
  'ripemd',
  'ripemd160',
  'ripemd160WithRSA',
  'rmd160',
  'sha',
  'sha1',
  'sha1WithRSAEncryption',
  'sha224',
  'sha224WithRSAEncryption',
  'sha256',
  'sha256WithRSAEncryption',
  'sha384',
  'sha384WithRSAEncryption',
  'sha512',
  'sha512WithRSAEncryption',
  'shaWithRSAEncryption',
  'ssl2-md5',
  'ssl3-md5',
  'ssl3-sha1',
  'whirlpool' ]

我们看到支持Hash真是不少,究竟怎么选择适合,我也说不清楚。以我对算法的理解,我会以加密计算时间和编码长度,做选择的依据。下面就简单地比较一下几种常见算法。

编辑hash.js文件


~ vi hash.js

///////////////////////////
// Hash算法
///////////////////////////
var crypto = require('crypto')
    ,fs = require('fs');

function hashAlgorithm(algorithm){
    var s1 = new Date();

    var filename = "package.json";
    var txt = fs.ReadStream(filename);

    var shasum = crypto.createHash(algorithm);
    txt.on('data', function(d) {
        shasum.update(d);
    });

    txt.on('end', function() {
        var d = shasum.digest('hex');
        var s2 = new Date();

        console.log(algorithm+','+(s2-s1) +'ms,'+ d);
    });
}

function doHash(hashs){
    hashs.forEach(function(name){
        hashAlgorithm(name);
    })
}

//var algs = crypto.getHashes();
var algs = [ 'md5','sha','sha1','sha256','sha512','RSA-SHA','RSA-SHA1','RSA-SHA256','RSA-SHA512'];
doHash(algs);

运行程序


~ node hash.js

md5,6ms,85cd416f811574bd4bdb61b241266670
sha,18ms,b1fc6647fa4fdb4b1b394f8dc7856ac140e2fbdb
sha1,20ms,0777e65066dca985569fa892fa88e21b45dc656d
sha256,21ms,5e4aea76f93ee87f422fcbd9458edad0e33ddf256d5d93bcc47977e33cb1654c
sha512,23ms,94249ec2f83b006354774dd8f8ec81125ea9e1e00f94393d8b20bbd7678e63db53fab6af125e139f9257fd7dbb6c69474e443d059903a9cb2dded03a94c8143
RSA-SHA,24ms,b1fc6647fa4fdb4b1b394f8dc7856ac140e2fbdb
RSA-SHA1,25ms,0777e65066dca985569fa892fa88e21b45dc656d
RSA-SHA256,26ms,5e4aea76f93ee87f422fcbd9458edad0e33ddf256d5d93bcc47977e33cb1654c
RSA-SHA512,26ms,94249ec2f83b006354774dd8f8ec81125ea9e1e00f94393d8b20bbd7678e63db53fab6af125e139f9257fd7dbb6c69474e4433d059903a9cb2dded03a94c8143

输出以逗号分隔,分别是算法名、时间、密文。最常见的md5,密文长度最短,计算时间也最少;sha和sha1比较接近;sha512密文最长,计算时间也最长。

由于md5已经有了大量的字典库,对于安全级别一般的网站用sha1吧;如果安全级别要求很高,CPU配置也很牛,可以考虑用sha512。

3. Hmac算法

HMAC是密钥相关的哈希运算消息认证码(Hash-based Message Authentication Code),HMAC运算利用哈希算法,以一个密钥和一个消息为输入,生成一个消息摘要作为输出。HMAC可以有效防止一些类似md5的彩虹表等攻击,比如一些常见的密码直接MD5存入数据库的,可能被反向破解。

定义HMAC需要一个加密用散列函数(表示为H,可以是MD5或者SHA-1)和一个密钥K。我们用B来表示数据块的字节数。(以上所提到的散列函数的分割数据块字长B=64),用L来表示散列函数的输出数据字节数(MD5中L=16,SHA-1中L=20)。鉴别密钥的长度可以是小于等于数据块字长的任何正整数值。应用程序中使用的密钥长度若是比B大,则首先用使用散列函数H作用于它,然后用H输出的L长度字符串作为在HMAC中实际使用的密钥。一般情况下,推荐的最小密钥K长度是L个字节。

由于HMAC就是使用散列函数,所以我们同样选择上面的几种算法进行测试。新建文件hmac.js。


~ vi hmac.js

///////////////////////////
// Hmac算法
///////////////////////////

var crypto = require('crypto')
    ,fs = require('fs');

function hmacAlgorithm(algorithm,key){
    var s1 = new Date();

    var filename = "package.json";
    var txt = fs.ReadStream(filename);

    var shasum = crypto.createHmac(algorithm,key);
    txt.on('data', function(d) {
        shasum.update(d);
    });

    txt.on('end', function() {
        var d = shasum.digest('hex');
        var s2 = new Date();

        console.log(algorithm+','+(s2-s1) +'ms,'+ d);
    });
}

function doHmac(hashs,key){
    console.log("\nKey : %s", key);
    console.log("============================");
    hashs.forEach(function(name){
        hmacAlgorithm(name,key);
    })
}

//var algs = crypto.getHashes();
var algs = [ 'md5','sha','sha1','sha256','sha512','RSA-SHA','RSA-SHA1','RSA-SHA256','RSA-SHA512'];

// 短KEY的测试
setTimeout(function(){
    doHmac(algs,"abc");
},1)

// 长KEY的测试
setTimeout(function(){
    var key = "jifdkd;adkfaj^&fjdifefdafda,ijjifdkd;adkfaj^&fjdifefdafdaljifdkd;adkfaj^&fjdifefdafda";
    doHmac(algs,key);
},2*1000)

运行程序


~ node hmac.js
Key : abc
============================
md5,6ms,bf106a077abcfa0fffe6ec0da039545b
sha,6ms,a43a00981346ac64bb7b6fb0641b72a101fb04a5
sha1,6ms,aead69a72da77d0615a854dda1086d885807574a
sha256,7ms,98ac955cb2205ba01a6337951d0ed3fd9b68753544cf81275eced365da57fc5d
sha512,8ms,054f37e34b55a19e64a7f88fb60b1122dc0a30e9864ca28d01d61115b13c74de292ab66e85bf007e1a463a52d7c30fdff174618ef954401bc9c2c3318e762c8f
RSA-SHA,10ms,a43a00981346ac64bb7b6fb0641b72a101fb04a5
RSA-SHA1,11ms,aead69a72da77d0615a854dda1086d885807574a
RSA-SHA256,12ms,98ac955cb2205ba01a6337951d0ed3fd9b68753544cf81275eced365da57fc5d
RSA-SHA512,13ms,054f37e34b55a19e64a7f88fb60b1122dc0a30e9864ca28d01d61115b13c74de292ab66e85bf007e1a463a52d7c30fdff174618ef954401bc9c2c3318e762c8f

Key : jifdkd;adkfaj^&fjdifefdafda,ijjifdkd;adkfaj^&fjdifefdafdaljifdkd;adkfaj^&fjdifefdafda
============================
md5,5ms,164a8fee6e37bb3e40a9d5dff5c2fd66
sha,5ms,ba06f536856553c3756aa36254a63ef35e225d38
sha1,7ms,f3a89b0a5ee8a55c2bb6a861748d43e9d44dc489
sha256,7ms,f2df911f40e74b2b9bb3d53a7ca4b78d438d511e015d4b50431eaea65339380d
sha512,8ms,5b4b57386b1fcc4f1945c47788bf38c013e1cde356fc15e1f946e6bf6738b5dc52ecf17b3ddc80b2ff21f985a1a707df9357fe305e9aa143da073d2cafd794dc
RSA-SHA,11ms,ba06f536856553c3756aa36254a63ef35e225d38
RSA-SHA1,12ms,f3a89b0a5ee8a55c2bb6a861748d43e9d44dc489
RSA-SHA256,14ms,f2df911f40e74b2b9bb3d53a7ca4b78d438d511e015d4b50431eaea65339380d
RSA-SHA512,16ms,5b4b57386b1fcc4f1945c47788bf38c013e1cde356fc15e1f946e6bf6738b5dc52ecf17b3ddc80b2ff21f985a1a707df9357fe305e9aa143da073d2cafd794dc

通过比对短key和长key,在编码比较长的算法上面会有一些影响。由于Hmac有了第二参数key,所以会比单独的hash加密登陆密码,有更好的安全性上的保证。

对于网站登陆密码的设计,我们可以做成2个字段存储,用password字段存密文,passkey字段存储key,把算法直接封装到程序里面。


{
  username: 'xxxx'
  password: 'aead69a72da77d0615a854dda1086d885807574a',
  passkey:'abc'
}

就算数据库被攻击,黑客也只是拿了密文和key,密码明文并没有被泄露。并且在不知道加密算法的情况下,也很难通过彩虹表进行攻击。

4. 加密和解密算法

对于登陆密码来说,是不需要考虑解密的,通常都会用不可逆的算法,像md5,sha-1等。但是,对于有安全性要求的数据来说,我们是需要加密存储,然后解密使用的,这时需要用到可逆的加密算法。对于这种基于KEY算法,可以分为对称加密和不对称加密。

  • 对称加密算法的原理很容易理解,通信一方用KEK加密明文,另一方收到之后用同样的KEY来解密就可以得到明文。
  • 不对称加密算法,使用两把完全不同但又是完全匹配的一对Key:公钥和私钥。在使用不对称加密算法加密文件时,只有使用匹配的一对公钥和私钥,才能完成对明文的加密和解密过程。

对于这种类型的操作,Crypto包也提供了大量的算法支持。新建文件cipher.js,打印出所支持的算法。


~ vi cipher.js

var crypto = require('crypto');
console.log(crypto.getCiphers());

运行程序


~ node cipher.js

[ 'CAST-cbc',
  'aes-128-cbc',
  'aes-128-cbc-hmac-sha1',
  'aes-128-cfb',
  'aes-128-cfb1',
  'aes-128-cfb8',
  'aes-128-ctr',
  'aes-128-ecb',
  'aes-128-gcm',
  'aes-128-ofb',
  'aes-128-xts',
  'aes-192-cbc',
  'aes-192-cfb',
  'aes-192-cfb1',
  'aes-192-cfb8',
  'aes-192-ctr',
  'aes-192-ecb',
  'aes-192-gcm',
  'aes-192-ofb',
  'aes-256-cbc',
  'aes-256-cbc-hmac-sha1',
  'aes-256-cfb',
  'aes-256-cfb1',
  'aes-256-cfb8',
  'aes-256-ctr',
  'aes-256-ecb',
  'aes-256-gcm',
  'aes-256-ofb',
  'aes-256-xts',
  'aes128',
  'aes192',
  'aes256',
  'bf',
  'bf-cbc',
  'bf-cfb',
  'bf-ecb',
  'bf-ofb',
  'blowfish',
  'camellia-128-cbc',
  'camellia-128-cfb',
  'camellia-128-cfb1',
  'camellia-128-cfb8',
  'camellia-128-ecb',
  'camellia-128-ofb',
  'camellia-192-cbc',
  'camellia-192-cfb',
  'camellia-192-cfb1',
  'camellia-192-cfb8',
  'camellia-192-ecb',
  'camellia-192-ofb',
  'camellia-256-cbc',
  'camellia-256-cfb',
  'camellia-256-cfb1',
  'camellia-256-cfb8',
  'camellia-256-ecb',
  'camellia-256-ofb',
  'camellia128',
  'camellia192',
  'camellia256',
  'cast',
  'cast-cbc',
  'cast5-cbc',
  'cast5-cfb',
  'cast5-ecb',
  'cast5-ofb',
  'des',
  'des-cbc',
  'des-cfb',
  'des-cfb1',
  'des-cfb8',
  'des-ecb',
  'des-ede',
  'des-ede-cbc',
  'des-ede-cfb',
  'des-ede-ofb',
  'des-ede3',
  'des-ede3-cbc',
  'des-ede3-cfb',
  'des-ede3-cfb1',
  'des-ede3-cfb8',
  'des-ede3-ofb',
  'des-ofb',
  'des3',
  'desx',
  'desx-cbc',
  'id-aes128-GCM',
  'id-aes192-GCM',
  'id-aes256-GCM',
  'idea',
  'idea-cbc',
  'idea-cfb',
  'idea-ecb',
  'idea-ofb',
  'rc2',
  'rc2-40-cbc',
  'rc2-64-cbc',
  'rc2-cbc',
  'rc2-cfb',
  'rc2-ecb',
  'rc2-ofb',
  'rc4',
  'rc4-40',
  'rc4-hmac-md5',
  'seed',
  'seed-cbc',
  'seed-cfb',
  'seed-ecb',
  'seed-ofb' ]

同样地,在这么一大堆的算法面前,完全不知道如何去选择。我还是以加密和解密的计算时间为参考指标,选出几个常见的算法进行测试。


///////////////////////////
// 加密解密算法
///////////////////////////
var crypto = require('crypto')
    ,fs = require('fs');

//加密
function cipher(algorithm, key, buf ,cb){
    var encrypted = "";
    var cip = crypto.createCipher(algorithm, key);
    encrypted += cip.update(buf, 'binary', 'hex');
    encrypted += cip.final('hex');
    cb(encrypted);
}

//解密
function decipher(algorithm, key, encrypted,cb){
    var decrypted = "";
    var decipher = crypto.createDecipher(algorithm, key);
    decrypted += decipher.update(encrypted, 'hex', 'binary');
    decrypted += decipher.final('binary');
    cb(decrypted);
}

function cipherDecipherFile(filename,algorithm, key){
    fs.readFile(filename, "utf-8",function (err, data) {
        if (err) throw err;
        var s1 = new Date();

        cipher(algorithm, key,data,function(encrypted){
            var s2 = new Date();
            console.log('cipher:'+algorithm+','+(s2-s1) +'ms');

            decipher(algorithm, key,encrypted,function(txt){
                var s3 = new Date();
                console.log('decipher:'+algorithm+','+(s3-s2) +'ms');
//                console.log(txt);
            });
        });
    });
}

//console.log(crypto.getCiphers());
var algs = ['blowfish','aes-256-cbc','cast','des','des3','idea','rc2','rc4','seed'];
var key = "abc";
var filename = "book.pdf";//"package.json";
algs.forEach(function(name){
    cipherDecipherFile(filename,name,key);
})

运行程序


~ node cipher.js

cipher:blowfish,46ms
decipher:blowfish,95ms
cipher:des,67ms
decipher:des,104ms
cipher:idea,54ms
decipher:idea,88ms
cipher:rc4,16ms
decipher:rc4,44ms
cipher:des3,158ms
decipher:des3,193ms
cipher:aes-256-cbc,19ms
decipher:aes-256-cbc,47ms
cipher:cast,46ms
decipher:cast,82ms
cipher:seed,64ms
decipher:seed,98ms
cipher:rc2,104ms
decipher:rc2,99ms

输出一共3列,第一列,cipher(加密),decipher(解密);第二列,是算法名称;第三列是计算时间。

在选中的这几个算法中,rc4和aes-256-cbc是表现不错的算法,加密和解密时间都比较短,加密时间:解密时间=1:3;其他的算法,总体时间相对较长,有的加密时间:解密时间=1:1。所以,怎么选算法,另外的一个标准就要看业务需求了。如果业务上,解密操作的次数远大于加密操作的次数,而且是在服务器计算,那么我们最好找到,加密时间:解密时间=N:1,N>1的算法;如果加密在服务器端,解密在客户端完成,那么aes-256-cbc算法的计算时间比例就非常适合了。

5. 签名和验证算法

我们除了对数据进行加密和解密,还需要判断数据在传输过程中,是否真实际和完整,是否被篡改了。那么就需要用到签名和验证的算法,利用不对称加密算法,通过私钥进行数字签名,公钥验证数据的真实性。

数字签名的制作和验证过程,如下图所示。

screenshot.62

下面我们用程序,来现实图中的操作流程,由于证书是我们自己制作的,不打算对外公开,没有到CA进行认证,所以下过程将不包括公钥伪造,再到CA认证的过程。

首先,我们要用openSSL命令先生成,私钥server.pem和公钥cert.pem。


# 生成私钥
~ D:\workspace\javascript\nodejs-crypto>openssl genrsa  -out server.pem 1024
Generating RSA private key, 1024 bit long modulus
..................++++++
..................++++++
e is 65537 (0x10001)

# 生成公钥
~ D:\workspace\javascript\nodejs-crypto>openssl req -key server.pem -new -x509 -out cert.pem
You are about to be asked to enter information that will be incorporated
into your certificate request.
What you are about to enter is what is called a Distinguished Name or a DN.
There are quite a few fields but you can leave some blank
For some fields there will be a default value,
If you enter '.', the field will be left blank.
-----
Country Name (2 letter code) [AU]:
State or Province Name (full name) [Some-State]:
Locality Name (eg, city) []:
Organization Name (eg, company) [Internet Widgits Pty Ltd]:
Organizational Unit Name (eg, section) []:
Common Name (eg, YOUR name) []:
Email Address []:

接下来,我们利用生成私钥生成数学签名,然后再用公钥验证数据,是否被篡改。新建文件signer.js。


~ vi signer.js

///////////////////////////
// 签名验证算法
// openssl genrsa  -out server.pem 1024
// openssl req -key server.pem -new -x509 -out cert.pem
///////////////////////////

var crypto = require('crypto')
    ,fs = require('fs');

function signer(algorithm,key,data){
    var sign = crypto.createSign(algorithm);
    sign.update(data);
    sig = sign.sign(key, 'hex');
    return sig;
}

function verify(algorithm,pub,sig,data){
    var verify = crypto.createVerify(algorithm);
    verify.update(data);
    return verify.verify(pubkey, sig, 'hex')
}

var algorithm = 'RSA-SHA256';
var data = "abcdef";   //传输的数据
var privatePem = fs.readFileSync('server.pem');
var key = privatePem.toString();
var sig = signer(algorithm,key,data); //数字签名

var publicPem = fs.readFileSync('cert.pem');
var pubkey = publicPem.toString();
console.log(verify(algorithm,pubkey,sig,data));         //验证数据,通过公钥、数字签名 =》是原始数据
console.log(verify(algorithm,pubkey,sig,data+"2"));    //验证数据,通过公钥、数字签名  =》不是原始数据

运行程序


~ node signer.js
true
false

两行输出结果,第一行验证的结果是true,表示数据在传输过程中,没有被篡改;第二行验证的结果是false,表示数据在传输过程中被篡改,不是原始的数据。当然,如何保证私钥和公钥的匹配,需要CA第三方来认证,与Crypto库无关本文不再介绍。

6. salt算法

我们知道,如果直接对密码进行散列,那么黑客可以对通过获得这个密码散列值,然后通过查散列值字典(例如MD5密码破解网站),得到某用户的密码。

盐(Salt),在密码学中,是指通过在密码任意固定位置插入特定的字符串,让散列后的结果和使用原始密码的散列结果不相符,这种过程称之为“加盐”。加盐后的散列值,可以极大的降低由于用户数据被盗而带来的密码泄漏风险,即使通过彩虹表寻找到了散列后的数值所对应的原始内容,但是由于经过了加盐,插入的字符串扰乱了真正的密码,使得获得真实密码的概率大大降低。

加盐的实现过程通常是在需要散列的字段的特定位置增加特定的字符,打乱原始的字串,使其生成的散列结果产生变化。比如,用户使用了一个密码:


123465

经过MD5散列后,可以得出结果:


3d9188577cc9bfe9291ac66b5cc872b7

但是由于用户密码位数不足,短密码的散列结果很容易被彩虹表破解,因此,在用户的密码末尾添加特定字串:


123465abcdefghijklmnopqrstuvwxyz

因此,加盐后的密码位数更长了,散列的结果也发生了变化:


27e20c64ccb8cce9ad68b8ccff6252cf

新建文件salt.js,实现上面的程序。


~ vi salt.js

//////////////////////////////
// salt算法
//////////////////////////////
var crypto = require('crypto');
var md5 = crypto.createHash('md5');
var txt = "123465";

md5.update(txt);
console.log(md5.digest('hex'));

md5 = crypto.createHash('md5');
var salt = "abcdefghijklmnopqrstuvwxyz";
md5.update(txt+salt);
console.log(md5.digest('hex'));

我们可以不用自己动手加盐,而使用crypto.pbkdf2()函数,默认会调用hmac算法,用sha1的散列函数,并且可以设置迭代次数和密文长度。具体的使用代码如下。


~ vi salt.js

var crypto = require('crypto');
var txt = "123465";
var salt = "abcdefghijklmnopqrstuvwxyz";

// 生成密文,默认HMAC函数是sha1算法
crypto.pbkdf2(txt, salt, 4096, 256, function (err,hash) {
    if (err) { throw err; }
    console.log(hash.toString('hex'));
})

运行程序,生成256位的密文。


~ node salt.js

29c7de002d942ebcbf3afe05f3eb0ff620f0515fe6b8f19176736273cd70805afdfcc828a6f227152dfa4d0c4f96da184fbd060d4d4c86a5deb8d704699c3e8653acdb0e5bc3e584e0890a44206bb2926f0289fc8e0abe49fd1876461fcc50f06dc7991c4b93cc4e80076529c73b2f2c56f16b5b319368edf017f3d3583a33aa44fd30f89801f0d8877eb8262925f5fdc40a5c57f1b275e5674784dca635c75bc58b6c22264e0f29e363eb25dedf1a242429084e3e17d344b59cab3b9723db03ee4838b632786d1a9eb968f2523404286e5d0a41a1707577650cc3cc2f1ab65714a4cb31f068e4aefa259c6be68174e0a475d5610168305a4935a14bb221a516

如果加盐每次都是固定的值也会不安全,我们还可以利用随机randomBytes()函数,配合pbkdf2()函数,让每次都是不同的salt,生成安全级别更高的密文。


~ vi salt.js

//通过伪随机码生成salt,进行加密
crypto.randomBytes(128, function (err, salt) {
    if (err) { throw err;}
    salt = salt.toString('hex');
    console.log(salt); //生成salt

    crypto.pbkdf2(txt, salt, 4096, 256, function (err,hash) {
        if (err) { throw err; }
        hash = hash.toString('hex');
        console.log(hash);//生成密文
    })
})

运行程序


~ node salt.js

# 随机生成的salt
78e59de99f16697e3eb684dcfa8efa086db0940c7cd47d33f9311e3bfcf9d58bf30915f54b3f72793b5c8568d32f1f15c55cc87affd043d96f1ed1f56c25a8054b3d83a306636f3b9e3bc9e48c3303aff54da006f92e370023165857fce0a1d1ff0b89178ae8c1416747275daba25652ea864d52a80427658ea69dbe500a7261

# 通过salt生成的密文
48943eb51ea702b436f95bf1dacc7f64bc41cf7cfa4cb40d101d5550c28caafc58ca720934352238430634f21fd5a6a4ef63fe5828c2665362e9902adc0305f93d2523fbd28521ad00947a74ff8229f63ad5796f2e12677cbed6af02b9973ee0187a69ad67e86790471d95f18d6d2c43ef904f7d17a5d8264f8236f227363a016ae2c14559c17236d540e06c5fd443af740721897f76bdbd9711c8499d7a34cae2e917f900fc364f72f9afaf301845c6e0b5c37def949b4af62336a39dbd1e829405d6189536092c7769a5d7e427b8e97419988da4e1bad49c69f25ac4e96f74a0ce3eab9e1433277568105b1dcc0cf9e1f9c91a7ed391c5825eefcd71ef5ca1

我们把salt和密文一起进行存储,保证用户密码的安全性。

7. 程序代码

本文的程序代码,可以直接从Github上面下载本文项目中的源代码,按照片文章中的介绍学习Crypto,下载地址:https://github.com/bsspirit/nodejs-crypto

也可以直接用github命令行来下载:


~ git clone git@github.com:bsspirit/nodejs-crypto.git   # 下载github项目
~ cd nodejs-crypto                                      # 进入下载目录

最后要声明一下:本人对密码技术理解不深,本文所涉及的密码技术仅仅是Crypto库在应用上的,文中观点不对或描述不清楚的地方,还请专家来指正!!

参考文章:

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

打赏作者