otp 구현하기

  • 갑자기 otp를 구현하고 싶어졌다

Unix Epoch Time (Unix Time) 구하기

js
1
new Date().getTime(); // unix time

N 구하기

js
1
2
const ts = 30; // 30초 간격으로 토큰을 생성할 것
let N = Math.floor(new Date().getTime() / (ts * 1000));
  • unix time에서 토큰 생성간격 ts 만큼을 나누고 소수점은 버려서 N을 구한다

N 16진수 변환

js
1
let N_hex = ('000000000000000' + N.toString(16)).substr(-16); // 16자리가 되도록 앞에 0 패딩추가
  • N을 16진수로 변환하는데 16자리가 되도록 처리

m 구하기 (시간 베이스 메세지 값)

js
1
let m = Buffer.from(N_hex);
  • N_hex를 byte array로 변환

K 생성하기 (K : 공유키)

js
1
2
3
4
5
const base32 = require('base32'); // base32 모듈 임포트

const random12 = `${Math.random() * Math.pow(10, 20)}`.substring(0, 12); // 12자리 랜덤숫자 생성

const K = base32.encode(random12);
  • 랜덤값으로부터 20자리의 공유키 K 구하기
  • base32 모듈 설치
  • Math.random()으로 생성한 랜덤값을 12자리 문자열로 변환하고 base32인코딩하면 20자리의 문자열을 얻을 수 있음

HMAC hash 얻기

js
1
2
3
const crypto = require('crypto');

let hmac_hash = crypto.createHmac('sha1', K).update(m).digest('hex');
  • 내장 모듈 crypto 에서 hmac hash를 얻을 수 있다

OTP 생성

js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
const offset = parseInt(Number(`0x${hmac_hash[hmac_hash.length - 1]}`), 10);

// offset으로부터 4개 바이트 변환
const token_hex_4bytes = hmac_hash.substring(offset * 2, offset * 2 + 4 * 2);
let toekn_hex = '';

toekn_hex += (
'00' + (Number(`0x${token_hex_4bytes.substring(0, 2)}`) & 0x7f).toString(16)
).substr(-2);

for (let index = 2; index < token_hex_4bytes.length; index += 2) {
const element = token_hex_4bytes.substring(index, index + 2);
toekn_hex += ('00' + (Number(`0x${element}`) & 0xff).toString(16)).substr(-2);
}

const token = Number(`0x${toekn_hex}`).toString().substr(-6);
console.log(`token : ${token}`);
  • hmac_hash의 맨 마지막값을 offset으로 한다
  • hmac_hash offset부터 4바이트를 추출한고
  • offset 바이트에 & 0x7f 이진 연산 수행
  • 나머지 3개 바이트에 대해 & 0xFF 연산 수행
  • 6자리 토큰을 만든다고하면 그 결과의 뒤에서 6자리를 토큰으로 한다

개선사항

  • 생성된 토큰 값이 일부분 동일하게 유지된다

참고