일회용 비밀번호 구현
어떻게 구현하는지 궁금해서 구현해봤습니다.
이번 글에서는 구글 OTP를 이용하여 쉽게 생성할 수 있는 TOTP(Time-Based One-Time Password)에 대해 설명하겠습니다.
암호
이 코드를 사용하여 쉽게 2단계 인증을 시도할 수 있습니다.
https://github.com/ksrnnb/otp
사양
TOTP
TOTP는 RFC6238에서 다음과 같이 정의된다.
HOTP(HMAC-Based One-Time Password)에 대해서는 후술한다.
TOTP = HOTP(K, T)
T = (Current Unix time - T0) / X
K: a key to generate HOTP
T: an integer and represents the number of time steps between the initial counter time T0 and the current Unix time.
T0: the Unix time to start counting time steps (default value is 0)
X: the time step in seconds (default value X = 30 seconds)
Golang 코드는 다음과 같습니다.
// TOTP time step
const timeStepSecond int64 = 30
func New(secret []byte) string {
return hotp.New(secret, counter())
}
func counter() uint64 {
return uint64(time.Now().Unix() / timeStepSecond)
}
뜨거운
HOTP는 RFC4226에서 다음과 같이 정의된다.
HMAC-SHA-1은 비밀 키 K와 카운터 C에서 계산된 다음 잘립니다.
TOTP의 경우 카운터는 T = (Current Unix time - T0) / X
로 계산됩니다.
HOTP(K,C) = Truncate(HMAC-SHA-1(K,C))
여기서는 Trancate 기능에 대해 설명하겠습니다.
1 단계
먼저 간단히 HMAC-SHA-1을 계산합니다. 이 결과는 20바이트입니다.
HS = HMAC-SHA-1(K,C)
Golang gcode는 다음과 같습니다.
이진 패키지는 숫자에서 []바이트로 변환하는 데 사용됩니다.
package hotp
import (
"crypto/hmac"
"crypto/sha1"
"encoding/binary"
)
func hmacSha1(secret []byte, counter uint64) []byte {
mac := hmac.New(sha1.New, secret)
// uint64 => 8 byte
byteCounter := make([]byte, 8)
binary.BigEndian.PutUint64(byteCounter, counter)
mac.Write(byteCounter)
return mac.Sum(nil)
}
2 단계
4바이트 문자열을 생성하고 HMAC-SHA-1 값에서 숫자로 변환합니다.
4바이트를 생성하는 방법은 RFC4226에 작성되어 있습니다.
DT(String) // String = String[0]...String[19]
Let OffsetBits be the low-order 4 bits of String[19]
Offset = StToNum(OffsetBits) // 0 <= OffSet <= 15
Let P = String[OffSet]...String[OffSet+3]
Return the Last 31 bits of P
Golang 코드는 다음과 같습니다.
하위 4비트의 오프셋은 후행 문자와 0xf의 논리적 곱으로 계산됩니다.
그러면 offset ~ offset+3에서 마지막 31비트를 얻는다.
이 예제는 사양처럼 작성되었지만 binary.BigEndian.Uint32(hs[offset:offset+4]) & 0x7fffffff
대체하는 것이 이해하기 쉬울 수 있습니다.
func dynamicTruncate(hs []byte) uint32 {
// get low-order 4 bits of hs[tail]
// 0xf => 0000 1111
offset := hs[len(hs)-1] & 0xf
// get last 31 bits for hs[offset]...hs[offset + 3]
// 0x7F => 0111 1111
return uint32(hs[offset]&0x7f)<<24 |
uint32(hs[offset+1]&0xff)<<16 |
uint32(hs[offset+2]&0xff)<<8 |
uint32(hs[offset+3]&0xff)
}
3단계
d를 일회용 비밀번호의 자릿수라고 하고, 2단계에서 구한 값을 10^d
로 나눈 나머지가 HOTP 값이다.
Golang 코드는 다음과 같습니다.
num := dynamicTruncate(hs)
codeNum := num % uint32(math.Pow10(digits))
f := fmt.Sprintf("%%0%dd", digits)
oneTimePassword := fmt.Sprintf(f, code)
예시
Google OTP 설정
여기서는 일회용 비밀번호를 사용해 보도록 하겠습니다. ( README )
먼저 Google Authenticator를 설치하고 QR 코드를 스캔합니다.
QR 코드에서 문자열 값otpauth://totp/otp_example?secret=NBSWY3DP
을 가져올 수 있습니다. 이 형식은 [Google OTP 키 URI 형식]에 정의되어 있습니다.( https://github.com/google/google-authenticator/wiki/Key-Uri-Format )
여기서 NBSWY3DP
는 인코딩된 값 hello
입니다. 일회용 비밀번호를 사용하는 경우 각 사용자마다 고유하고 임의의 문자열을 지정해야 합니다.
서버 실행
make run
를 실행하여 서버를 실행합니다.
로그인
localhost:8080에 접속하여 아이디/비밀번호로 로그인합니다.
이름
값
ID
호게호게
비밀번호
호게호게
그런 다음 일회용 비밀번호가 필요하며 Google Authenticator에서 생성된 6자리 숫자를 입력해야 합니다.
이 값과 서버에서 생성된 값을 확인한 후 로그인할 수 있습니다.
자세한 세부 사항
TOTP
TOTP는 RFC6238에서 다음과 같이 정의된다.
HOTP(HMAC-Based One-Time Password)에 대해서는 후술한다.
TOTP = HOTP(K, T)
T = (Current Unix time - T0) / X
K: a key to generate HOTP
T: an integer and represents the number of time steps between the initial counter time T0 and the current Unix time.
T0: the Unix time to start counting time steps (default value is 0)
X: the time step in seconds (default value X = 30 seconds)
Golang 코드는 다음과 같습니다.
// TOTP time step
const timeStepSecond int64 = 30
func New(secret []byte) string {
return hotp.New(secret, counter())
}
func counter() uint64 {
return uint64(time.Now().Unix() / timeStepSecond)
}
뜨거운
HOTP는 RFC4226에서 다음과 같이 정의된다.
HMAC-SHA-1은 비밀 키 K와 카운터 C에서 계산된 다음 잘립니다.
TOTP의 경우 카운터는
T = (Current Unix time - T0) / X
로 계산됩니다.HOTP(K,C) = Truncate(HMAC-SHA-1(K,C))
여기서는 Trancate 기능에 대해 설명하겠습니다.
1 단계
먼저 간단히 HMAC-SHA-1을 계산합니다. 이 결과는 20바이트입니다.
HS = HMAC-SHA-1(K,C)
Golang gcode는 다음과 같습니다.
이진 패키지는 숫자에서 []바이트로 변환하는 데 사용됩니다.
package hotp
import (
"crypto/hmac"
"crypto/sha1"
"encoding/binary"
)
func hmacSha1(secret []byte, counter uint64) []byte {
mac := hmac.New(sha1.New, secret)
// uint64 => 8 byte
byteCounter := make([]byte, 8)
binary.BigEndian.PutUint64(byteCounter, counter)
mac.Write(byteCounter)
return mac.Sum(nil)
}
2 단계
4바이트 문자열을 생성하고 HMAC-SHA-1 값에서 숫자로 변환합니다.
4바이트를 생성하는 방법은 RFC4226에 작성되어 있습니다.
DT(String) // String = String[0]...String[19]
Let OffsetBits be the low-order 4 bits of String[19]
Offset = StToNum(OffsetBits) // 0 <= OffSet <= 15
Let P = String[OffSet]...String[OffSet+3]
Return the Last 31 bits of P
Golang 코드는 다음과 같습니다.
하위 4비트의 오프셋은 후행 문자와 0xf의 논리적 곱으로 계산됩니다.
그러면 offset ~ offset+3에서 마지막 31비트를 얻는다.
이 예제는 사양처럼 작성되었지만
binary.BigEndian.Uint32(hs[offset:offset+4]) & 0x7fffffff
대체하는 것이 이해하기 쉬울 수 있습니다.func dynamicTruncate(hs []byte) uint32 {
// get low-order 4 bits of hs[tail]
// 0xf => 0000 1111
offset := hs[len(hs)-1] & 0xf
// get last 31 bits for hs[offset]...hs[offset + 3]
// 0x7F => 0111 1111
return uint32(hs[offset]&0x7f)<<24 |
uint32(hs[offset+1]&0xff)<<16 |
uint32(hs[offset+2]&0xff)<<8 |
uint32(hs[offset+3]&0xff)
}
3단계
d를 일회용 비밀번호의 자릿수라고 하고, 2단계에서 구한 값을
10^d
로 나눈 나머지가 HOTP 값이다.Golang 코드는 다음과 같습니다.
num := dynamicTruncate(hs)
codeNum := num % uint32(math.Pow10(digits))
f := fmt.Sprintf("%%0%dd", digits)
oneTimePassword := fmt.Sprintf(f, code)
예시
Google OTP 설정
여기서는 일회용 비밀번호를 사용해 보도록 하겠습니다. ( README )
먼저 Google Authenticator를 설치하고 QR 코드를 스캔합니다.
QR 코드에서 문자열 값otpauth://totp/otp_example?secret=NBSWY3DP
을 가져올 수 있습니다. 이 형식은 [Google OTP 키 URI 형식]에 정의되어 있습니다.( https://github.com/google/google-authenticator/wiki/Key-Uri-Format )
여기서 NBSWY3DP
는 인코딩된 값 hello
입니다. 일회용 비밀번호를 사용하는 경우 각 사용자마다 고유하고 임의의 문자열을 지정해야 합니다.
서버 실행
make run
를 실행하여 서버를 실행합니다.
로그인
localhost:8080에 접속하여 아이디/비밀번호로 로그인합니다.
이름
값
ID
호게호게
비밀번호
호게호게
그런 다음 일회용 비밀번호가 필요하며 Google Authenticator에서 생성된 6자리 숫자를 입력해야 합니다.
이 값과 서버에서 생성된 값을 확인한 후 로그인할 수 있습니다.
자세한 세부 사항
The validation system should compare OTPs not only with the receiving timestamp but also the past timestamps that are within the transmission delay.
기타
TOTP는 예를 들어 재동기화와 같이 더 자세히 정의되지만 간단하기 때문에 생략합니다.
더 자세히 알고 싶다면 참고 문헌을 읽는 것이 좋습니다.
참조
Reference
이 문제에 관하여(일회용 비밀번호 구현), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://dev.to/ksrnnb/implementing-one-time-password-2m70텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)