ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 암호화 - MD5, SHA256, PBKFD2
    JavaScript/Node.js 2018. 12. 6. 02:29

    암호화 - MD5, SHA256, PBKFD2

    사용자로부터 전달받은 사용자의 정보 중 패스워드 정보를 그대로 데이터 베이스나 다른 저장 공간에 저장하는 경우 그것이 유출된다면 매우 심각한 문제가 발생하게 된다.

    이는 소송이 발생할 수 있는 문제로 이어지게 된다.

    그러므로 사용자 정보를 저장하게 될 때에는 항상 보안을 염두해야한다.

    이번 시간에는 사용자 정보 중 패스워드를 해커들이 알 수 없도록 암호화하는 방법을 알아볼 것이다.




     MD5

    MD5는 1991년에 보안을 위해 만들어진 기능이다.

    그러나 현재에 와서는 다양한 결함들이 발생되었고 이 결함들을 통해 SSL 인증서를 변조할 수 있게 되었다는 것이 발표된 지금은 보안을 위해서는 사용되지 않는 기술이다.

    MD5가 어떻게 데이터를 암호화하는지, 어떠한 기술인지를 알아보도록 하자.




     MD5 패키지 설치

    npm 사이트에서 md5를 검색해보면 다음과 같은 패키지를 찾을 수 있다.

    MD5로 메시지를 해싱하기 위한 자바스크립트 함수라고 설명하고 있다.

    설치 구문을 통해 설치하도록 하자.




     MD5 API

    npm md5 페이지를 보면 API 정보를 제공하고 있다.

    사용방법이 간단하다.

    "md5(message)"로 사용할 수 있으며 인자로 받는 메시지 타입은 String이거나 Buffer여야 하며 리턴되는 타입은 String이다.

    원하는 데이터를 "md5()"함수의 인자로 넣으면 Hash데이터를 리턴하는 방식이다.




     MD5 사용하기

    사용 방법을 알아봤으니 직접 사용해보자.

    애플리케이션에 md5 패키지를 require()한다.


    이제 /md5 URL로 접속했을 때 표현할 라우트를 작성하자.

    md5 URL로 접속하면 사용자 정보에 대해서 MD5로 암호화하지 않는 패스워드 정보와 MD5로 암호화한 패스워드 정보를 출력해서 비교해볼 것이다.

    user_info라는 사용자 정보를 담는 배열을 생성하고 그 안에 사용자 아이디와 패스워드를 담는 객체를 저장한다.

    그리고 output을 만들고 비밀번호만을 암호화하지 않는 퓨어한 형태의 정보와 md5로 암호화한 정보를 같이 표현한다.


    작업을 완료했으면 애플리케이션을 실행시키고 "localhost:port/md5" URL로 접속해보자.

    USER_PASSWORD란에서 MD5를 거치지 않은 비밀번호가 그대로 출력된다는 것을 알 수 있고, MD5를 거친 데이터는 알 수 없는 값으로 표현된다는 것을 알 수 있다.

    MD5를 통해 리턴되는 값을 '해쉬값' 혹은 '다이제스트'라고 하며 이러한 작업을 해싱이라고 표현한다.




     SHA256

    SHA는 미국 국가안보국에서 설계했다고 한다.

    SHA-1은 현재 공격이 발견되어 보안에 취약해짐이 알려졌고 SHA-2에 대해서는 아직 공격이 발견되지 않았다고 한다.

    현재는 SHA-3로 불리는 새로운 암호화 해시 알고리즘을 연구하고 있는듯 하다.




     SHA-256 패키지 설치

    npm에서 SHA256을 검색하면 아래의 패키지 정보를 찾을 수 있다.

    가장 중요한 건 해당 패키지는 사용되지 않는 패키지라는 것이다.

    만약 실제 웹 프로그램을 서비스해야하는 경우 SHA-256에 대한 패키지는 많기 때문에 해당 패키지 대신 다른 패키지를 사용하도록 하자.

    지금은 이런 암호화 방식도 있다라는 것을 알기 위해 해당 패키지로 진행하도록 하겠다.

    설치 방법을 알려주고 있으니 구문을 통해 해당 패키지를 설치하도록 하자.




     SHA256 API

    "sha256('message');"는 일반적인 데이터를 해싱하는 방법이고

    "sha256.x2('message');"는 이중 해싱하는 방법이다.

    인자로 받는 데이터는 바이트 배열이거나 문자열이다.

    문자열은 항상 이진 데이터로 해석되며, 파싱할 데이터 문자열이 16진수인 경우 먼저 2진수 문자열 또는 배열로 변환해야한다.

    기본적인 출력은 16진수 인코딩 문자열이다.

    다른 옵션으로는 바이트 배열이나 이진 인코딩 문자열이 있다.


    필요에 맞게 옵션데이터를 사용하거나 이중 해싱을 사용하면 될 것 같다.




     SHA256 사용하기

    애플리케이션에 sha256 패키지를 주입시키자.


    그리고 기존에 작성한 /md5 라우트를 수정하자.

    output에 li태그를 하나 추가하여 sha256()으로 사용자의 패스워드를 해싱한 데이터를 작성한다.


    그리고 웹 애플리케이션으로 들어가보자.

    SHA-256으로 해싱처리한 내용이 나타난다.

    MD5로 해싱한 해쉬 데이터와 SHA-256으로 해싱한 해쉬 데이터가 서로 다르다는 것을 알 수 있다.

    이런 부분들이 서로 다른 암호화 알고리즘을 사용한다고 생각하면 될 것 같다.




     PBKFD2(Password-Based Key Derivation Function)

    PBKFD2는 key derivation function(키 유도 함수)의 종류 중 하나이다.

    기존 MD5나 SHA-1와 같은 암호화 방식은 단방향 해시 함수라고 한다.

    이런 단방향 해시 함수에는 아래와 같은 문제가 존재한다고 한다.

    출저: https://d2.naver.com/helloworld/318732


    아직 내 수준에서 위의 모든 문제를 이해할 수 없으며 경험해본 적이 없지만 보안적으론 단방향 해시 함수보다 더 우수하다는 것을 알겠다.

    어쨌든 출저에 표기한 링크를 들어가보면 이러한 단방향 해시 함수의 문제점들을 해결하기 위해 솔팅(salting)이라는 개념키 스트레칭(key stretching) 개념추가되어 보안할 수 있다고 한다.

    다이제스트를 생성하기 위해서는 사용자가 입력한 패스워드 정보에 추가로 의미없는 데이터인 솔트를 추가하여 생성해야한다.

    그 이유는 해커가 사용자의 패스워드에 대한 정보를 알아낸다 하더라도 해싱된 다이제스트를 풀어낼 수 없게 하기 위함이다.

    그리고 이러한 기능들이 구현되어 있는 기능이 PBKFD2이다.




     PBKFD2-Password 패키지 설치

    PBKFD2기능을 활용해 로그인이나 회원가입할 때 사용자의 패스워드 정보를 암호화 처리할 수 있는 패키지가 존재한다.

    npm 검색란에 PBKFD2-Password를 검색하고 아래의 패키지를 들어가보자.

    Node.js를 위한 salt/password를 쉽게 생성할 수 있는 패키지라고 설명하고 있다.

    패키지 설치 방법은 알려주지 않으니 그냥 아래의 방법으로 설치하도록 하자.




     PBKFD2-Password API

    "pbkdf2-password" 패키지를 require()로 애플리케이션에 주입시키고 hasher라는 객체를 생성한다.

    그리고 hasher()라는 메소드를 사용해 해싱처리를 할 수 있다.


    해시와 pbkd2 암호화 모듈을 사용하여 암호를 해쉬할 수 있다고 설명하고 있으며 그 아래 인자로 들어가는 옵션들을 설명하고 있다.

    옵션으로 해시 처리할 password정보와 솔트에 사용할 base64 스트링 값을 설정할 수 있다.


    패스워드를 정의하지 않는 경우, 새로운 10-바이트 패스워드가 생성되어 base64로 변환된다.

    salt를 정의하지 않는 경우, 새로운 salt를 생성한다.

    그리고 콜백함수는 err, pass, salt, hash 4개의 데이터를 인자로 갖는다.




     PBKFD2-Password 사용하기

    PBKFD2-Password 패키지를 이용해 실제 데이터 베이스에 사용자 정보를 저장하여 관리하기위해 회원가입 기능과, 입력받은 정보와 저장된 사용자 정보를 비교하여 동일한 데이터인 경우 로그인을 하는 프로그램을 만들어보자.


    가장 먼저 사용자 정보를 저장할 테이블을 생성하자.

    o2 데이터 베이스에 userinfo 테이블을 생성하고 아래와 같은 컬럼들을 추가한다.

    index - 데이터의 index를 뜻한다. 자동증가(Auto Incremental)기능을 활성화하여 자동증가 처리되도록 한다.

    id - 사용자의 id 정보를 저장할 컬럼이다.

    pwd - 사용자의 패스워드를 의미하는 다이제스트 값을 저장할 컬럼이다. 사용자의 패스워드를 직접 저장하지 않음으로써 보안처리를 하는 것이다.

    salt - 사용자의 패스워드인 다이제스트값을 판단하기 위한 해시 값을 저장할 컬럼이다.


    우선 이렇게만 알아두고 자세한 설명은 코드를 작성하면서 설명하도록 하겠다.



    우리가 이전 시간에 작성했던 app_session_mysql.js를 열어 아래의 코드를 작성하자.

    사용자 정보를 데이터 베이스에 정보할 것이므로 mysql을 애플리케이션에 주입시킨다.

    그리고 패스워드를 암호화 처리하기 위해 pbkdf2-password 패키지도 주입시킨다.

    그 다음 mysql객체를 생성하고 pbpkdf2-password 객체도 생성한다.


    이로써 mysql과 패스워드를 암호화할 pbkdf2-password를 사용할 준비가 되었다.



    회원가입 폼을 생성하기 위해 아래의 라우트를 작성하자.

    사용자로부터 아이디와 패스워드, 닉네임을 입력받도록 인풋 폼을 제공한다.

    데이터를 작성 후 처리는 "/auth/register"로 POST메소드로 처리한다.


    사용자 정보를 전달받아 패스워드 정보를 암호화하고, 데이터 베이스에 정보를 저장하는 작업을 하자.

    API를 참고해서 hasher()메소드를 작성한다.

    인자로 패스워드 객체정보를 전달하고, 콜백함수를 작성한다.

    콜백 함수에서 사용자 정보를 변수로 저장한다.

    uid는 POST로 전달된 사용자가 입력한 정보를 담고 upwd는 hasher()를 통해 해싱처리된 '다이제스트'를 담는다.

    해시 데이터를 pwd를 대신하여 저장하므로써 해커들이 데이터를 알 수 없도록 처리하기 위함이다.

    그리고 usalt에 salt 데이터를 담는다. salt는 다이제스트를 생성할 때 사용자가 입력한 패스워드 정보에 덧붙이는 데이터이다.

    이 데이터를 저장하는 이유는 로그인을 할 때 알 수 있으므로 추후 설명하도록 하겠다.

    그리고 POST로 전달된 nickname 정보를 담고 userinfo 테이블에 데이터를 INSERT한다.


    INSERT에 성공하면 로그인 페이지로 리다이렉션 시켜준다.


    애플리케이션을 실행시키고 "/auth/register" URL로 접속해서 회원가입을 하도록 하자.

    제출 버튼을 클릭하고 o2 데이터 베이스의 userinfo 테이블을 조회해보자.

    데이터가 추가된 것을 볼 수 있는데, pwd에는 해싱처리된 다이제스트 값이 등록되어 있다는 점salt에 해싱처리된 다이제스트를 복호화할 키 정보가 담겨있다는 것을 알 수 있다.

    pwd에 사용자가 입력한 실제 비밀번호 데이터를 담고 있지 않으므로 외부로부터 데이터 유출에 안전해질 수 있게 됬다.

    salt는 추후 로그인을 시도할 때 패스워드 정보를 검증하기 위해 사용될 것이다.

    salt는 데이터를 추가할 때 반드시 다이제스트와 함께 저장되어야 한다.

    왜냐면 같은 데이터라도 해싱 처리가 될 때 마다 각각 다른 다이제스트가 생성되기 때문이다.

    그것을 방지하기 위해서 사용자 데이터와 salt 정보를 같이 전달하는 경우 복호화가 가능해진다.



    이제 로그인 처리를 수정하자.


    로그인 폼은 수정할 필요 없고 로그인을 처리하는 비즈니스 로직을 수정해야한다.

    우리는 사용자 정보를 데이터 베이스에서 가져올 것이므로 기존에 만들었던 저장된 사용자 정보는 사용하지 않는다.

    가장 먼저 POST로 전달된 사용자로부터 입력받은 id정보를 통해 userinfo 테이블에 조회를 시도한다.

    데이터 조회에 성공했을 경우 사용자가 입력한 id정보와 데이터 베이스에 저장된 id정보를 비교한다.

    검증된 아이디인 경우 패스워드 정보도 동일한지 비교를 해야한다.

    그러나 데이터 베이스에 저장된 패스워드 정보는 해싱된 다이제스트 값이기 때문에 이를 비교하기 위해서는 사용자가 입력한 패스워드를 다시한번 해싱처리하여 동일한 다이제스트 값을 얻어낼 수 있어야 한다.

    hasher()에 인자로 사용자가 입력한 패스워드와 데이터 베이스에 저장한 salt정보를 인자로 전달하는 경우 "패스워드 + salt값"으로 해싱이 처리되기 때문에 동일한 다이제스트 값을 얻어낼 수 있다.


    아래를 보자.

    사용자가 입력한 패스워드 정보가 "123456"이라고 했을 때 !@#라는 salt가 추가되어 digest가 생성되었다.

    그러나 아래를 보자.

    동일한 패스워드 정보에 다른 salt가 추가되는 경우 digest가 다른 값을 가진 상태로 생성되었다.

    이는 사용자가 입력한 데이터는 동일하지만 전혀 다른 데이터를 생성하게 되는 것이다.

    따라서 salt정보를 데이터 베이스에 가지고 있어야하는 것이다.

    salt정보가 없거나 다를 경우 digest는 상이한 값만을 생성하게되며 이는 비교 결과가 true가 될 수 없는 상황만 나타나게 된다.


    이것을 반드시 이해해야한다.



    애플리케이션을 실행하고 "/auth/login" URL로 접속해서 로그인을 시도해보자.

    올바른 패스워드로 로그인을 한다면 정상적으로 nickname이 출력될 것이다.

    그리고 핵심은 바로 아래다.

    코드에서 if(hash === userinfo[0].pwd)를 비교하는 데이터들이다.

    db에 저장된 hash 값과 hasher()에 사용자 패스워드에 salt정보를 인자로 함께 전달하여 새로 생성한 hash값이 동일하다는 것을 알 수가 있다.


    만약 로그인을 할 때 잘못된 패스워드를 입력하여 로그인을 시도할 경우

    새로 생성한 해시 값과 db에 저장한 해시 값이 서로 상이하므로 로그인을 할 수 없게 된다.

    'JavaScript > Node.js' 카테고리의 다른 글

    Authentication - Passport  (0) 2018.12.07
    Process Management - PM2  (0) 2018.12.06
    Express - MySQL Session  (0) 2018.12.04
    Express - Session 1  (0) 2018.12.04
    Cookie 서명  (0) 2018.12.01
Designed by Tistory.