이전 포스트에서 rand() 함수의 취약점을 설명하였다.


rand() 함수를 보완한 랜덤 함수가 CryptGenRandom() 함수이다.


CryptGenRandom 함수는 crpytographic service provider(CSP)를 이용한 랜덤 함수로 암호학적으로 랜덤하게 난수값을 생성한다. 다만, rand() 함수에 비해 매우 느리다는 단점이 있다.


사용 방법은 아래와 같다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
#include "stdafx.h"
#include <Windows.h>
#include <STDIO.H>
#include <Wincrypt.h>
 
#define MAX_RANDOM_NUM 100
 
int main(int argc, char* argv[])
{
    //--------------------------------------------------------------------
    // 변수를 선언하고 초기화 합니다.
    HCRYPTPROV   hCryptProv;
    BYTE         pbData;
 
    //-------------------------------------------------------------------
    // 암호 제공자의 컨텍스트 핸들을 얻습니다.
    if (CryptAcquireContext(&hCryptProv, NULLNULL, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT))
    {
        printf("New CryptAcquireContext succeeded. \n");
    }
    else
    {
        printf("Error 0x%x during CryptAcquireContext!\n", GetLastError());
        goto ERR;
    }
 
    //--------------------------------------------------------------------
    // BYTE 범위내에서 난수를 생성합니다.
    if(CryptGenRandom(hCryptProv, 1&pbData)) 
    {
        printf("Random number is: %d.\n", ((int)pbData) * MAX_RANDOM_NUM / 255);
    }
    else
    {
        printf("Error 0x%x during CryptGenRandom.\n", GetLastError());
        goto ERR;
    }
 
ERR:
    //-------------------------------------------------------------------
    // 컨텍스트 핸들을 해제합니다.
    if(hCryptProv)
    {
        if (!CryptReleaseContext(hCryptProv, 0))
        {
            printf("Failed CryptReleaseContext\n");
        }
    }
 
    return 0;
}
cs



CryptGetRandom() 함수는 현재 deprecated 되었다.

Cryptography Next Generation APIs를 사용해라.


<본 글은 win32 api를 바탕으로 작성되었습니다.>


개발을 하다 보면 랜덤 값을 생성해야 하는 경우가 있다. 이때 자주 사용되는 함수가 rand() 함수이다.



* rand() 함수 설명


C에서 rand() 함수는 랜덤 값을 생성하는 함수이다.

rand() 함수 사용법은 아래와 같다.


1
2
3
4
5
6
int i;
unsigned int nSeed = 1000;
 
srand(nSeed);
for( i = 0; i < 10; i++ )
    printf("%d ", rand() );
cs


seed 값을 설정하고 rand() 함수를 호출하면 0~0x7fff 사이의 값이 랜덤으로 리턴된다.

srand() 함수에 seed 값을 주면 전달 된 seed 값을 기준으로 정해진 알고리즘에 따라 0~0x7fff 사이의 랜덤 값 리스트를 생성하게 된다.

그 후 rand() 함수를 호출하면 랜덤 값 리스트에서 값을 순서대로 하나씩 꺼내 리턴한다.



* rand() 함수의 한계


결론부터 말하자면 rand() 함수는 보안에 취약하므로 사용하지 마라.

위에서 설명했듯이 rand() 함수는 srand() 함수를 통해 생성된 리스트에서 값을 하나씩 꺼내게 되고,

srand() 함수는 전달 된 seed 값을 기준으로 정해진 알고리즘에 따라 리스트를 생성한다.

즉, seed 값을 알게 되면 rand() 함수로부터 리턴 될 값을 예측할 수 있게 된다.


아래 코드를 보자.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
void rand_test_1()
{
    int i;
    unsigned int nSeed = 1000;
 
    srand(nSeed);
    for( i = 0; i < 10; i++ )
        printf("%d ", rand() );
 
    printf("\n");
}
 
int main(int argc, char* argv[])
{
    rand_test_1();
    Sleep(1000);
    rand_test_1();
    Sleep(1000);
    rand_test_1();
 
    return 0;
}
cs


결과는 다음과 같다.

1
2
3
4
3304 8221 26849 14038 1509 6367 7856 21362 6968 10160
3304 8221 26849 14038 1509 6367 7856 21362 6968 10160
3304 8221 26849 14038 1509 6367 7856 21362 6968 10160
Press any key to continue
cs


seed 값을 알게 되면 그 다음 생성되는 랜덤 값은 항상 같게 되고 이를 유추할 수 있게 된다.

그래서 seed 값을 추측할 수 없게 하기 위해 seed를 현재 시간으로 하기도 한다.


아래 코드는 time() 함수를 사용하여 seed 값에 현재시간 정보를 넣은 코드이다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
void rand_test_2()
{
    int i;
    unsigned int nSeed = (unsigned)time( NULL );
 
    srand(nSeed);
    for( i = 0; i < 10; i++ )
        printf("%d ", rand() );
 
    printf("\n");
}
 
int main(int argc, char* argv[])
{
    rand_test_2();
    Sleep(1000);
    rand_test_2();
    Sleep(1000);
    rand_test_2();
 
    return 0;
}
cs


결과는 아래와 같다.

1
2
3
4
22734 32447 12644 11773 13510 15387 24256 27097 22692 24687
25993 11452 15295 8016 18303 2103 17522 22028 18955 16531
29304 31360 8861 28825 24290 18879 27819 2892 17260 8311
Press any key to continue
cs


이렇게 하면 seed 값을 유추하기 어려우나 이는 어려운 것일 뿐 불가능한 것은 아니다.


암호학적으로 랜덤한 값을 만들기 위해 Unix 계열에서는 '/dev/random' 또는 '/dev/urandom'를,

Windows 계열에서는 'CryptGenRandom()' 함수를 사용하는 것이 좋다. (CryptGetRandom() 함수는 deprecated 되었다. Cryptography Next Generation APIs를 사용해라)

'프로그래밍 > C' 카테고리의 다른 글

[C/C++] CryptGenRandom()를 이용한 랜덤값 출력  (0) 2018.02.09


1
2
3
4
5
6
7
8
9
>>> def f (x, y=[]) :
    y.append(x)
    print(y)
>>> def test () :
    f('a')
    f('a')
    f('a')
>>> test()

cs


9번째 라인의 출력 결과는 어떻게 될까?

위 코드의 결과는 아래와 같다.


1
2
3
4
>>> test()
['a']
['a''a']
['a''a''a']
cs


나는 당연히 ['a']가 세 번 출력될 것이라 예상했는데 이전 값들이 계속 남아 출력되어 적잖이 당황했다.

정황으로 보아 f 함수의 y 변수가 지워지지 않고 계속 남아있는 것으로 보이는데 이해가 되지 않았다.

y 변수는 분명 지역변수로 매번 생성 될텐데 어째서 지워지지 않았을까.


이러한 내용과 관련하여 검색을 해보고 아래와 같은 사실을 알게 되었다.

Default Parameter Values in Python 참고


python에서 함수 인자의 기본값으로 "mutable" 성격의 오브젝트가 선언되고 그 기본값을 통해 변수가 생성될 경우 해당 변수는 static 변수의 성격을 갖게 된다.


일부 사이트에서는 인자의 기본값이 mutable 이면 global 변수가 된다고 하는데,

변수의 life time은 global 이지만 scope이 함수 내부여서 global 변수 보다는 static 변수로 보는 것이 맞을 것 같다.


이러한 이유 때문에 동일한 함수(위 코드에서 f 함수)를 계속 호출하면 매번 y 리스트가 새로 생성되는 것이 아니라, 이전에 호출 되었던 y 변수를 다시 불러와 다시 사용하게 되는 것이다.


리스트, 딕셔너리 뿐만 아니라 클래스와 같은 오브젝트들도 함수 인자의 기본 값으로 사용하게 될 경우 위의 내용과 동일하게 동작한다.


이러한 문제를 해결하기 위한 방법은 여러가지가 있을 것인데 아래 코드와 같이 바꿔서 사용할 수도 있다.


1
2
3
4
5
6
>>> def f (x, y=None) :
    if y is None:
        y=[]
    y.append(x)
    print(y)
 

cs


삽질을 피하기 위해 인자의 기본 값 설정 시에는 가능하면 "mutable" 한 오브젝트는 사용하지 말자.

+ Recent posts