2.1 무작위 대 비무작위

11010110 과 00000000

둘 중 무엇이 더 무작위해 보이는가?

대부분의 사람들은 11010110이 더 무작위해 보인다고 한다. 하지만 확률로 따져보았을 때, 둘은 1/(2^8)의 확률로 동일하게 생성된다. 여기서 사람들이 무작위성(randomness)을 식별할 때 저지르는 두 가지 오류를 확인할 수 있다.

1) 비무작위성(non-randomness)을 무작위성으로 오해 : 단지 무작위해보인다는 이유로 무작위하게 생성되었다고 생각하는 실수

2) 무작위성을 비무작위성으로 오해 : 어떠한 패턴을 보고 그런 패턴이 발생한 이유가 있을 것이라고 생각하는 실수

** 보안을 위해서 무작위해 보이는 것과 실제로 무작위한 것을 구별하는 것은 굉장히 중요!!

2.2 확률분포로서의 무작위성

- 확률분포

: 주어진 과정의 무작위성에 관해 알아야 할 모든 정보를 제공한다.

: 무작위 과정의 모든 가능한 결과에 부여된 확률(probability)을 나열한 것이다.

- 균등분포 ( uniform distribution : 또는 고른 분포 )

: 모든 결과의 발생 가능성이 같다.

ex) 128비트 키를 무작위로 고르게 선택한다면 1/(2^128) 만큼 의 확률로 선택

- 비균등분포 ( non-uniform distribution )

: 확률들이 모두 같지는 않은 분포

ex) 동전 던지기에서 앞면과 뒷면이 나올 확률이 다르다고 가정할 때.

-> 이러한 동전을 가리켜 ' 편향되었다(biased) ' 라고 한다.

2.3 엔트로피 : 불확실성의 측도

- 엔트로피

: 시스템에 존재하는 무질서의 측도(measure)

: 어떤 무작위 과정이 의외의 결과를 내는 경우가 얼마나 많은지를 나타내는 값

: 정보를 비트들의 개수로 표현할 수 있게 이진 로그를 사용

: 수식에서 음의 합을 사용하는 이유 : log(1/2) 면 (물론 이진로그) 음의 값이 나오기 때문에 엔트로피를 음수가 아닌 값으로 맞춰주기 위해서 음의 합 사용

: 엔트로피는 확률분포가 균등분포일 때 최대 ( 균등분포는 모든 확률이 같기 때문에 불확실성이 최대 )

-> 만약 5가지의 선택사항이 있을 때

a) 모든 확률은 1/5로 같음

b) 1/25, 18/25, 2/25, 3/25, 1/25 각각 이런 확률

그러면 a)는 모든 확률이 같기 때문에 무엇이 선택될 지 예측 불가능

b)는 2번이 선택될 것이라 예측 가능

** 정리를 해보면

엔트로피가 높다??? 예측이 어렵고, 랜덤하다.

2.4 난수 발생기(RNG)와 의사난수 발생기(PRNG)

암호 시스템은 무작위성을 얻기 위해 다음과 같은 구성요소를 갖추어야 한다.

1) 불확실성의 원천 또는 엔트로피원(source of entropy) : 난수 발생기 ( random number generator, RNG )가 담당

2) 엔트로피원으로부터 고품질 무작위 비트를 산출하는 암복호화 알고리즘 : 의사난수 발생기 ( pseudorandom number generators, PRNG )가 담당

- RNG : 디지털 시스템 안에서 아날로그 세계의 엔트로피를 이용해서 예측 불가능한 비트들을 산출하는 소프트웨어 또는 하드웨어 구성요소 ( ex) 온도, 음향적 잡음, 공기의 난류, 정전기, 시스템에 부착된 sensor, 입출력장치, 네트워크나 디스크활동, 시스템 로그, 실행중인 프로세스들, 키 입력이나 마우스 이동 등 )

-> 좋은 엔트로피원일 수 있지만, 공격자의 조작과 악용에 취약, 난수 비트들을 생성하는 속도 느림

- PRNG : 적은 수의 진정한 난수 비트들로부터 다수의 인위적인 난수 비트들을 신뢰성 있게 산출

RNG          vs          PRNG

          느리다                    빠르다 (비교적)

비결정론적                결정론적

엔트로피가 높다는 보장 X             최고의 엔트로피 보장

 

** 결정론적 : 예측 그대로 동작하는 알고리즘, 어떤 특정 입력이 들어오면 언제나 똑같은 과정을 거쳐 언제나 똑같은 결과를 내놓는다.

과정 도식화한 것

2.4.1 PRNG의 작동 방식

주기적으로 RNG로부터 받은 난수 비트들을 이용해서 큰 메모리 버퍼(entropy pool)의 내용을 갱신.

엔트로피 풀이 PRNG의 엔트로피원이다.

의사난수 비트열을 생성하기 위해 DRBG( Deterministic random bit generator, 결정론적 난수 비트 발생기 ) 알고리즘 실행. DRBG는 같은 입력을 넣으면 같은 출력이 나옴 -> 같은 입력을 두 번 제공하지 말아야 한다.

작동과정에서 PRNG는 다음 세 가지 연산 수행

1) init() : 엔트로피 풀, PRNG 내부 상태 초기화

2) refresh(R) : 어떤 자료 R( RNG 에서 얻은 자료 ) 을 이용해서 엔트로피 풀 갱신 -> 임의의 통계적 편향 제거

3) next(N) : N개의 의사난수 비트 생성 후 엔트로피 풀 갱신

2.4.2 보안 관련 문제

PRNG에 요구되는 몇 가지 고수준 사항

1) 역추적 저항성 ( backtracking resistance; 순방향 비밀성 ) : 이전에 생성된 비트들을 절대로 다시 생성할 수 없어야 한다. -> refresh, next 연산에서 내부 상태를 갱신할 때 수행되는 변환들이 비가역적이어야 한다.

2) 예측 저항성 ( prediction resistance; 역방향 비밀성 ) : 이후의 비트들을 예측하는 것이 불가능해야 한다. -> 공격자가 알지 못하고 추측하기 힘든 R 값으로 refresh를 호출해야한다.

2.4.4 암호학적 PRNG와 비암호학적 PRNG

비암호학 PRNG : 응용 프로그램에서 균등 분포 무작위 비트열을 산출하도록 설계된 것들, 안전하지 않다.

암호 PRNG : 잘 분포된 비트들을 산출하는 데 쓰이는 바탕 연산들의 강도에 신경 쓰기 때문에 예측 불가능.

하지만 여러 시스템에 쓰이는 것은 대부분 비암호 PRNG임. ex) 메르센 트위스터

- 메르센 트위스터 ( Mersenne Twister, MT )

: PHP, 파이썬, R, Ruby를 비롯한 여러 시스템에 쓰이는 비암호 PRNG이다.

: 통계적 편향이 없는 균등분포 무작위 비트열을 생성하지만 예측 가능하다 -> 내부 작동 방식을 살펴보면 죄다 xor 을 통해서만 상호작용하기 때문이다.

* 비트들의 XOR 결합을 선형결합라고 부른다. -> 선형결합은 예측이 가능하다. ( AND 결합은 비선형결합 )

2.5 실제로 쓰이는 PRNG들

데스크톱과 노트북, 네트워크 라우터나 센톱박스 같은 내장 시스템을 비롯한 대부분의 플랫폼은 운영체제 차원에서 암호학적 PRNG를 제공.

가장 널리 쓰이는 PRNG는

1) 여러 Unix 기반 시스템이 제공하는 것

2) Windows가 제공하는 것

3) 인텔 마이크로프로세서에 쓰이는 하드웨어 기반 PRNG 이다.

2.5.1 Unix 기반 시스템에서 무작위 비트 생성

장치파일 /dev/urandom 은 암호 PRNG를 제공하는 사용자 영역 인터페이스이다.

신뢰성 있는 무작위 비트들을 얻을 때 이 장치 파일을 생성한다.

리눅스가 사용하는 PRNG는 drivers/char/random.c 에 정의되어있고 SHA-1 해시 함수를 이용하여 원본 엔트로피 비트 -> 신뢰성 있는 의사난수 비트를 만듦.

/dev/urandom은 비차단 ( non-blocking ) 풀 , /dev/random은 차단 풀을 사용

** /dev/urandom 과 /dev/random은 무슨 차이일까?

/dev/random은 엔트로피 양을 추정하여 엔트로피 수준이 낮으면 비트들을 돌려주지 않는다. 이것이 좋은 방식인 것 같지만 신뢰성이 낮기로 악명이 높고, 공격자가 그것을 속여 넘길 수 있다고 한다.

그리고 추정된 엔트로피를 상당히 빨리 소진하여, 엔트로피가 높아지길 기다리느라 응용프로그램이 느려진다. (DOS 가능)

이래서 /dev/random은 별로라고 한다!!

2.5.2 Windows의 CryptGenRandom() 함수

예전 버전의 Windows 에서는 운영체제의 PRNG에 대한 사용자 영역 인터페이스로 CryptGenRandom() 함수가 쓰였다.

최근 버전의 Windows 에서는 Cryptography API: Next Generation(CNG) 라는 API의 BCryptGenRandom()함수가 쓰인다.

윈도우 PRNG는 커널 모드 드라이버 cng.sys(전에는 ksecdd.sys) 에서 엔트로피를 가져오는데 느슨하게 말하면 포르투나에 기초한다. (난수를 얻는 과정 꽤나 복잡)

CryptGenRandom() - 핸들을 이용하여 암복호화 문맥 얻어야함 -> 이 과정에서 잘못될 기회가 늘어남

BCryptGenRandom() - 핸들 없이 가능!

2.5.3 하드웨어 기반 PRNG: 인텔 마이크로프로세서의 RDRAND 명령

응용 프로그램은 RDRAND라는 어셈블리 명령을 통해 인텔의 이 PRNG에 접근.

운영체제와는 독립적인 인터페이스 제공, 이론상 소프트웨어 PRNG보다 빠름

2.6 문제 발생 요인들

다음 4가지의 예시를 보쟈!

2.6.1 불충분한 엔트로피원

- 넷스케이프 브라우저 SSL 구현은 128 비트 PRNG 종잣값을 계산할 때

global variable seed;
RNG_CREATEContext()
    (seconds, microseconds) = 하루 중 시간
    pid = 프로세스 id; ppid = 부모 프로세스 id; /* 각 15 비트 */
    a = mklcpr(microseconds);
    b = mklcpr(pid + seconds + (ppid << 12));
    seed = MD5(a, b);

mklcpr(x)
 return ((0xDEECE66D * x + 0x2BBB62DC) >> 1);

MD5()

이 의사코드에 따라 계산을 하는데

seconds 를 알아버리면 microseconds의 seconds/10^6 이므로 이 때 엔트로피가 log(10^6) 즉 약 20비트.. 여기서 큰 빵꾸가 나기 때문에 엔트로피가 128비트가 안된다.. 그래서 문제 발생함.

또한 b를 구하는 과정에서 ppid 를 왼쪽으로 12비트 shift 해서 3비트가 겹쳐버려서 여기서의 엔트로피 15(pid)+12(ppid) 이므로 3비트 빵꾸남 원래 15+15 여야 하는 데 그래서 문제 발생!!

2.6.2 시동 시점의 엔트로피 부족

시동(booting) 도중 아직 엔트로피가 충분히 수집되지 않은 시점에서 엔트로피를 계산하는 장치가 많다는 것을 조사를 통해 얻음 -> 엔트로피가 충분치 않다 보니 서로 다른 시스템의 PRNG들이 동일한 기본 엔트로피원으로부터 동일한 난수비트들을 생성 -> 동일 키 생성됨

prng.seed(종잣값)
p = prng.generate_random_prime()
q = prng.generate_random_prime()
n = p*q
/* 결과적으로 n이 같아지는 시스템 */

 

생성 도중에 추가적인 엔트로피 주입되는 키 생성 방안을 의사코드로 나타내면 아래와 같다.

prng.seed(종잣값)
p = prng.generate_random_prime()
prng.add_entropy()
q = prng.generate_random_prime()
n = p*q

이 시스템에서 p가 같다 하더라도 추가적인 엔트로피 주입으로 인해 n은 달라진다. 하지만 n과 n' 의 최대공약수를 계산하면 공통인 소인수인 p를 알아낼 수 있기에 문제가 생긴다. (문제 : 개인키를 알아낼 수 있음)

2.6.3 비암호 PRNG

앞서 말했듯이 비암호 PRNG는 안전하지 않다.

예를 들어 위키백과를 비롯한 여러 위키 사이트는 Media Wiki를 사용한다. Media Wiki는 무작위성을 활용해서 보안 토큰이나 임시 패스워드를 생성했으며 이때 mt_rand() 함수를 이용하여 생성했다. mt_rand()는 메르센 트위스터이다.. 예측 가능하단 소리! 그래서 안전하지 않다!!

2.6.4 무작위성은 강하지만 표본 추출에 문제가 있는 사례

Cryptocat 이라는 채팅 프로그램은 보안 통신을 제공하도록 설계되었다.

0~9 의 숫자들을 고르게 분포하려고 했는데 0부터 255까지의 모든 수를 10으로 나눈 나머지에서는 0~9가 고르게 분포되지 않는다. ( 1 byte = 8 bits 라서 0부터 255 )

그래서 이 개발자들은 다음과 같은 방법을 사용했다.

Cryptocat.random = function() {
    var x,o ='';
    while(o.length < 16) {
          x = state.getBytes(1);
          if(x[0] <=250)
              o += x[0] % 10;
     }
}

10의 특정 배수인 250을 조건으로 해서 그 미만의 수에서만 10으로 나눈 나머지를 취하고 그외는 버리는 방법을 사용했다. 근데 250을 10으로 나누면 0이라는 수가 한번 더 나오게 되서 균등하지 못하게 된다. 그래서 엔트로피가 낮아진다.