Menu

문서정보

2.3절. /dev/random 의 이용

Unix 에서는 좀더 범용적으로 사용할수 있는 방법을 제공한다. /dev/random 이라는 문자장치를 통한 랜덤값가져오기가 이 방법이다.

이 문자장치는 커널에서 제공하는데, int 형의 값을 이용해서 random seed 를 생성해내는 random 함수 와는 달리 다른 장치드라이버와 엔트로피풀안의 다른 소스 들로 부터 노이즈를 모으고 이러한 노이즈와 장치드라이버에 걸리는 인터럽트시간 간격등을 이용해서 난수를 생성시킨다.

간단히 생각해서 키보드, 마우스, 디스크 혹은 내부적으로 발생되는 다른 인터럽트등을 이용해서 난수를 발생시킨다고 보면 된다. 이들 인터럽트 값등은 예측하기가 매우 힘들기 때문에 근본적으로 random 함수를 이용하는것보다 매우 안전하게 랜덤값을 생성할수가 있다. 또한 난수의 범위를 매우 크게 잡을수 있기 때문에, 128bit 크기를 기본으로 사용하는 지금의 컴퓨팅 환경에 유용하게 사용할수 있다

실제 openssl 과 같은 라이브러리등은 암호화된 key의 생성을 위해서 /dev/random 을 사용한다. 다음은 128bit 크기의 난수를 생성하는 간단한 예제 프로그램이다.

예제 : dev_mem.c

#include <unistd.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>

int main()
{
    int i, fd;
    char key[16];
    if ((fd = open("/dev/random", O_RDONLY)) == -1)
    {
        perror("open error");
        exit(1);
    }
    if ((read(fd, key, 16)) == -1)
    {
        perror("read error");
        exit(1);
    }

    for (i = 0; i < 16; i++)
    {
        printf("%c", key[i]);
    }
}
			
위의 코드는 16 * 8(128)bit 크기를 가지는 랜덤값을 만들어낸다. 위프로그램을 실행시킨 결과값을 확인하기 좋게 만들기 위해서 mimecode 를 통해서 아래와 같이 출력해보았다.
[root@localhost c_source]# ./dev_mem | mimencode 
6qK3AlTHc0nUUETnoL5LRA==
			
mimencode 는 입력값을 base64 인코딩해서 그 결과를 출력하며, 보통 MIME 메시지를 첨부하기 위한 목적으로 사용되는 어플리케이션이다.

코드는 매우 간단하며, 실행시마다 서로 다른 랜덤값이 출력되는걸 확인할수 있을것이다. 또한 랜덤값의 크기 제한역시 매우 자유롭다. 위의 key 배열의 크기를 32 로 한다면 간단하게 256bit 크기를 가지는 함수를 생성할수 있다.


2.3.1절. 조용한 시스템에서의 /dev/random 문제점

/dev/random을 사용하는데 있어서 사소한(때에 따라서는 심각한) 문제가 하나 있는데, 장치의 노이즈를 수집해서 앤트로피 풀에 저장하고 이 값을 이용해서 랜덤값을 만들어 낸다는 특징 때문에 장치에 노이즈가 없을 때는 앤트로피 풀이 비어 버리고, 때문에 매우 오랜 시간동안 랜덤값이 발생하지 않을 수 있다는 점이다.

다음의 코드를 테스트 해보기 바란다.

#include <time.h>
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <fcntl.h>

#define MAX_RND_SIZE 32 

int random_init()
{
    int fd;
    fd = open("/dev/random", O_RDONLY);
    return fd;
}

int random_get(int fd, void *buf, size_t size)
{
    int i = 0;
    int n = 0;

    // 주석 1. 
    while( n < size)
    {
        n += read(fd, buf, size - n);
    }
    return n;
}

int random_clear(int fd)
{
    close(fd);
}
int main()
{
    int fd;
    int n;
    unsigned int value;

    fd = random_init();
    sleep(5);
    while(1)
    {
        n = random_get(fd, (void *)&value, 4);
        printf("%d %lu\n", n, value);
    }
    random_clear(fd);
}
				
당신의 시스템이 조용한 상태라고 가정한다면 처음 몇 개는 발생하지만 그 후에는 띄엄띄엄 발생 하는 것을 확인할 수 있을 것이다. 자 이제 키보드를 눌러 보거나. 마우스를 움직여 보거나 복사와 같은 파일 관련 작업을 해보기 바란다. 아마 랜덤값이 빠르게 발생하는 걸 확인 할 수 있을 것이다.

이러한 /dev/random의 특징 때문에 연속해서 랜덤한 값을 얻고자 할 때 문제가 발생할 수 있으니 이럴 경우 사용에 주의해야 한다.(물론 그리 흔한 경우가 아니긴 하지만)

만약 읽어 들이려는 크기만큼의 노이즈가 앤트로피 풀에 있지 않을 경우 요청한 크기보다 더 적은 값을 읽어 올 수도 있으므로 짧은 시간에 여러개의 랜덤값을 생성해야 할 경우 주석 1.에서 처럼 사이즈를 계산해줘야 할 필요성이 있다.

짧은 시간에 여러개의 랜덤값 생성은 인증값과 같은 중요한 부분에 사용된다고 보기는 힘들다. 이런 경우에는 그냥 random()을 이용하도록 하자.

커널 2.6에서는 /dev/random에 향상이 있다고 하니 한번 확인해 보도록 하자.


2.3.2절. 지원 OS 제한

/dev/random 문자장치를 이용해서 랜덤값을 얻어오는 방법은 매우 효율적이긴 하지만, 모든 OS가 이 문자장치를 지원하는건 아니다. 필자가 아는 바로는 Linux 의 경우 2.x 이상의 커널에서 지원하며 Sun os 의 경우 5.8 이상에서만 지원하는 걸로 알고 있다. Sun os 5.8 의경우에는 패치를 통해서 지원한다.

그럼으로 /dev/random 을 이용한 어플리케이션을 제작하고자 할때는 배포하는 OS에 대해서 신경을 써줘야 한다.


2.3.2.1절. Sun OS 에서의 /dev/random 생성

Sun의 경우 /dev/random 을 생성하기 위한 간단한 방법이 있다. egd 라는 Perl 모듈을 이용하는 방법인데, 방법인데 sunfreewarecpan.org 에서 얻을수 있다. 개인적으로 sunfreeware 에서 버젼에 맞는 egd 를 설치하는걸 추천한다.

sunfreeware 에 가보면 각 버젼별로 egd 모듈이 존재할것이다. 적당한 egd 를 다운받아서 설치하면 되는데, 패키지가 아닌 쏘쓰를 다운받아서 직접 설치하도록 한다. egd-0.x.tar.gz 를 다운받아서 압축을 푼다음에 다음과 같은 방식으로 컴파일후 설치하도록 한다.

[root@localhost egd]# perl Makefile.PL 
...
[root@localhost egd]# make 
...
[root@localhost egd]# make install
...
					
성공적으로 컴파일을 마쳤다면 egd.pl 이라는 펄 스크립트가 만들어지고 이걸 이용해서 /dev/random 을 생성할수 있다.
[root@locaohost egd]# egd.pl /dev/random
...
					
egd.pl 을 실행시키면 /dev/random 이 만들어지는데 ls 로 확인해 보면 문자장치 파일이 아닌 Unix Domain 소켓파일임을 알수 있다. 그럼으로 우리가 랜덤값을 얻어오기 위해서는 직접 소켓에 연결해서 /dev/random 소켓파일로 부터 값을 얻어와야 한다. egd.pl 은 perl 로된 Unix Domain 소켓 서버이다.

다음과 같은 방법을 통해서 랜덤값을 얻어올수 있다.

	
#include <unistd.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>

int main()
{
    int sockfd;
    int clilen;
    int value;
    char de[36];
    struct sockaddr_un clientaddr;

    sockfd = socket(AF_UNIX, SOCK_STREAM, 0);
    if (sockfd < 0)
    {
        perror("exit : ");
        exit(0);
    }

    clientaddr.sun_family = AF_UNIX;
    strcpy(clientaddr.sun_path, "/dev/random");
    clilen = sizeof(clientaddr);
    if (connect(sockfd, (struct sockaddr *)&clientaddr, clilen) < 0)
    {
        perror("connect error : ");
        exit(0);
    }
    printf("OK READ\n");

    while(1)
    {
        memset(de, 0x01, 4);
        write(sockfd, de, 4);
        read(sockfd, (void *)&value, sizeof(int));
        printf("%d\n", value);
        sleep(1);
    }

    close(sockfd);
    exit(0);
}
					

참고로 edg.pl 은 SHA(Secure Hash algorithm)을 사용한다. MD5 계열의 Hash 함수와 매우 유사하게 작동하며, SHS(secure hash standard) 에 정의되어 있다. MD5 보다 다소 느리지만 더 안전하다는 평가를 받고 있다.


3절. 결론

이상간단하게 랜덤값을 얻어오는 2가지 일반적인 방법에 대해서 알아보았다. /dev/random 의 경우 나중에 다루게될 ssl 프로그래밍에서도 쓰임으로 알아 놓으면 언젠가 유용하게 써먹을수 있을것이다.