/dev/random
1. 개요
/dev/random은 리눅스 커널에서 제공하는 난수 장치 파일로, 안전한 난수 생성을 위해 사용된다. 1994년 시어도어 초에 의해 처음 구현되었으며, `/dev/random`과 `/dev/urandom` 두 가지 장치 파일을 제공한다. `/dev/random`은 엔트로피 풀이 비어있을 경우 블로킹되며, `/dev/urandom`은 블로킹되지 않고 내부 풀을 재사용하여 의사 난수를 생성한다. 리눅스 커널 5.6부터는 `/dev/random`이 CSPRNG 초기화 후 `/dev/urandom`과 동일하게 동작하며, 2021년 리눅스 커널 5.17부터는 SHA-1 대신 BLAKE2s 해시 함수를 사용하여 보안을 강화했다. `/dev/random`은 다른 운영체제에서도 유사하게 구현되어 있으며, EGD와 같은 데몬을 통해 /dev/random을 지원하지 않는 시스템에서도 사용할 수 있다.
| 종류 | 장치 파일 |
|---|---|
| 설명 | 의사 난수 생성기 |
| 위치 | 유닉스 계열 운영체제 |
| 설명 | 환경 소음에서 추정된 엔트로피 풀에서 난수를 생성. 무작위성이 필요한 경우에 적합 (암호 키 생성 등). |
|---|---|
| 특징 | 엔트로피 풀 고갈 시 읽기 차단 높은 보안성 |
| 설명 | 의사 난수 생성기에서 난수를 생성. /dev/random보다 빠르지만, 초기 시드값 노출 시 보안에 취약할 수 있음. |
|---|---|
| 특징 | 엔트로피 풀 고갈 시에도 읽기 차단하지 않음 높은 성능 암호학적으로 안전하지 않을 수 있음 |
| 주의사항 | 부트 시퀀스 초기에 엔트로피가 부족할 경우 예측 가능한 값을 생성할 수 있음. 시스템 시작 후 충분한 엔트로피가 축적된 후에 사용하는 것이 안전함. |
-
장치 파일 -
널 장치
널 장치는 Version 5 Unix에서 처음 소개된 특수 파일로, 의도하지 않은 출력 스트림을 버리거나 입력 스트림을 위해 빈 파일 역할을 하며, 유닉스 및 유닉스 계열 운영체제에서 메시지 출력을 제어하는 데 활용된다. -
장치 파일 -
/dev/zero
`/dev/zero`는 유닉스 계열 운영체제에서 읽기 시 널 문자를 반환하고 쓰기 시 무시되는 특수한 장치 파일로, 데이터 초기화나 특정 크기의 파일 생성 등에 사용되며 SunOS 4.0에서 처음 도입되었다. -
난수 발생기 -
신뢰 플랫폼 모듈
신뢰 플랫폼 모듈(TPM)은 신뢰 컴퓨팅 그룹(TCG)에서 구상한 보안 장치로, 하드웨어 난수 생성, 암호 키 안전 생성, 원격 증명, 바인딩, 밀봉된 저장소 등의 기능을 제공하여 플랫폼 무결성 보장, 디스크 암호화, 디지털 권한 관리(DRM) 등 다양한 분야에 활용되며 여러 유형으로 구현되고 있다. -
난수 발생기 -
저불일치 수열
저불일치 수열은 수치적 적분 및 몬테카를로 방법에서 균등 분포에 가까운 점들을 생성하는 수열로, 데이터 포인트를 추가해도 기존 결과를 유지하며 무리수 활용, 판 데르 코르퓃 수열, 할턴 수열, 소볼 수열, 해머슬리 집합 등으로 구성되고 불일치도 평가를 위해 콕스마-흐와프카 부등식 등이 활용된다.
2. 역사
/dev/random은 1994년 시어도어 초가 리눅스 커널에 처음 구현하였다. 초기 구현은 SHA-1 해시 함수를 사용했으며, 암호화 해시 함수의 취약점이 발견될 경우를 대비하여 설계되었다.
초기 `/dev/random`은 엔트로피 풀의 노이즈 추정 비트 내에서만 난수 바이트를 반환했다. 엔트로피 풀이 비어 있으면 추가적인 환경 노이즈가 수집될 때까지 블로킹되었다. 이는 가능한 한 큰 엔트로피를 가진 출력을 제공하기 위함이며, 암호 키 생성 등에 사용되었다.
`/dev/urandom`은 `/dev/random`과 달리 내부 풀을 재사용하여 의사 난수 비트를 생성하므로 블로킹되지 않지만, 엔트로피가 적을 수 있다.
2006년 리눅스 난수 생성기에 대한 분석에서 임베디드 시스템이나 Live CD 환경에서의 취약점이 지적되었다.
2.1. 리눅스
리눅스 커널은 `/dev/random`과 `/dev/urandom` 두 가지 특수 장치 파일을 제공하여 난수 발생 기능을 제공한다. 시어도어 초가 1994년에 최초로 구현하였으며, 초기에는 SHA-1 해시 함수를 사용하였다.
`/dev/random`은 엔트로피 풀이 충분히 쌓일 때까지 블로킹되는 특징이 있다. 즉, 충분한 난수성이 확보될 때까지 대기한다. 반면 `/dev/urandom`은 무제한의 비차단 방식으로 동작하여, 엔트로피 풀이 부족하더라도 의사 난수를 생성하여 반환한다.
초기 리눅스 커널은 생성 방법에 취약점이 발견될 수 있다는 가정하에, 그러한 취약성에 내성을 갖도록 설계되었다. 엔트로피 풀의 노이즈 비트 수를 예측하고, 이 풀에서 난수를 생성했다. `/dev/random`은 원타임 패드나 암호 키와 같이 높은 수준의 무작위성이 필요한 경우에 사용되었으며, 엔트로피 풀이 비어있으면 블록되어 환경 노이즈 수집을 기다렸다.
`/dev/urandom`은 내부 풀을 재사용하여 의사 난수 비트 열을 생성하므로 블록되지 않지만, `/dev/random`에 비해 진정한 난수가 아닌 의사 난수로 생성된다. 그러나 암호론적 의사 난수 생성기를 사용하므로 일반적인 의사 난수 생성기보다는 안전하다.
2020년 리눅스 커널 5.6부터는 `/dev/random`의 동작 방식이 변경되었다. 초기화 이후에는 `/dev/urandom`과 동일하게 블로킹되지 않고 동작한다. 즉, 초기 부팅 시에만 잠깐 블로킹될 수 있다.
리눅스 커널은 난수 생성기의 보안 및 성능 개선을 지속적으로 진행해왔다. 2016년 커널 4.8에서는 `/dev/urandom`의 알고리즘이 ChaCha20 기반으로 변경되었고, 2020년 커널 5.17부터는 SHA-1 대신 더 안전한 BLAKE2s 해시 함수를 사용하도록 변경되었다.
2006년에는 리눅스 난수 생성기의 몇 가지 약점이 지적되기도 했다. 특히 임베디드 시스템이나 Live CD 환경에서 부팅 시 예측 가능성과 엔트로피 부족 문제가 제기되었다. 비휘발성 메모리가 있는 시스템에서는 셧다운 시 난수 생성기 상태를 저장하고 다음 부팅 시 사용하는 것이 권장되었다.
2.1.1. getrandom
리눅스 커널 3.17부터 `getrandom()` 시스템 콜이 추가되었다. 이는 `/dev/random` 또는 `/dev/urandom`에서 데이터를 읽어오는 것과 동일한 기능을 제공한다. 기본적으로 `/dev/urandom`과 동일하며, 옵션으로 `/dev/random`과 동일한 동작을 선택할 수 있다.
`/dev/random`과 `/dev/urandom`은 장치 파일이므로, 사용 시 `open` 시스템 콜로 열어야(파일 디스크립터를 할당) 한다. 따라서 파일 디스크립터가 한계까지 사용된 상태에서는 새로운 파일을 열 수 없으므로, 이러한 난수 읽기도 불가능해진다. 이는 자원 고갈 공격에 해당한다. 이 때문에 시스템 콜로서의 동일 기능이 추가되었다.
2.2. FreeBSD
FreeBSD 운영 체제는 CSPRNG를 제공하기 위해 256비트 얘로 알고리즘 변종을 구현하며, 이는 기존의 리눅스 스타일의 랜덤 장치를 대체한다. 리눅스의 `/dev/random`와 달리 FreeBSD의 `/dev/random` 장치는 차단(블로킹)을 하지 않는다. 동작 자체는 리눅스의 `/dev/urandom`와 비슷하지만, FreeBSD의 `/dev/urandom`은 `/dev/random`에 연결된다.
FreeBSD는 `/dev/urandom` 링크를 `/dev/random`에 제공한다. 둘 다 제대로 시드되기 전까지는 차단된다. FreeBSD의 PRNG(Fortuna)는 정기적으로 리시드(reseed)되며, 엔트로피를 추정하려 하지 않는다. 네트워크와 디스크 활동이 적은 시스템에서는 리시딩이 몇 초 후에 수행된다.
FreeBSD에서는 이전에는 Linux와 같은 구현이었지만, Fortuna영어를 사용하여 의사 난수를 제공하는 구현으로 되어 있다. Linux의 `/dev/random`과는 달리, FreeBSD의 `/dev/random`은 절대로 블록하지 않는다. 즉, Linux의 `/dev/urandom`과 유사하며, 엔트로피 풀이 아닌 암호론적 의사 난수 생성기로서 기능할 것을 의도하고 있다(FreeBSD에서는 `/dev/urandom`이 random으로의 심볼릭 링크이다).
공격자가 내부 상태를 모르는 경우, 의사 난수 생성기라도 충분히 안전하며, 게다가 엔트로피 풀보다 이해하기 쉽다. 엔트로피 풀에 의한 구현은 완벽하게 구현하면 완전히 안전해지지만, 엔트로피 예측이 과대하면 잘 설정된 의사 난수 생성기보다 약해진다. 공격자는 경우에 따라 엔트로피의 대부분을 제어할 수 있다. 예를 들어, 디스크리스 서버의 경우, 환경 잡음의 대부분은 네트워크에서 유래하므로, 중간자 공격에 취약해질 가능성이 있다.
2.3. OpenBSD
OpenBSD 5.1 (2012년 5월 1일) 이후로 `/dev/random`와 `/dev/arandom`은 RC4 기반의 알고리즘을 사용한다. (라이선스를 위해 ARC4로 이름이 변경됨)
OpenBSD 5.5 (2014년 5월 1일) 기준으로, OpenBSD의 랜덤 장치에 쓰이는 `arc4random()` 호출은 더 이상 ARC4를 사용하지 않고 ChaCha20을 사용한다. NetBSD의 레거시 `arc4random()`의 구현 또한 ChaCha20으로 전환되었다.
2.4. macOS 및 iOS
macOS와 iOS의 커널은 xnu로, `/dev/random`과 `/dev/urandom`은 동일하게 동작한다. iOS는 CTR_DRBG 기반 알고리즘을 사용한다.
모든 애플 운영체제는 적어도 2019년 12월부터, 아마 그 이전부터 SHA-256 기반의 Fortuna로 변경되었다. 보안 엔클레이브 RNG, 부팅 단계 타이밍 지터, 하드웨어 인터럽트 (타이밍 추정) 등 여러 엔트로피 소스가 사용된다. RDSEED/RDRAND는 이를 지원하는 인텔 기반 맥에서 사용된다. 시드(엔트로피) 데이터는 후속 재부팅을 위해 저장된다.
이전에는 macOS와 iOS는 SHA-1 기반의 160비트 Yarrow를 사용했다. `/dev/random`과 `/dev/urandom`은 차이가 없으며 동일하게 동작한다.
3. 기타 운영 체제
`/dev/random영어`과 `/dev/urandom영어`은 솔라리스, NetBSD, Tru64 UNIX 5.1B, AIX 5.1, HP-UX 11i v2에서도 이용할 수 있다.
윈도우 NT에서는 비슷한 기능이 `ksecdd.sys`를 통해 제공되지만, `\Device\KsecDD`라는 특수 파일을 읽는 것은 유닉스에서처럼 동작하지 않는다. 유사난수 바이트를 발생하는 문서화된 방식은 CryptGenRandom과 RtlGenRandom이다. 윈도우 파워셸은 `Get-SecureRandom영어` cmdlet을 통해 암호학적으로 안전한 의사 난수 생성기에 접근할 수 있다.
도스에서는 해당 기능을 기본적으로 지원하지 않지만, `noise.sys`라는 타사 오픈 소스 드라이버가 있으며, 이를 통해 `RANDOM$`과 `URANDOM$`이라는 두 장치를 만들어내며 랜덤 데이터 접근을 위해 `/DEV/RANDOM$영어`, `/DEV/URANDOM$영어`으로도 접근할 수 있다.
4. EGD (Entropy Gathering Daemon)
OpenSSL, GNU Privacy Guard, Apache HTTP Server 등 /dev/random이 없는 시스템에서 EGD를 이용할 수 있다. EGD는 다양한 시스템 정보를 수집하여 엔트로피를 확보하고, 편향을 제거하여 암호학적 품질을 높이며, 유닉스 도메인의 소켓 경유 (/dev/egd-pool이 자주 사용됨) 또는 TCP 소켓 경유로 접근할 수 있도록 한다. 엔트로피 수집은 자식 프로세스를 정기적으로 fork하여 수행하며, 빈번하게 변화하고 예측할 수 없는 시스템의 다양한 속성 (CPU, I/O, 네트워크, 로그 파일 내용, 임시 디렉토리 내용 등)을 조사한다.
EGD와 난수 데이터를 필요로 하는 다른 프로그램과의 통신은 단순한 프로토콜로 이루어진다. 클라이언트는 EGD 소켓에 연결하고, 선두의 옥텟이 나타내는 명령에 따라 요청을 보낸다.
* command 0: 현재 사용 가능한 엔트로피량을 문의한다. EGD는 블록하지 않고 제공 가능한 난수 바이트 수를 빅 엔디안의 4바이트 숫자로 반환한다.
* command 1: 블록하지 않고 난수 바이트 열을 얻는다. 요청 메시지의 두 번째 바이트에서 난수 바이트 열의 바이트 수를 지정한다 (1에서 255). 요청된 만큼의 난수 바이트가 없는 경우, 블록하지 않고 제공할 수 있는 만큼의 난수 바이트 열을 반환한다 (0바이트인 경우도 있음). 응답 메시지의 첫 번째 바이트가 반환된 난수 바이트 수, 그 직후에 실제 난수 바이트 열이 이어진다.
* command 2: 블록하면서 난수 바이트 열을 얻는다. 요청 메시지의 두 번째 바이트에서 난수 바이트 열의 바이트 수를 지정한다. 요청된 만큼의 엔트로피가 없는 경우, 충분한 수집이 이루어질 때까지 기다린다. command 1과 달리 응답 메시지는 처음부터 난수 바이트 열로 시작하며, 그 길이는 항상 요청 메시지에서 지정된 것과 같다.
* command 3: 엔트로피 업데이트. 클라이언트로부터 EGD 내부 풀에 추가할 엔트로피를 제공한다. 명령의 다음 2바이트가 16비트 빅 엔디안 정수로 해석되어, 공급할 난수 비트 수를 나타낸다. 네 번째 바이트는 그 뒤에 따르는 실제 데이터의 길이를 바이트 수로 나타낸다. EGD는 이를 내부 풀에 혼합하며, 응답 메시지는 반환하지 않는다.
5. 비판 및 개선
다니엘 J. 번스타인은 2014년 1월, 리눅스가 다양한 엔트로피 소스를 혼합하는 방식에 대해 비판했다. 그는 한 엔트로피 소스가 다른 엔트로피 소스를 감시하고, 다른 소스의 무작위성을 무효화하기 위해 출력을 수정할 수 있는 공격을 설명했다. 번스타인은 공격자가 DSA와 ECDSA를 손상시키기 위해 RNG 출력의 처음 4비트를 0으로 만들 수 있다고 추정했다. 이는 리눅스가 단일 고품질 시드 대신 지속적으로 해시 함수를 다시 시드하기 때문에 가능하다고 보았다. 또한, 번스타인은 암호학적으로 안전한 의사 난수 생성기(CSPRNG)가 초기화되면 엔트로피 주입은 무의미하다고 주장했다.
2006년 3월, 구터만(Gutterman), 핑카스(Pinkas), 앤드 레이먼(Reinman)은 리눅스 난수 생성기에 대한 상세한 암호 분석을 발표하여 몇 가지 약점을 설명했다. 그들이 보고한 가장 심각한 문제는 임베디드 시스템이나 라이브 CD 시스템, 예를 들어 라우터 및 디스크리스 노드와 같은 시스템으로, 부팅 상태를 예측할 수 있고 환경에서 사용할 수 있는 엔트로피의 양이 제한적일 수 있다는 점이다. 비휘발성 메모리가 있는 시스템의 경우, 다음 재부팅 시 RNG 상태에 포함될 수 있도록 종료 시 RNG에서 일부 상태를 저장하는 것을 권장했다. 네트워크 트래픽이 주요 엔트로피 소스인 라우터의 경우, 재부팅 시 상태를 저장하면 "잠재적 공격자가 라우터를 처음 서비스에 투입할 때부터 모든 네트워크 트래픽을 도청하거나" 라우터의 내부 상태에 직접 접근해야 한다고 지적했다. 그들은 네트워크 트래픽을 원격에서 캡처할 수 있고 RNG를 사용하여 데이터 암호화를 위한 키를 생성할 수 있는 무선 라우터의 경우 이 문제가 특히 중요하다고 언급했다.
제이슨 A. 도넨펠트는 커널 5.17(커널 5.10.119로 백포팅)에서 리눅스 엔트로피 풀 인프라의 새로운 설계를 제시했다. 도넨펠드는 단일 4096비트 LFSR로 구성된 이전 풀이 (1) 공격자가 알려진 입력의 효과를 되돌릴 수 있으며, (2) 전체 풀의 상태가 유출되면 공격자가 풀의 모든 비트를 0으로 설정할 수 있다는 두 가지 공격에 취약하다고 보고했다. 더 빠르고 안전한 그의 새로운 설계는 256비트 풀을 혼합하기 위해 BLAKE2s 해시 함수를 사용한다.