본문 바로가기

블록체인 sw개발자

crypto 모듈 단방향&양방향 암호화

crypto

 

다양한 방식의 암호화를 도와주는 모듈입니다. 몇 가지 메서드는 익혀두면 실제 서비스에도 적용 할 수 있어 정말 유용합니다. 고객의 비밀번호는 반드시 암호화해야 합니다. 비밀번호를 암호화하지 않으면 비밀번호를 저장해둔 데이터베이스가 해킹당하는 순간 고객들의 비밀번호도 고스란히 해커 손에 넘어가고 맙니다.

물론 데이터베이스가 해킹당하지 않도록 노력해야겠지만, 무엇보다 안전 장치를 이중으로 만들어 놓는 것이 좋습니다.

 

단방향 암호화

 

비밀번호는 보통 단방향 암호화 알고리즘을 사용해서 암호화합니다, 단방향 암호화란 복호화할 수 없는 암호화 방식을 뜻합니다. 복호화는 암호화된 문자열을 원래 문자열로 되돌려 놓는 것을 의미합니다. 즉, 단방향 암호화는 한번 암호화하면 원래 문자열을 찾을 수 없습니다. 복호화할 수 없으므로 암호화라고 표현하는 대신 해시 함수라고 부르기도 합니다.

 

복호회할 수 없는 암호화가 왜 필요한지 의문이 들 수도 있습니다. 하지만 생각해보면 고객의 비밀번호는 복호화할 필요가 없습니다. 먼저 고객의 비밀번호를 암호화해서 데이터베이스에 저장합니다. 그리고 로그인할 때마다 입력받은 비밀번호를 같은 암호화 알고리즘으로 암호화한 후, 데이터베이스의 비밀번호와 비교하면 됩니다. 원래 비밀번호는 어디에도 저장되지 않고 암호화된 문자열로만 비교하는 것입니다.

단방향 암호화 알고리즘은 주로 해시 기법을 사용합니다. 해시 기법이란 어떠한 문자열을 고정된 길이의 다른 문자열로 바꿔버리는 방식입니다, 예를 들면 abcdefgh라는 문자열을 qvew로 바꿔버리고 , ijklm이라는 문자열을 zvsf로 바꿔버리는 겁니다. 입력 문자열의 길이는 다르지만, 출력 문자열의 길이는 네자리로 고정되어 있습니다.

 

노드에서 해시 함수는 다음과 같이 사용합니다.

// hash.js

const  crypto = require('crypro');

console.log('base64', crypto.creatHash('sha512').update('비밀번호').digest('base64'));
console.log('hex', crypto.createHash('sha512').update('비밀번호').digest('hex'));
console.log('base64', crypto.createHash('sha512').update('다른 비밀번호').digest('base64'));

 위 콘솔로 출력하면 비밀번호라는 문자열을 해시로 사용해 바뀌게 됩니다..

  • createHash(알고리즘): 사용할 해시 알고리즘을 넣습니다. md5, sha1, sha256m sha512 등이 가능하지만, md5와 sha1 은 이미 취약점이 발견되었습니다. 현재는 sha512 정도로 충분하지만, 나중에 sha512마저도 취야해지면 더 강화된 알고리즘으로 바꿔야 합니다.
  • update(문자열): 변환할 문자열을 넣습니다.
  • digest(인코딩): 인코딩할 알고리즘을 넣습니다, base64, hex, latin1이 주로 사용되는데, 그중 base64 가 결과 문자열이 가장 짧아서 애용됩니다. 결과물로 변환된 문자열을 반환합니다.

해시 함수

가끔 nopqrst라는 문자열이 qvew로 변환되어 abcdefgh를 넣었을 때와 똑같은 출력 문자열로 바뀔 때도 있습니다. 이런 상황을 충돌이 발생했다고 표현합니다. 해킹용 컴퓨터의 역할은 어떠한 문자열이 같은 출력 문자열을 반환하는지 찾아내는 것입니다. 여러 입력 문자열이 같은 출력 문자열로 변환될 수 있으므로 비밀번호를 abcdefgh로 설정했어도 nopqrst로 뚫리는 사태가 발생하게 됩니다.

해킹용 컴퓨터의 성능이 발달함에 따라 기존 해시 알고리즘들이 위협받고 있습니다만, 그에 따라 해시 알고리즘도 더 강력하게 진화하고 있습니다. 언제가 sha512도 취약점이 발견될 것입니다.

그렇게 된다면 더 강력한 알고리즘인 sha3으로 이전하면 됩니다.

현재는 주로 pbkdf2bcrypt, scrypt라는 알고리즘으로 비밀번호를 암호화하고 있습니다. 그 중 노드에서 지원하는 pbkdf2를 알아보겠습니다. pbkdf2는 간단히 말하자면 기존 문자열에 salt라고 불리는 문자열을 붙인후 해시 알고리즘을 반복해서 적용하는 겁니다.

salt란 데이터, 비밀번호, 통과암호를 해시 처리하는 단방향 함수의 추가 입력으로 사용되는 랜덤 데이터이다. 솔트는 스토리지에서 비밀번호를 보호하기 위해 사용된다.

const crypto = require('crypto');

crypto.randomBytes(64, (err,buf) => {
  const salt = buf.toString("base64");
  console.log('salt', salt);
  crypto.pbkdf2('비밀번호', salt, 10000, 64, 'sha512', (err, key) => {
    console.log('password', key.toString('base64'));
  });
});

먼저 randomBytes() 메서드로 64바이트 길이의 문자열을 만듭니다. 이것이 salt가 됩니다. pbkdf2() 메서드에는 순서대로 비밀번호, salt, 반복 횟수, 출력 바이트, 해시 알고리즘을 인수로 넣습니다. 예시에서는 10만 번 반복해서 적용한다고 했습니다. 즉, sha512로 변환된 결과값을 다시 sha512로 변환하는 과정을 10만 번 반복하는 겁니다.

pbkdf2

너무 많이 반복하는 것은 아닌지 걱정될 수도 있지만. 1초 정도밖에 걸리지 않습니다. 이는 컴퓨터의 서능에 좌우되므로 조금 느리다 싶으면 반복횟수를 낮추고, 너무 빠르다 싶으면 1초 정도가 될 때까지 반복횟수를 늘립니다.

싱글 스레드 프로드래밍을 할 때 1초 동안 블로킹이 되는 것은 아닌지 걱정할 수도 있습니다. 다행이 crypto.randomBytes 와 crypto.pbkdf2 메서드는 내부적으로 스레드 풀을 사용해 멀티 스레딩으로 동작합니다.

 

$ node pbkdf2
salt +Dy2RyF/1b5Y1WpYlLudpx7rMk7GTR2uJL8ORpQaK4PqE+9wbXAn7vKUu9Nt5YgSStYhNPlx/xcCRGPIUtoWkQ==
password JQNqrpD0xC35fohHn30bt4KiSmzGWh+fjbdvmw/ddq5Qga1cbE3Q1tPLidVc+iRDAj9AVkzZhncutRACHcSbrQ==

randomBytes이므로 매번 실행할 때마다 결과가 달라집니다. 따라서 salt를 잘 보관하고 있어야 비밀번호도 찾을수 있습니다.

pbkdf2는 간단하지만 bcrypt나 scrypt보다 취약하므로 나중에 더 나은 보안이 필요하면 bcrypt나 scrypt 방식을 사용하면 됩니다. 이 책에서는 나중에 회원의 비밀번호를 암호화할 때 bcrypt방식을 사용합니다.

 

양방향 암호화

 

암호화된 문자열을 보호화할 수 있으며, 키 (열쇠) 라는 것이 사용됩니다. 대치형 암호화에서는 암호를 보호화하려면 암호화할 때 사용한 키와 같은 키를 사용해야합니다.

 

다음은 노드로 양방향 암호화하는 방법입니다.

const crypto = require("crypto");

const algorithm = "aes-256-cbc";
const key = "abcdefghijklmnopqrstuvwxyz123456";
const iv = "1234567890123456";

const cipher = crypto.createCipheriv(algorithm, key, iv);
let result = cipher.update("암호화할 문장", "utf-8", "base64");
result += cipher.final("base64");
console.log("암호화", result);

const decipher = crypto.createDecipheriv(algorithm, key, iv);
let result2 = decipher.update(result, "base64", "utf-8");
result2 += decipher.final(`utf-8`);
console.log("복호화", result2);
  • crypto.reacteCipheriv(알고리즘, 키, iv): 암호화 알고리즘과 키, iv를 넣습니다. 암호화 알고리즘은 aes-256-cbc를 사용했으며, 다른 알고리즘을 사용해도 됩니다. aes-256-cbc 알고리즘의 경우 키는 32바이트여야 하고, iv는 암호화할 때 사용하는 초기화 벡터를 의미하지만, 이 책에서 설명하기에는 내용이 많으므로 AES 암호화를 따로 공부하는 것이 좋습니다. 사용 가능한 알고리즘 목록은 crypto.getCiphers() 를 호출 하면 볼 수 있습니다.
  • cipher.update(문자열, 인코딩, 출력 인코딩): 암호화할 대상과 대상의 인코딩, 출력 결과물의 인코딩을 넣습니다. 보통 문자열은 utf-8 인코딩을, 암호는 base64를 많이 사용합니다.
  • cipher.final(출력 인코딩): 출력 결과물의 인코딩을 넣으면 암호화가 완료됩니다.
  • crypto.createDecipheriv(알고리즘, 키, iv): 복호화할 때 사용합니다. 암호화할 때 사용했던 알고리즘과 키, iv를 그대로 넣어야합니다.
  • decipher.update(문자열, 인코딩, 출력 인코딩): 암호화된 문장, 그 문장의 인코딩, 복호화할 인코딩을 넣습니다. createCipheriv의 update() 에서 utf8, bse64 순으로 넣었다면 createDecipheriv의 update()에서는 base64, utf8 순으로 넣으면 됩니다.
  • decipher.final(출력 인코딩): 복호화 결과물의 인코딩을 넣습니다.
$ node cipher
암호화 iiopeG2GsYlk6ccoBoFvEH2EBDMWv1kK9bNuDjYxiN0=
복호화 암호화할 문장

 

'블록체인 sw개발자' 카테고리의 다른 글

웹 소켓(Web Socket) 이해  (1) 2023.10.31
클래스형 컴포넌트( comment 기능)  (0) 2023.10.16
[MySQL] DB, 서버통신  (0) 2023.09.20
URI, URL 와 RESTful API  (0) 2023.09.11
Git Error, 해결  (0) 2023.08.24