1. OTP
오늘날 보안의 핵심은 '일회용 비밀번호(OTP)'입니다.
고정된 비밀번호는 탈취의 위험이 크지만, 매번 바뀌는 OTP는 강력한 2차 인증 수단이 됩니다.
이번 포스팅에서는 OTP의 대표적인 3가지 방식인 시간 동기화(TOTP), 이벤트 동기화(HOTP), 챌린지-응답(C-R) 방식을 Java와 Python으로 각각 구현해 보겠습니다.
2. 서버 동기화 방식 (TOTP: Time-based OTP)
개념: 서버와 사용자의 '현재 시간'을 기준으로 비밀번호를 생성합니다. 보통 30초마다 번호가 갱신되는 가장 대중적인 방식입니다.
첫번째는 자바(JAVA) 구현방식이다.





위의 사진과 같은 코드의 방식으로 진행되고 여기서 핵심 코드는
long currentTimeSeconds = System.currentTimeMillis() 핵심: 시간을 30초 단위 카운터로 변환
1000L; long counter = currentTimeSeconds / 30L; 이후 HMAC-SHA1로 해싱하여 6자리 추출
System.currentTimeMillis()
: 현재 시간을 밀리초 단위로 가져옵니다.
이를 1000으로 나누어 초 단위로 변환 후 30으로 나누어 시간 동기화 카운터를 만듭니다.
Mac.getInstance("HmacSHA1")
HMAC-SHA1 암호화 알고리즘 인스턴스를 생성합니다.
공유 비밀키와 시간을 조합해 해시를 만드는 핵심 역할을 합니다.
SecretKeySpec 제공된 비밀키 문자열을 암호화 알고리즘에서
사용할 수 있는 키 형식으로 변환합니다.
mac.doFinal(data)
준비된 카운터 데이터(시간 값)를 비밀키와 함께 해싱하여 고유한 바이트 배열을 생성합니다.
Dynamic Truncation
생성된 해시 배열에서 특정 위치의 값을 추출해 사람이 읽기 쉬운 6자리 숫자로 바꾸는 과정입니다. (코드 하단의 비트 연산 부분)
결과 값:

두번째는 파이썬(Python) 구현방식이다.

결과 값:

3. 이벤트 동기화 방식 (HOTP: HMAC-based OTP)
개념: 시간이 아닌 '카운터(버튼을 누른 횟수)'를 기준으로 합니다. 서버와 클라이언트가 동일한 카운트 숫자를 유지해야 인증이 가능합니다.
첫번째는 자바(JAVA) 구현방식이다.


위의 사진과 같은 코드의 방식으로 진행되고 여기서 핵심 코드는
byte[] data = ByteBuffer.allocate(8).putLong(counter).array(); 핵심: 횟수(counter)를 8바이트 데이터로 변환
ByteBuffer.allocate(8).putLong(counter)
숫자 형태인 카운터(1, 2, 3...)를 8바이트(64비트)의 바이트 배열로 변환합니다.
암호화 함수인 HMAC은 숫자 데이터가 아닌 바이트 데이터를 입력값으로 받기 때문에 이 변환 과정이 꼭 필요합니다.
SecretKeySpec(key.getBytes(), "HmacSHA1")
사용자가 설정한 비밀키 문자열을 HMAC-SHA1 알고리즘 전용 키 객체로 변환합니다. 이 키가 있어야만 해시값을 안전하게 생성할 수 있습니다.
mac.doFinal(data)비밀키와 바이트로 변환된 카운터 값을 조합하여 최종적인 **해시값(20바이트)**을 계산합니다.
이 값은 외부에서 예측하기 불가능한 난수 형태를 띱니다.
Bitwise Operations (& 0x7f, << 24 등)
생성된 긴 해시값에서 특정 4바이트를 뽑아내어 사람이 읽을 수 있는 정수 형태로 변환하는 과정입니다. 이를 통해 복잡한 해시가 6자리 OTP 번호로 바뀝니다
결과 값:

두번째는 파이썬(Python) 구현방식이다.

결과 값:

4. 챌린지-응답 방식 (Challenge-Response)
개념: 서버가 난수(Challenge)를 던지면, 사용자가 자신의 비밀키로 계산한 결과값(Response)을 돌려주는 방식입니다. 재전송 공격에 매우 강력합니다.
첫번째는 자바(JAVA) 구현방식이다.


위의 사진과 같은 코드의 방식으로 진행되고 여기서 핵심 코드는
SecretKeySpec signKey = new SecretKeySpec(secret.getBytes(), "HmacSHA256"); 핵심: HMAC-SHA256을
사용한 긴 해시 응답
Mac.getInstance("HmacSHA256")
1, 2번보다 더 강력한 보안이 필요한 챌린지 방식에서는 SHA256 알고리즘을 사용 이는 해시값이 더 길고 복잡하여 위조가 거의 불가능합니다.
challenge.getBytes()서버가 보낸 일회용 난수(Challenge)를 암호화 계산을 위해 바이트 배열로 변환합니다.
new BigInteger(1, rawHmac)계산된 HMAC 결과(바이트 배열)는 읽기 어렵기 때문에, 이를 매우 큰 숫자로 변환합니다.
String.format("%064x", ...)큰 숫자로 변환된 데이터를 16진수(hex) 문자열로 포맷팅합니다. 챌린지-응답 방식에서는 보통 6자리 숫자 대신 이렇게 긴 문자열(응답값)을 서버로 전송하여 인증합니다.
결과 값 :

두번째는 파이썬(Python) 구현방식이다.

결과 값:

5. 결론 및 비교
- TOTP: 스마트폰 앱 인증에 적합 (Google Authenticator 등)
- HOTP: 전용 하드웨어 토큰기에 적합
- Challenge-Response: 높은 보안이 필요한 서버-클라이언트 통신에 적합