노드

노드 crypto 와 util

포칼이 2023. 4. 12. 11:02

단방향 암호화 (crypto)

암호화는 가능하지만 복호화는 불가능한 방법이다.

  • 암호화 : 평문을 암호로 만듦
  • 복호화 : 암호를 평문으로 해독

단방향 암호화의 대표 주자는 해시 기법이다.

  • 문자열을 고정된 길이의 다른 문자열로 바꾸는 방식
  • abcdefgh 문자열 -> qvwe

 

해시 함수의 예시는 다음과 같다

 

Hash 사용하기(sha512)

createHash(알고리즘) : 사용할 해시 알고리즘을 넣어준다.

*md5, sha1, sha256을 넣는 것도 가능하지만 md5, sha1 같은 경우 취약점이 발견되기도 했고 추천하지는 않는다.

현재는 sha512 정도로 충분하지만, 나중에 sha512마저도 취약해지면 더 강화된 알고리즘으로 바꿔야 한다.

 

update(문자열) : 변환할 문자열을 넣어준다.

 

digest(인코딩) : 인코딩할 알고리즘을 넣어준다.

base64. hex, latin1이 주로 사용되는데, 그 중 base64가 결과 문자열이 가장 짧아 애용된다. 결과물로 변환된 문자열을 반환한다.

사용법은 다음과 같다

//hash.js
const crypto = require('crypto');

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

'비밀번호' 가 sha512 알고리즘으로 인해 아래와 같은 문자열로 변했다.

이런식으로 암호화를 하는 방식도 있고 다른 방식도 있다.

pbkdf2

컴퓨터의 발달로 기존 암호화 알고리즘이 위협받고 있다.

  • sha512가 취약해지면 sha3로 넘어가야한다.
  • 현재는 pbkdf2, bcrypt, scrypt 알고리즘으로 비밀번호를 암호화한다.
  • Node는 pbkdf2와 scrypt 를 지원한다.

pbkdf2란?

crypto.randomBytes로 64바이트 문자열을 생성한다. -> salt 역할

pbkdf2 인수로 순서대로 -> 비밀번호, salt, 반복 횟수, 출력 바이트, 알고리즘 

반복 횟수를 조정해 암호화하는데 1초 정도 걸리게 맞추는 것이 권장된다. 

//pbkdf2.js
const crypto = require('crypto');

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

*salt는 해독을 더 어렵게 하기 위해서 추가된 것이라고 일단 생각하자.

*pbkdf2로 암호화를 한다면 DB에는 salt와 비밀번호 둘 다 저장해 놔야 한다. salt가 달라지면 비밀번호도 달라지기 때문.

양방향 암호화 & 메서드

대칭형 암호화(암호문 복호화 가능)

  • key가 사용됨
  • 암호화할 때와 복호화 할 때 같은 key를 사용해야 한다.
//cipher.js
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('암호화할 문장', 'utf8', 'base64');
result += cipher.final('base64');
console.log('암호화:', result);

const decipher = crypto.createDecipheriv(algorithm, key, iv);
let result2 = decipher.update(result, 'base64', 'utf8');
result2 += decipher.final('utf8');
console.log('복호화:', result2);

crypto.createCipheriv(알고리즘, 키, iv) : 암호화 알고리즘과 키, 초기화 벡터를 넣어준다.

  • 암호화 알고리즘은 aes-256-cbc를 사용했다. 다른 알고리즘도 사용 가능하다.
  • 키는 32바이트, 초기화벡터(iv)는 16바이트로 고정이다.

cipher.update(문자열, 인코딩, 출력 인코딩) : 암호화할 대상과 대상의 인코딩, 출력 결과물의 인코딩을 넣어준다.

  • 보통 문자열은 utf8 인코딩을, 암호는 base64를 많이 사용한다.

cipher.final(출력 인코딩) : 출력 결과물의 인코딩을 넣어주면 암호화가 완료된다.

 

crypto.createDecipheriv(알고리즘, 키, iv) : 복호화할 때 사용된다. 암호화할 때 사용했던 알고리즘과 키, iv를 그대로 넣어주어야 한다.

 

decipher.update(문자열, 인코딩, 출력 인코딩) : 암호화된 문장, 그 문장의 인코딩, 복호화할 인코딩을 넣어준다.

  • createCipher의 update() 에서 utf8, base64 순으로 넣었다면 createDecipher의 update()에서는 base64, utf8 순으로 넣으면 된다.

decipher.final(출력 인코딩) : 복호화 결과물의 인코딩을 넣어준다.

util

각종 편의 기능을 모아둔 모듈이다.

  • deprecated와 promisify가 자주 쓰인다.
//util.js
const util = require('util');
const crypto = require('crypto');

const dontUseMe = util.deprecate((x, y) => {
  console.log(x + y);
}, 'dontUseMe 함수는 deprecated되었으니 더 이상 사용하지 마세요!');
dontUseMe(1, 2);

const randomBytesPromise = util.promisify(crypto.randomBytes);
randomBytesPromise(64)
  .then((buf) => {
    console.log(buf.toString('base64'));
  })
  .catch((error) => {
    console.error(error);
  });

deprecated를 쓰는 이유는 잘못 만들었거나 필요가 없는 것이 있을 경우 함부로 그 코드를 지울 수가 없기 때문이다.

함부로 코드를 지우게 되면 그것을 사용하는 사람들 또한 영향을 받기 때문이다. 

그래서 지우는 대신에 알려주는 deprecated를 사용하게 된다. 

 

promisify의 경우 아직 노드에서는 아직 promise를 지원 안하는 함수들이 많다. 그냥 콜백 함수로 남아있는 함수들이 많다.

이런 함수들을 promise와 같이 쓸 때 async와 await을 같이 활용하기가 어려운데 이때 uitil.promisify로 그 함수를 감싸주면 마법같이 then 과 catch를 사용할 수 있게 된다. 단, 콜백이 (error, data) => {} 형식이어야 한다. 

정리하자면 다음과 같다

util.deprecate : 함수가 deprecated 처리되었음을 알려준다.

  • 첫 번째 인자로 넣은 함수를 사용했을 때 경고 메시지가 출력된다.
  • 두 번째 인자로 경고 메시지 내용을 넣으면 된다. 함수가 조만간 사라지거나 변경될 때 알려줄 수 있어 유용하다.

util.promisify : 콜백 패턴을 프로미스 패턴으로 바꿔준다.

  • 바꿀 함수를 인자로 제공하면 된다. 이러면 async/await 패턴까지 사용할 수 있다. 단, 콜백이 (error, data) => {} 형식이어야 한다.