이 시스템 전체의 바닥에 있는 난수를 다룹니다. 난수는 출처에서도, 쓰는 방법에서도 틀릴 수 있습니다.
IV, salt, challenge 토큰, 세션 토큰, PKCE verifier는 모두 난수에서 나옵니다. 다른 모듈이 아무리 정확해도 난수가 예측 가능하면 전부 무너지므로, 난수는 구현 세부가 아니라 독립된 계약으로 다룹니다. 실패는 잘못된 출처와 잘못된 사용, 두 가지로 나타납니다. 뒤의 것은 올바른 생성기를 쓰고도 일어납니다.
내부 상태를 관찰해도 다음 값을 계산할 수 없는 생성기만 토큰과 키에 씁니다.
토큰 길이를 비트 단위로 따져 추측 비용이 충분한지 판단합니다.
나머지 연산으로 범위를 접으면 분포가 고르지 않아 추측 비용이 낮아집니다.
Math.random()은 통계적으로 고르게 보이도록 설계됐을 뿐, 예측할 수 없도록 설계되지는 않았습니다. 내부 상태가 있고, 출력을 몇 개 관찰하면 그 상태를 복원해 이후 값을 전부 계산할 수 있습니다. 예측할 수 있는 토큰은 토큰 구실을 못 하므로, 이 시스템은 어떤 경로에서도 Math.random()을 쓰지 않습니다.
randomBytes(1)[0] % 10은 흔한 실수입니다. 0~255를 10으로 나눈 나머지는 고르지 않아서 0~5는 26번, 6~9는 25번 나옵니다. 통계가 아니라 산술의 문제라 전부 열거해 확인할 수 있고, 증명이 실제로 그렇게 합니다. 해법은 나머지로 접는 대신 범위를 벗어난 값을 버리고 다시 뽑는 것(거부 표집)이며, randomInt가 이 방식을 씁니다.
16진수 32자와 base64url 32자는 길이가 같아 보이지만 각각 128비트와 192비트를 담습니다. 추측 비용을 정하는 것은 표기 방식이 아니라 원본 바이트 수입니다. 그래서 이 모듈은 토큰에 최소 128비트를 요구하고, 그보다 짧으면 아예 만들지 않습니다.
아래 증명은 글로 된 설명이 아닙니다. 버튼을 누르면 이 서버가 지금 직접 계산합니다.
엔트로피는 문자열 길이가 아니라 원본 바이트 수로 정해집니다. 16진수 32자와 base64url 32자는 길이가 같아 보여도 담는 비트 수가 다릅니다. 이 모듈은 토큰에 128비트 하한을 두고, 그보다 짧으면 생성 자체를 거부합니다.
표준 문서에 실린 테스트 벡터를 그대로 넣어 같은 값이 나오는지 확인합니다.
올바른 생성기를 써도 범위를 줄이는 방법이 틀리면 편향이 생깁니다. randomBytes(1)[0] % 10은 0~5를 26번, 6~9를 25번 냅니다. 통계가 아니라 산술이라 전수 열거로 확인할 수 있습니다. 해법은 나머지로 줄이는 대신 범위를 벗어난 값을 버리고 다시 뽑는 것입니다.
막아야 할 입력을 실제로 넣고 거부되는지 확인합니다.
규격 원문을 먼저 실었습니다. 사례는 이 분야에서 실제로 보안이 뚫린 일을 다룬 자료입니다.
엔트로피를 어디서 얻는지와 흔한 오용을 정리한 문서입니다.
결정론적 난수 생성기가 갖춰야 할 요구사항을 정의합니다.
엔트로피 출처를 줄인 한 줄짜리 패치 때문에 키 공간이 32,768개로 줄어든 사례입니다.
다음 단계
컨테이너가 막 부팅한 직후처럼 엔트로피가 부족할 수 있는 구간에서도 생성기를 쓸 수 있는지 점검합니다.