管中窥豹谈谈简单的密文存储
本文首发于:我的个人博客 墨客
本文源码地址: https://github.com/edwardwang0302/encrypt_demo
如需转载请注明出处
这篇文章会讲什么?
写这篇文章的目的主要是想讲讲常见的密文存储方式,从明文存储,到Md5加密,再到“加盐”(salting)和多次md5加密,涉及到的只是加密方面非常小的一部分。
涉及到的环境和工具
如果你是把本文当做一个扩展来看,完全没有必要安装和配置以下的环境,安装这些只是为了方便演示
- Node.js环境 Npm包管理 Express框架 Nodemon来热加载 body-parser插件解析post请求的body
- MongoDB数据库 mongoose操作数据库
- PostMan工具
正式开始
在开始之前我们假设你的电脑已经安装了Node.js环境,Npm包管理以及MongoDB
初始化工程
mkdir encrypt
cd encrypt
npm init
接下来一路回车,就是提示一些npm工程初始化的信息,如下图
Screen Shot 2017-11-08 at 10.13.31.png
安装Express,Nodemon,Mongoose
npm i -g nodemon
npm i -S express mongoose body-parser
稍微等待之后,我们目录下面多了node_modules目录,里面是一些依赖
开始写后台代码
mkdir server //存放server代码目录
cd server
touch server.js //server代码
touch model.js //存放数据库相关
touch user.js //存放和user相关的后台代码
打开model.js文件,复制下面代码:
const mongoose = require('mongoose')
// 连接mongo 并使用encrypt这个集合
const DB_URL = 'mongodb://localhost:27017/encrypt'
mongoose.connect(DB_URL)
// 定义存储用户数据的模型
const models = {
user:{
'user':{type:String, require:true},
'pwd':{type:String, require:true},
},
chat:{
}
}
// 根据模型创建集合
for(let m in models) {
mongoose.model(m, new mongoose.Schema(models[m]))
}
module.exports = {
getModel:function(name) {
return mongoose.model(name)
}
}
打开user.js文件,复制下面代码
const express = require('express')
const Router = express.Router()
const models = require('./model')
const User = models.getModel('user')
Router.get('/all', function(req, res) {
// 查找所有的用户信息
User.find({}, function(err, doc) {
return res.json(doc)
})
})
Router.post('/regist', function(req, res) {
const { user, pwd } = req.body
User.findOne({user}, function(err, doc) {
if(doc) {
return res.json({code:1, msg: '用户名存在'})
}
User.create({user, pwd}, function(e, d) {
if(e) {
return res.json({code:1, msg: '注册失败'})
}
return res.json({code:1, msg: '注册成功'})
})
})
})
module.exports = Router
打开server.js文件,复制下面代码:
const express = require('express')
const bodyParser = require('body-parser')
const userRouter = require('./user')
const app = express()
// 用于解析post请求的body
app.use(bodyParser.json())
// 把所有user操作都路由到/user这个url下面
app.use('/user', userRouter)
app.listen(9000, function() {
console.log('Hi, guys! I am listening prot 9000')
})
前后端联调
这里其实不是真正意义上的前后端联调,这为了简单使用PostMan工具发送一个请求,大家可以随意用自己喜欢的工具
- 启动MongoDB
如果你已经安装好了MongoDB,直接使用mongod命令就可以启动
mongod
- 启动后台服务器
nodemon server/server.js
看到下图说明启动成功
Screen Shot 2017-11-08 at 11.00.04.png
-
使用PostMan发送注册请求
Screen Shot 2017-11-08 at 13.08.58.png -
查看已注册的用户
可以看到这里我们的用户信息已经保存了,不过是明文保存的,这点当然不能接受,下面我们将怎么变为密文
Screen Shot 2017-11-08 at 11.01.35.png
从明文到md5加密
加密中最简单的方式是使用md5这种单向的不可逆的方式,我们这里借助utility这个工具来加密
- 停掉刚刚的服务,安装utility
npm i -S utility
- 修改user.js
...省略一些内容
// 引用utility
const utils = require('utility')
...省略一些内容
Router.post('/regist', function(req, res) {
const { user, pwd } = req.body
User.findOne({user}, function(err, doc) {
if(doc) {
return res.json({code:1, msg: '用户名存在'})
}
// 这里使用md5加密
User.create({user, pwd: utils.md5(pwd)}, function(e, d) {
if(e) {
return res.json({code:1, msg: '注册失败'})
}
return res.json({code:0, msg: '注册成功'})
})
})
})
module.exports = Router
- 启动服务,重新注册一个用户u2 密码123
再次查看请求 http://localhost:9000/user/all
可以看到这已经使用md5加密过了,这种方式依然存在着一些问题,我们下面展开讨论
从md5加密到“加盐”
到目前为止我们已经使用了最简单的加密方式md5,可是有些网站使用“彩虹表”的方式可以逆向这种加密,举个例子:
打开 http://www.cmd5.com 直接输入我们的密文: 202cb962ac59075b964b07152d234b70 点击查询
天哪理论上不可逆的md5居然被破译了,怎么降低这种概率呢(注意这里说的是降低),那么就要使用我们之前提到的“加盐”(salting)的方式。加盐其实就是我们自己加密的过程中加入一串复杂的字符串。
我们修改user.js文件,在module.exports前面加入下面函数定义
// 加密加盐
function salting(pwd) {
const salt = 'i_wanna_be_bestx8yza6!@#IUHJH~~'
return utils.md5(pwd+salt)
}
修改加密部分
User.create({user, pwd: utils.md5(pwd)}, function(e, d)
// 将上面的这句换成
User.create({user, pwd: salting(pwd)}, function(e, d)
重启服务,再次注册一个u3 密码为123,注册成功后我们可以看到密码,再去刚刚的网站发现已经不能解析了
多次md5增加复杂度
修改上面提到的salting函数,多加一次md5加密
function salting(pwd) {
const salt = 'i_wanna_be_bestx8yza6!@#IUHJH~~'
return utils.md5(utils.md5(pwd+salt))
}
总结
至此,我们大致完成了一般简单的系统加密过程的演进,对于一些有更高要求的加密系统来说,上述的方式依然是不安全的,只是简单的增加了破译的时间成本和空间成本;加密方式目前有很多,当然也已经不属于我们“管中窥豹”的简单定义了,大家有兴趣可以再去深入研究,就先写到这里吧。