🌟前言
哈喽小伙伴们,新的专栏 Node 已开启;这个专栏里边会收录一些Node的基础知识和项目实战;今天我们带领大家初识一下 Node内置模块 crypto加密模块 ;让我们一起来看看吧🤘
🌟crypto加密模块
密码技术是互联网应用的一项最基本的技术之一,主要保证了数据的安全。安全定义是多维度的,通过不可逆的hash算法可以保证登陆密码的安全;通过非对称的加密算法,可以保证数据存储的安全性;通过数字签名,可以验证数据在传输过程中是否被篡改。
我们要做互联网应用,数据安全性一个是不容忽视的问题。不然可能会造成,如CSDN的100万用户明文密码被泄露事情;携程网,100万用户个人信息泄露事情等。
我们在日常的业务中经常会遇到这样的场景:
对比两个文件的内容是否相同;
生成 token;
密码保护;
加密和解密数据;
等等,有各种各样的需要加密的场景。在 node 中也有原生的 crypto 模块,该模块提供了各种加密算法,如hash、hmac、加密解密、签名、验证功能等一整套的封装,可以非常方便地让我们使用加密技术,解决应用开发中的问题。
🌟Crypto模块介绍
Crypto模块是随Nodejs内核一起打包发布的,主要提供了加密、解密、签名、验证等功能。它提供了一种安全凭证的封装方式,可以用于HTTPS安全网络以及普通HTTP连接。
Crypto模块需要底层系统提供OpenSSL的支持,它提供OpenSSL中的一系列哈希方法,包括hash(哈希),hmac(密钥哈希),cipher(编码),decipher(解码),sign(签名)以及verify(验证)等方法的封装。Nodejs 使用 C++ 实现这些算法后,通过该模块提供给我们调用,相比于 Javascript 算法,执行效率大为提升。
引入crypto模块:
const crypto = require('crypto');
🌟Hash算法
Hash算法也称为哈希算法,是指将任意长度的二进制值串映射为固定长度的二进制值串。原始数据经过映射之后得到的二进制值串就是哈希值。
🌟Hash算法介绍
哈希算法特性
哈希值是一段数据唯一且极其紧凑的数值表示形式
从哈希值不能反向推导出原始数据(哈希算法更多算是一种单向加密算法,不可逆)
对输入数据敏感,输入数据只要改变 1 bit,那么最终得到的哈希值也不相同
即对于不同的原始数据,哈希值在计算上是不可能的相同的
哈希算法的执行效率要高,针对较长的文本,也能快速计算出哈希值
哈希算法的应用场景非常多,列举较为重要的几个:
校验数据完整性
利用哈希算法特性,对输入数据敏感,不同数据生成的哈希值是不一样。因此,当哈希值是一样时,表示原数据是一样的。因此,可以用于校验数据的完整性和正确性。
唯一标识
哈希算法针对不同的二进制内容生成的二进串是不一样,一般来讲都是一对一的情况。哈希算法可以对数据量比较大的内容做信息摘要得到一个极其紧凑的哈希值。因此,我们可以通过一个很短的二进制编码来表示这个数据的唯一性。
安全加密
哈希算法可以将二进制串转换为一串毫无规律的二进制值串,同时是很难通过哈希值反推出原二进制值串的内容。因此,可以用于单向加密。
比如在网站中,为了避免明文带来的不安全,我们可以使用哈希算法对用户密码进行单向加密。当用户登录时输入密码之后,使用哈希算法对这个密码进行哈希计算。之后,再跟数据库中存储的哈希值进行比较,如果一样,则可认为用户输入的密码是正确。
典型的哈希算法包括 ‘md5′,’sha’,’sha1′,’sha256′,’sha512′,’RSA-SHA’。下面我们查看一下Nodejs中所支持的哈希算法:
const crypto = require('crypto'); # 加载crypto库 console.log(crypto.getHashes()); # 打印支持的hash算法
🌟Hash算法之MD5
🌟算法简介
MD5的全称是Message-Digest Algorithm 5(信息-摘要算法),在90年代初由Mit Laboratory for Computer Science和Rsa data security inc的Ronald l. rivest开发出来,经md2、md3和md4发展而来。它的作用是让大容量信息在用数字签名软件签署私人密匙前被“压缩”成一种保密的格式(就是把一个任意长度的字节串变换成一定长的大整数).不管是md2、md4还是md5,它们都需要获得一个随机长度的信息并产生一个128位的信息摘要.
MD5 算法的哈希值大小为 128 位。是一种不可逆的算法。
MD5是一种常用的哈希算法,用于给任意数据一个“签名”。这个签名通常用一个十六进制的字符串表示。
🌟MD5加密使用
将一个普通字符串经过MD5算法加密的示例:
var crypto = require('crypto'); var content = 'password' var md5 = crypto.createHash('md5'); md5.update(content); var d = md5.digest('hex'); //MD5值是5f4dcc3b5aa765d61d8327deb882cf99
🌟Hash算法之SHA1
🌟算法简介
SHA1的全称是Secure Hash Algorithm(安全哈希算法)。加密哈希函数将任意长度的二进制字符串映射为固定长度的小型二进制字符串。加密哈希函数有这样一个属性:在计算上不大可能找到散列为相同的值的两个不同的输入;也就是说,两组数据的哈希值仅在对应的数据也匹配时才会匹配。数据的少量更改会在哈希值中产生不可预知的大量更改。所以你很难从加密后的文字中找到蛛丝马迹。
SHA1 算法的哈希值大小为 160 位。是一种不可逆的算法。
🌟SHA1加密使用
将一个普通字符串经过SHA1算法加密的示例:
var crypto = require('crypto'); var content = 'password' var shasum = crypto.createHash('sha1'); shasum.update(content); var d = shasum.digest('hex');
🌟Hash算法评估
简单地比较一下几种常见算法,对这些算法进行一个简单评估:
最常见的md5,密文长度最短,计算时间也最少;sha和sha1比较接近;sha512密文最长,计算时间也最长。 由于md5已经有了大量的字典库,对于安全级别一般的网站用sha1吧;如果安全级别要求很高,CPU性能好,可以考虑用sha512。
🌟Hmac算法
🌟算法简介
随着互联网的发展,MD5已经变得越来越不安全了,黑客可以通过彩虹表,查出MD5值所对应的密码,为了解决这个问题,很多网站都开始采用需要密钥加密的Hmac算法。
彩虹表(Rainbow Table)是一种主流的密码破解技术,它事先把所有可能的密码计算出哈希并保存在索引文件中,在需要破解时只需根据哈希对索引文件进行查询即可很快获得明文密码,在避免大量的重复计算的同时,也大大提高了密码的破解速度。
HMAC是密钥相关的哈希运算消息认证码(Hash-based Message Authentication Code),HMAC运算利用哈希算法,以一个密钥和一个消息为输入,生成一个消息摘要作为输出。HMAC可以有效防止一些类似md5的彩虹表等攻击,比如一些常见的密码直接MD5存入数据库的,可能被反向破解。
Hmac算法也是一种哈希算法,它可以利用MD5或SHA1等哈希算法。不同的是,Hmac还需要一个密钥,只要密钥发生了变化,那么同样的输入数据也会得到不同的签名,因此,可以把Hmac理解为用随机数“增强”的哈希算法。
🌟Hmac加密使用
将一个普通字符串经过Hmac算法加密的示例:
var crypto = require('crypto'); var content = 'password' // 待加密字符串 var SecrectKey = 'houningzhou'; // 秘钥 Signture = crypto.createHmac('sha1', SecrectKey); //SecrectKey 秘钥 Signture.update(content); var d = Signture.digest('hex') // 结果
🌟Hmac算法选择不同Hash算法的评估
简单地比较一下Hmac算法选择不同Hash算法,对这些算法进行一个简单评估:
🌟加密和解密算法
对于登陆密码来说,是不需要考虑解密的,通常都会用不可逆的算法,像md5,sha-1等。但是,对于有安全性要求的数据来说,我们是需要加密存储,然后解密使用的,这时需要用到可逆的加密算法。对于这种基于KEY算法,可以分为对称加密和不对称加密。
对称加密算法 的原理很容易理解,通信一方用KEK加密明文,另一方收到之后用同样的KEY来解密就可以得到明文。
不对称加密算法 使用两把完全不同但又是完全匹配的一对Key:公钥和私钥。在使用不对称加密算法加密文件时,只有使用匹配的一对公钥和私钥,才能完成对明文的加密和解密过程。
🌟AES
🌟算法简介
高级加密标准(英语:Advanced Encryption Standard,缩写:AES)在密码学中又称Rijndael加密法,是美国联邦政府采用的一种区块加密标准。这个标准用来替代原先的DES,已经被多方分析且广为全世界所使用。经过五年的甄选进程,高级加密标准由美国国家标准与技术研究院(NIST)于2001年11月26日发布于FIPS PUB 197,并在2002年5月26日成为有效的标准。2006年,高级加密标准已然成为对称密钥加密中最流行的算法之一。
🌟AES加密
AES是一种常用的对称加密算法,加解密都用同一个密钥。crypto模块提供了AES支持,提供了createCipheriv和createDecipheriv来进行加密和解密的功能。之前的 createCipher 和 createDecipher 在 10.0.0 版本已经废弃了。
我们这里以新的方法为例,但是需要自己封装好函数,便于使用:
const crypto = require('crypto'); /** * AES对称加密函数 aesEncrypt * @param data 待加密数据 * @param key 秘钥 * @returns 加密数据 */ function aesEncrypt(data, key) { const cipher = crypto.createCipheriv('aes192', key); var crypted = cipher.update(data, 'utf8', 'hex'); crypted += cipher.final('hex'); return crypted; } /** * AES对称解密函数 aesDecrypt * @param encrypted 解密字符 * @param key 秘钥 * @returns 解密数据 */ function aesDecrypt(encrypted, key) { const decipher = crypto.createDecipheriv('aes192', key); var decrypted = decipher.update(encrypted, 'hex', 'utf8'); decrypted += decipher.final('utf8'); return decrypted; } var data = 'Hello, this is a secret message!'; // 加密数据 var key = 'Password!'; // 秘钥 var encrypted = aesEncrypt(data, key); // 调用加密函数得到加密结果 var decrypted = aesDecrypt(encrypted, key); // 调用解密函数得到解密结果 console.log('原始文本: ' + data); console.log('加密文本: ' + encrypted); console.log('解密文本: ' + decrypted);
运行结果如下:
原始文本: Hello, this is a secret message! 加密文本: 8a944d97bdabc157a5b7a40cb180e7... 解密文本: Hello, this is a secret message!
可以看出,加密后的字符串通过解密又得到了原始内容。
注意到AES有很多不同的算法,如aes192,aes-128-ecb,aes-256-cbc等,AES除了密钥外还可以指定IV(Initial Vector),不同的系统只要IV不同,用相同的密钥加密相同的数据得到的加密结果也是不同的。加密结果通常有两种表示方法:hex和base64,这些功能Nodejs全部都支持,但是在应用中要注意,如果加解密双方一方用Nodejs,另一方用Java、PHP等其它语言,需要仔细测试。如果无法正确解密,要确认双方是否遵循同样的AES算法,字符串密钥和IV是否相同,加密后的数据是否统一为hex或base64格式。
🌟签名和验证算法
我们除了对数据进行加密和解密,还需要判断数据在传输过程中,是否真实际和完整,是否被篡改了。那么就需要用到签名和验证的算法,利用不对称加密算法,通过私钥进行数字签名,公钥验证数据的真实性。
数字签名的制作和验证过程,如下图所示。
下面我们用程序,来现实图中的操作流程,由于证书是我们自己制作的,不打算对外公开,没有到CA进行认证,所以下过程将不包括公钥伪造,再到CA认证的过程。
首先,我们要用openSSL命令先生成,私钥server.pem和公钥cert.pem。
# 生成私钥 ~ D:\workspace\nodejs-crypto>openssl genrsa -out server.pem 1024 Generating RSA private key, 1024 bit long modulus ..................++++++ ..................++++++ e is 65537 (0x10001) # 生成公钥 ~ D:\workspace\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 /// const 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库无关。
🌟写在最后
更多Node知识以及API请大家持续关注,尽请期待。各位小伙伴让我们 let’s be prepared at all times!