티스토리 뷰

Side Technologies

Mongo: mongoose-encryption

Korean Eagle 2021. 4. 11. 20:03
728x90

1. 일반적으로 데이터베이스의 저장은 hashing을 많이 사용하지만 암호화를 사용하는 경우도 많다.

  1-1 관공소의 데이터베이스는 XecureDB, D'amo 같은 암호화 모듈을 사용하여 저장한다. 

 

2. node.js에서 mongo DB를 사용할 때는 편의성 때문에 mongoose를 대부분 사용한다.

  2-1 mongoose를 사용할 경우 mongoose-encryption를 사용하면 간단하게 암호화 처리를 할 수 있다.

 

3. mongoose-encryption은 하나의 키를 가지고 암호화, 복호화를 진행하는 대칭형 AES-256-CBC를 사용한다.

  3-1 즉 암호키를 알면 모든 것을 다 복호화하여 다시 plain text를 만들 수 있다.

    3-1-1 암호화 는 수학적으로 다양한 연산을 사용하는데 알 필요가 없다. 별짓을 다해도 결국 key가 있으면 끝난다.

  3-2 인증 방식은 HMAC-SHA-512를 사용한다.

    3-2-1 HMAC는 hashing를 기본으로 한 메시지 인증방식으로 메시지의 integrity와 authenticity를 검증한다.

    3-2-2 HMAC은 MD5, SHA 같은 방식의 해싱과 공유비밀키를 가지고 수행한다.

    3-2-3 HMAC-SHA-512는 메시지 인증 시 SHA 512방식의 hashing을 사용한다는 의미이다.   

  3-3 사용방식 - 디지털 서명과 거의 유사하다.

    3-3-1 사용자 A, B가 공유된 비밀키를 가지고 있고 A가 메시지 작성 후, 공유비밀키와 메시지를 함께 해싱한다.

    3-3-2 해싱 알고리즘에 따라 HMAC-SHA-512, HMAC-SHA-256, HMAC-MD-5 같이 불려 진다.

    3-3-3 만들어진 hash와 메시지를 함께 A는 B에게 전송한다. 

    3-3-4 B는 수신한 메시지를 공유 비밀키로 A가 한 것과 같이 동일한 방식으로 hash를 생성한다.

    3-3-5 B가 만든 hash가 A가 보내 준 것과 동일하면 정상적인 인증이 완료된다.

  3-4 mongoose-encryption은 암호화 할 데이터를 JSON으로 변경, 암호화한 후 Binary구조로 _ct라는 필드에 저장한다.

    3-4-1 복호화 과정은 대칭키이기 때문에 _ct를 복호화하고 JSON을 객체로 변경한다.

  3-5 인증하는 과정은

    3-5-1 암호키가 서버에 있기 때문에 _id와 _ct 등의 필드와 함께 서명 암호키로 hash를 만들어 _ac 필드에 저정한다.

    3-5-2 검증 시 사용자에서 받은 정보로 DB에서 유저정보를 찾아 동일하게 hash를 만들어 일치하는지를 확인한다.

 

4. 프로그램의 사용

  4-1 암호화 관련된 기본적인 내용이 이해하기 까다로운 것이지 프로그램은 아주 간단하다.

    4-1-1 mongoose-encryption을 설치하고 require로 가져온다.

    4-1-2 암호키와 인증키를 같은 것을 사용해도 되지만 다른 것을 사용하는 것이 좀 더 안전하다.

      4-1-2-1 아래 처럼 openssl을 사용하면 좀 더 편리하게 키를 만들 수 있다.

    4-1-3 암호키는 64 비트기반의 32바이트를 요구하고, 인증키는 64비트 기반 64바이트의 길이의 키를 요구한다.

 

 

    4-1-4 dotenv를 사용하여 외부에 환경변수를 사용하는 것이 이런 키를 저장하는데 안전하다.

      4-1-4-1 .env 파일에 다음과 같이 속성들을 정의해 둔다.

 

ENCKEY=Sak+W4OLlL5hv/rCzN3usphWT731vACAWV8AuSfDXwg=
SIGKEY=Ec8+VkvilBm6dMPSz3aUIpVEFeX5w0msB+MccreqY2rcAvYgr+rli7vwUWaPqlrEJbcCAKQFww8A06wXV1euIg==

 

    4-1-5 encKey, sigKey를 받아와서 저장해 두고 mongoose에 암호화 기능을 추가하기 위해서 plugin에 등록한다.

      4-1-5-1 등록할 때 각 키도 같이 option으로 등록해야 하고 암호화 할 필드를 지정할 수 있다.

      4-1-5-2 아래는 password 부분만 암호화 하도록 하였다.

 

    4-1-6 mongoose의 save를 호출할 경우 암호화되고 서명된 후에 저장되고 find가 호출될 때 인증 및 복호화 된다.

 

const express = require('express')
const mongoose = require('mongoose')
const encrypt = require('mongoose-encryption')
require('dotenv').config()

const app = express()

const userSchema = new mongoose.Schema({
  email: String,
  password: String
})

// openssl rand -base64 32; openssl rand -base64 64;
const encKey = process.env.ENCKEY
const sigKey = process.env.SIGKEY

userSchema.plugin(encrypt, {
  encryptionKey: encKey,
  signingKey: sigKey,
  encryptedFields: ['password']
})

// we have to connect all the plugins to the schema before making model
const User = mongoose.model("User", userSchema)

app.use(express.urlencoded({ extended: true }))
app.use(express.static("public"))
app.set("view engine", "ejs")

mongoose.connect("mongodb://localhost:27017/userDB", {
  useNewUrlParser: true,
  useUnifiedTopology: true
}, function(err) {
  if (!err) {
    console.log('Connected to mongodb at 27017 port')
  } else {
    console.log('Could not connect to mongodb on localhost');
  }
})

app.get("/", function (req, res) {
  res.render("home")
})

app.get("/register", function (req, res) {
  res.render("register")
})

app.get("/login", function (req, res) {
  res.render("login")
})

app.post("/register", function (req, res) {
  const user = new User({
    email: req.body.username,
    password: req.body.password
  })

  user.save(function(err, user) {
    if (err) {
      res.redirect("/register")
    } else {
      console.log(user)
      res.render("secrets")
    }
  })

})

app.get("/secrets", function(req, res) {
  res.redirect("/")
})

app.post("/login", function(req, res) {
  User.findOne({ email: req.body.username }, function(err, user) {
    if (err) {
      res.redirect("/login")
    } else {
      if (user) {
        if (user.password === req.body.password) {
          res.render("secrets")
        } else {
          res.redirect("/login")
        }
      } else {
        res.redirect("/login")
      }
    }
  })
})

app.get("/logout", function(req, res) {
  res.redirect("/login")
})

app.listen(3000, function () {
  console.log("Server is runnong on port 3000");
})

 

  4-2 위의 코드를 보면 register가 회원등록인데 다른 코드와 다른 점이 없다.

  4-3 post의 /login을 보면 사용자가 입력한 username으로 DB를 검색하여 찾아 user로 데이터를 받을 때 복호화 된다.

    4-3-1 복호화가 되었기 때문에 데이터베이스에서 가져온 user 객체는 password가 plain text로 보여진다.

    4-3-2 사용자가 입력한 password와 복호화 된 password를 비교하여 사용자 검증을 하게 된다.

728x90
댓글