Security Considerations
OSXDEV
목차 |
[편집] Paging에 관련된 보안 문제
페이징은 보안에 민감한 프로그래머들에게 오래전부터 중요한 문제였다. 암호화를 수행하는 프로그램을 작성할때, 문서의 작은 부분이라도 암호화되지 않은 내용이 저장공간에 들어있다면, 그것만으로도 암호 해독의 복잡도를 orders of magnitude로 감소시킨다.
실제로, hash, 암호화되지 않은 민감한 데이터, 그리고 인증 토큰과 같은 많은 타입의 데이터는 남용될 수 있는 가능성 때문에 디스크에 저장되어서는 안된다. 이러한 사실은 흥미로운 문제를 초래한다. user space에서는 이것을 처리할 좋은 방법이 없다(프로그램이 root로 실행되지 않는다면). 그러나, 커널 코드라면 페이지가 저장공간에 써지는 것을 막을 수 있다. 이러한 처리를 메모리를 “wiring down” 한다고 한다. 이것은 나중에 “Memory Mapping and Block Copying.”(링크)에서 더 자세히 다루겠다.
wired memory 사용의 주된 목적은 DMA-based I/O를 허용하기 위해서이다. 하드웨어 DMA 컨트롤러는 일반적으로 가상메모리 주소를 이해하지 못하기 때문에, I/O에 사용되는 정보는 반드시 물리적으로 메모리의 특정 지점에 있어야 하며, I/O 작업이 완료될때까지 절대로 옮겨져서는 안된다. 이러한 방식은 민감한 데이터가 저장공간에 써지는 것을 막는데도 사용될 수 있다.
wired memory는 절대로 페이지 아웃되지 않기 때문에(unwired될때까지는), 많은 양의 메모리를 wired 메모리로 만들어 버리는 것은, 특히 적은 양의 메모리를 갖고 있는 시스템에 엄청난 성능 문제를 발생시킨다. 이러한 이유로, 무분별하게 메모리를 wire down 하지 않도록 주의해야 한다, 그리고 오직 그렇게 할만한 이유가 있을때만 메모리를 wire down 시키도록 한다..
Mac OS X에서는, 메모리가 할당되는 순간에 wire down 될 수 있다. I/O Kit에서는, 명시적으로 IOMalloc 과 IOFree를 이용하여 wired memory를 할당할 수 있다. Mach에서는, kmem_alloc_wired(그리고 kmem_free)를 사용한다. 이것 역시 할당 후에 wire down 될 수 있다. wired memory에 대한 더 많은 정보는 “Memory Mapping and Block Copying.”(링크)에 나와있다.
[편집] 버퍼 오버플로우와 잘못된 입력
버퍼 오버플로우는 응용프로그램과 커널 프로그래밍 모두에서 자주 발생되는 버그중에 하나이다. 가장 흔한 원인으로는 C나 C++의 문자열을 끝내는데 사용되는 NULL 문자('\0')를 위한 공간을 빠뜨리고 메모리를 할당하는 것이다. 그러나, 고정된 크기의 입력 버퍼가 사용되고, 이 버퍼가 오버플로우 되지 않게 할 적절한 처리가 취해지지 않을 경우, 사용자의 입력이 버퍼 오버플로우를 유발할 수도 있다.
이 경우엔 확실하게 보호를 해주는 것이 최선이다. 고정 크기의 버퍼를 사용하지 말거나, 버퍼의 오버플로우를 유발하는 입력을 거절하거나 크기에 맞게 잘라주는 코드를 추가한다. 구현에 관한 자세한 사항은 여러분이 작성하는 코드의 형식에 달려있다.
예를 들면, 문자열을 처리하는데 크기에 맞춰 잘라주는 방식을 선택했다면, strcpy(3)을 사용하는 대신에 strlcpy(3)을 사용하여 복사할 데이터의 양을 제한할 수 있다. Mac OS X는 strlcpy(3), strlcat(3), strncmp(3), snprintf(3), and vsnprintf(3)을 포함하여 많은 문자열 처리 함수들의 길이 제한 버젼을 제공한다.
데이터를 크기에 맞게 자르는 방식이 적당하지 않다면, strlen(3)을 호출하여 입력받은 문자열의 길이를 얻은 다음, 최대 길이(버퍼크기-1, strlen은 NULL을 제외한 문자열의 길이를 리턴한다)를 초과하면 에러를 리턴한다..
다른 유형의 잘못된 입력 값은 처리하기 더 어려울 수가 있다. 일반적인 규칙으로, switch 구문은 입력받은 값의 타입의 범위에 해당하는 모든 값에대한 경우를 처리하지 않는다면, 반드시 default case를 정의해야 한다.
일반적으로 enum 타입을 선언할때 가능한 모든 값의 리스트를 선언하면 안전할거라고 착각하기가 쉽다. 일반적으로 enum은 내부적으로 char나 int형으로 구현된다. 간단히 파라메터를 int같은 것으로 정의한 다른 프로토타입을 사용하는 것으로, 부주의하거나 악의가있는 프로그래머가 리스트에 없는 값을 커널 함수에 넘기는게 쉽게 가능하다.
또다른 흔한 오해는 다른 함수에서 넘어온 포인터를 역참조(dereference)할 수 있다고 가정하는 것이다. 포인터를 역참조하기 전에 반드시 null 포인터인지 확인을 해야 한다. 다음과 같이 함수를 시작하는 것은
int do_something(bufptr *bp, int flags) {
char *token = bp->b_data;
악의가 있거나, 프로그래머의 잘못으로인해 null 버퍼 포인터가 넘어왔다는 것을 보장하는 가장 확실한 길이다. 유저 프로그램에서는, 이것은 성가신일이다. 파일 시스템에서, 이것은 재앙이다.
네트웍에서 입력을 받아오는 커널 코드의 경우 각별히 보안에 신경써야 한다. 패킷 크기에 대한 가정은 보안 문제의 흔한 원인이다. 항상 패킷이 너무 크지는 않나 확인하고 합당한 방법으로 다루도록 하자. 마찬가지로, 항상 패킷의 체크썸(checksums)을 검증하도록 하자. 이렇게 함으로써 패킷이 변경되었는지, 손상되었는지, 전송중에 잘렸는지 확인하기가 수월해진다. 네트웍으로 받은 데이터의 유효성이 중요한 문제가 된다면, 원격 인증(remote authentication), 서명(signing), 암호화(encryption)같은 방법을 이용한다. 이것들에 대한 내용은 “Remote Authentication”(링크) and “Key-based Authentication and Encryption.”(링크)에서 다루고 있다.
[편집] 사용자 인증(credentials)
이 챕터의 소개부분에서 설명했듯이, Mac OS X에는 두가지의 사용자 인증 방법이 있다. 사용자 레벨의 보안 모델(Keychain Manager와 Security Server)은 이 문서의 범위에서 벗어나므로 넘어가겠다. 커널 보안 모델은 커널 개발자들에게 더 큰 관심사이다, 그리고 사용자 레벨 모델보다 훨씬 직관적이다.
커널 보안 모델은 기본 사용자 인증(basic user credentials)과 ACL permissions의 두 가지 메커니즘에 기반하고 있다. 첫번째, 기본 사용자 인증은 요청한 프로세스의 현재 사용자와 그룹을 확인하기 위해 커널안에서 넘겨진다. 두번째, 접근 제어 목록(access control lists(ACLs))은 더 상세한 수준의 접근 제어를 제공한다.
인증을 사용하여 작업할때 한가지 기억해야할 중요한 내용은 그것들이 컨텍스트(context)가 아니라 프로세스라는 점이다. 프로세스는 콘솔 사용자로서 실행되고 있지 않을수도 있기때문에, 이것은 중요하다. 이것의 두가지 예가 ssh 세션이 시작될때의 프로세스(ssh가 startup 컨텍스트에서 실행하는 이후에)와 setuid 프로그램(하나의 로그인 컨텍스트에서 다른 유저로써 실행됨)이다.
이 주제에 관해서 알고있는 것은 아주 중요하다. 만약 사용자의 로그인 컨텍스트에서 setuid root GUI 응용프로그램과 통신한다면, 그리고 또다른 응용프로그램을 실행하거나 민감한 데이터를 읽어온다면, setuid 실행으로 인한 effective user ID의 권한이 아닌 원래 콘솔 사용자의 권한으로 그것들을 다루길 원할 것이다. 콘솔 사용자가 admin 그룹에 있지 않다면, setuid root로 실행되는 프로그램을 다룰때 이것은 특히 문제가 된다. 프로그램이 정상 적인 제어에서 벗어나게 되면 장래에 주요한 보안 구멍이 될 수 있다.
그러나 이것이 명확한 규칙이 아니다. 때로 실행중인 프로세스 인증이나 콘솔 유저의 인증을 사용할지 불분명하다. 그러한 경우에, helper 응용프로그램을 사용하여 다이얼로그 상자를 콘솔에 표시하여 콘솔 사용자로부터의 상호작용을 요구하는게 보통 적당한 방법이다. 이것이 불가능하다면, 사용자의 권한을 어림잡아 낮게 설정한다. 커널 코드가 인증되지 않은 사용자나 프로세스에게 무심코 서비스를 제공하는 것보다, 서비스가 실패하는 편이 대체적으로 더 낫다.
일반적으로 커널 공간의 코드보다 유저 공간의 응용프로그램으로부터 콘솔 사용자를 결정하기가 더 쉽다. 그러므로, 보통 이러한 검사는 유저 공간에서 하는게 좋다. 그러나 그게 불가능하다면, console_user변수(VFS 서브시스템에의해 유지됨)가 /dev/console(chown 시스템 콜의 약간의 코드에 의해 유지됨)의 마지막 소유자의 uid를 제공해 줄것이다. 이것은 분명히 이상적인 해결책은 아니다, 그러나 이것은 거의 맞는 콘솔 유저의 신원을 제공한다. 그러나 이것은 오직 "가장정확한 추측"이기 때문에, 오직 유저 공간에서 적절한 검사를 할 수 없을 때에만 이 방법을 사용해야 한다.
[편집] Basic User Credentials
커널에서 사용되는 기본 사용자 인증(Basic user credentials)은 struct ucred 타입의 변수에 저장된다. 이것들은 커널에서 일반적으로 퍼미션이 루트인지 아닌지가 중요한 특별한 부분에서 대부분 사용된다.
이 구조체는 4개의 필드가 있다:
- cr_ref — 레퍼런스 카운드(내부적으로 사용됨)
- cr_uid — 사용자 ID
- cr_ngroups — cr_groups안의 그룹의 개수
- cr_groups[NGROUPS] — 사용자가 속해있는 그룹의 목록
이 구조체는 사용되고 있는 중에 실수로 해제되어버리는 것을 방지하기 위해 내부적으로 레퍼런스 카운터를 갖고 있다. 이러한 이유로, 이 객체를 무분별하게 복사해서는 안된다. 대신에 crdup를 사용하여 사본을 만들거나, crcopy를 사용하여 사본을 만들고 (잠재적으로) 원본을 해제한다. crfree를 사용하여 생성한 모든 복사본을 해제하는 것을 명심하도록 한다. crget을 사용하여 새로운 ucred 구조체를 생성할 수 있다.
이 함수들의 원형은 다음과 같다:
- struct ucred *crdup(struct ucred *cr)
- struct ucred *crcopy(struct ucred *cr)
- struct ucred *crget(void)
- void crfree(struct ucred *cr)
[편집] Access Control Lists
접근 제어 목록(Access control lists)은 Mac OS X 10.4에서 새로 추가된 기능이다. 접근 제어 목록은 Mac OS X 커널의 파일 시스템 부분에서 주로 사용된다, 그리고 kauth API의 사용을 통해서 지원된다.
kauth API는 /System/Library/Frameworks/Kernel.framework/Headers/sys/kauth.h 헤더파일 안에 설명되어있다. 이 API는 아직도 발전중에 있기때문에 아직 상세한 문서가 제공되지 않는다.
[편집] 원격 인증(authentication)
컴퓨터에 원격으로 접속하고있는 누군가를 식별하는 것은 컴퓨터 보안에서 좀 더 어려운 문제이다. 가장 안전한 방법중의 하나는 공개 키 암호(public key cryptography)를 사용하는 것이다. 이것은 key 기반의 인증과 암호화에서 더 자세히 다룬다. 그러나, 보안의 정도가 다양한 사용 가능한 다른 많은 인증 방법이 있다.
다음을 포함하는 몇가지 다른 인증 방법이 있다:
- 무조건 신뢰(blind trust)
- IP-only authentication
- password (공유된 보안) authentication
- IP와 password authentication의 조합
- one-time pads (challenge-response)
- 시간에 기반한 인증(time-based authentication)
이것들 대부분이 명확해서, 추가적인 설명이 필요없을 것이다. 그러나 one-time pads와 시간에 기반한 인증(time-based authentication)은 보안 분야 밖의 사람들에겐 생소할 것이다, 그러므로 좀 더 상세히 살펴볼만한 가치가 있다.
[편집] One-Time Pads
"challenge-response" 짝(pairs) 개념에 기반하여, one-time pad (OTP) 인증은 첫번째 아이템으로 정렬된, 한 쌍의 숫자, 단어, 심볼, 또는 그밖의 어떤것들의 목록을 동일하게 갖고 있는 두개의 모임(party)을 필요로 한다. 원격 시스템에 접근을 시도하면, 원격 시스템은 사용자에게 challenge(역자 주: 일종의 문제같은 개념이다)를 가르쳐준다. 사용자는 첫번째 컬럼에서 challenge를 찾아서 해당하는 응답(response)을 보내준다. 이과정을 두개의 소프트웨어가 자동으로 처리할 수도 있다.
최고의 보안을 위해서, 같은 challenge는 절대로 2번이상 발행되서는 안된다. 이런 이유때문에, 그리고 이 시스템이 처음에 challenge-response 또는 한쌍의 CR을 포함하는 종이철(paper pad)로 구현되었기때문에, 이러한 시스템을 one-time pads라고 부른다.
OTP 인증의 한번의 특성은 누군가가 계속해서 다른 값으로 challenge에 응답하는 식으로 적절한 응답을 알아낼 수 없게 만든다. 기본적으로 이런 시스템을 깰 수 있는 유일한 방법은, 운좋게 맞추는 것을 제외하고, 사실상 challenge-response 리스트에서 일부분을 알고 있는 것이다.
이러한 이유로, one-time pads는 불안전한 통신 채널에서도 사용이 가능하다. 만약 누군가가 통신을 스눕(snoop)한다면, challenge-response 짝을 획득할 수 있다. 그러나, challenge가 다시 발행될 일은 없기때문에 그 정도는 이제 쓸모가 없다. (challenge는 반드시 유일해야 하기 때문에, 이것은 심지어 reponse를 얻기위한 잠재적인 가능성조차 줄여주지 않는다.)
[편집] 시간에 기반한 인증(authentication)
이것은 아마도 가장 이해가 안되는 인증 수단일것이다. 그러나 이것은 SecurID같은 기술로써 일반적으로 사용된다. 개념은 비교적 직관적이다. 적은 수의 매개변수(예를 들면 2개)를 취하고, 새로운 매개변수를 리턴하는 수학적인 함수 하나를 가지고 시작할 것이다. 이러한 함수의 좋은 예가 피보나치 수열을 생성하는 함수이다(임의의 값에서 시작하여 특정 비트의 값에서 끝내는 것이 가능한).
이 함수를 취하고, 세번째 파라메터로 t를 더한다. 이 값은 k초 단위로 시간을 표시한다. a와 b 2개의 시작(seed) 값으로 t에 대한 함수를 생성해내는 함수를 만든다. 다음과 같다.
다시 말해서, 매 k초 동안, 이전의 두 값과 방정식을 가지고 새로운 값을 계산해 낸다. 그리고 나서 가장 오래된 값을 버리고, 그것을 두번째로 오래된 값으로 바꾼다. 두번째로 오래된 값은 방금 생성한 값으로 바꾼다.
양쪽끝이 현재 시간과 원래의 두개의 수들에 대한 같은 의미를 갖게되는한, 가장 최근에 생성된 수를 계산해낼 수 있다. 그리고 이 수가 공유 보안으로써 사용된다. 물론 이러한 처리를 하는 코드를 작성한다면, 이 방정식의 closed form을 사용해야 한다. 피보나치 수는 추가적인 저장공간 없이 재귀적으로 O(2^(t/k))까지 증가한다. 이것은 t가 년도로 측정되고, k가 초단위로 측정된 작은 상수값일때 실용적이지 못하다.
이러한 체계의 보안은 생성(generator) 함수의 다양한 특성에 의존한다, 그리고 그러한 함수는 이 문서가 다루는 범위를 벗어난다. 더 많은 정보가 필요하다면, Bruce Schneier의 Applied Cryptography 같은 교재를 찾아 보기바란다.
[편집] 임시 파일
임시 파일은 보안 문제의 주요한 이유이다. 프로그램이 퍼미션을 제대로 설정하지 않는다면, 공격자들이 임의로 파일을 수정하거나 읽을 수 있는 수단을 제공하게된다. 그러한 수정으로 인한 보안 피해는 파일의 내용에 따라 다르다.
임시 파일은 커널 프로그래머들에게 별로 관심사가 못된다. 대부분의 커널 코드는 임시 파일을 사용하지 않기 때문이다. 실제로 커널코드는 일반적으로 파일을 전혀 사용하지 않는다. 그러나, 커널에서 프로그래밍하는 많은 사람들이 응용프로그램의 사용을 쉽게하기 위해 임시 파일을 사용하고 있다. 그렇기 때문에, 이 주제가 언급할 가치가 있다.
임시 파일을 사용할때 가장 공통적인 문제는 악의를 가진 제 3자가 임시 파일을 지우고, 더 느슨한 퍼미션의 다른 파일로 그것을 대체해 버리는 것이 가능하다는 것이다. 그 파일의 내용에 따라, 이것은 작은 불편 정도에서, 특별히 그 파일이 프로그램 사용자의 퍼미션으로 실행되는 쉘 스크립트를 포함한 경우에 비교적 큰 보안 구멍이 될 수도 있다.
[편집] /dev/mem 과 /dev/kmem
대부분의 UNIX 또는 UNIX류의 환경에서 보안 프로그래밍을 하는 사람들에게 한가지 특별히 꽤나 놀라운 것이 /dev/mem과 /dev/kmem의 존재이다. 이 장치 파일은 루트 유저가 임의적으로 물리적인 메모리와 커널 메모리의 내용에 접근할 수 있게한다. 여러분이 이것을 막을 방법은 전혀 없다. 커널의 관점에서, root는 전지전능한 존재이다. 이것이 여러분의 프로그램의 보안에 관심사항이라면, 다른 누군가에 의해 제어되는 시스템에서 사용되어야 할 것인지 고려하고 필요한 예방 조치를 취해야 할 것이다.
Mac OS X에서 /dev/mem을 통해 PCI 장치 메모리에 접근하는 장치 드라이버를 작성하는 것은 불가능하다. 그러한 드라이버의 지원이 필요하다면, 반드시 그 장치에 맞는 커널 stub 드라이버를 작성하여서 메모리 공간을 사용자 프로세스의 메모리 공간에 맵핑해야한다. 더 많은 정보가 필요하다면, I/O Kit Fundamentals의 사용자 클라이언트에 대해서 읽기 바란다.
[편집] Key 기반의 인증과 암호화
Key 기반의 인증과 암호화는 확실히 좀 더 안전한 인증과 암호화 수단이다, 그리고 다양한 형태로 존재한다. 가장 일반적인 형태는 공유 보안(shared secret)에 기반한 것이다. DES, 3DES(triple-DES), IDEA, twofish, and blowfish ciphers가 공유 보안에 기반한 암호화 정책의 예이다. 암호(Passwords)는 공유 보안에 기반한 인증 정책의 한 예이다.
대부분의 key 기반의 암호화의 기본이 되는 개념은, 데이터를 인코딩하는데 사용되는 임의 길이의 암호화 키를 여러분이 갖고 있고, 그 키가 반대 방법으로(어떤 경우에는 같은 방법으로 처리하기도 한다) 데이터를 디코딩하는데도 사용되는 것이다.
공유 보안의 문제점은 초기화 키 교환이 안전한 방법으로 반드시 이루어져야 한다는 것이다. 만약 key의 무결성이 전송중에 타협(compromised)된다면, 그 데이터는 무결성을 잃게된다. key가 한번에 생성되어 전송의 양쪽 끝단에 안전하게 놓여진다면 문제가 되지 않는다. 그러나, 많은 경우, 이것은 불가능하거나 실용적이지 못하다 왜냐하면 양 끝단이(물리적 장치거나 시스템 작업) 다른 사람이나 실체(entity)에 의해 제어되기 때문이다. 다행이도, zero-knowledge proofs라고 알려진 대안이 있다.
zero-knowledge proof의 개념은 다음과 같다. 임의의 key 값 x와 y가 생성되고, 다음과 같은 방법으로 수학적인 함수 ƒ와 관련된다.
ƒ(ƒ(a,k1),k2) = a
원래의 텍스트에 첫번째 key를 사용하여 잘 알려진 함수를 적용하면 암호화된 결과가 나온다. 두번째 key를 사용하여 같은 함수를 암호화된 텍스트에 적용하면 원래의 데이터가 나온다. 다음과 같이 key를 바꿔서 하는 것도 가능하다.
ƒ(ƒ(a,k2),k1) = a
만약 함수 ƒ가 올바르게 선택되었다면, y로부터 x를 뽑아내거나 그 반대도 엄청나게 어렵다. 이것은 인코딩할때 사용된 key에 대하여 암호화된 텍스트를 원래의 내용으로 쉽게 변환할 수 있는 함수가 없다는 것을 뜻한다.
이것의 예로 다음과 같은 수학적인 함수를 들 수 있다.
f(a,k)=((a*k) MOD 256) + ((a*k)/256)
a는 원래 텍스트의 바이트이고, k는 8비트 길이의 어떤 key이다. 함수 f는 다른 것으로 부터 하나의 키를 쉽게 결정할 수 있기 때문에, 이것은 엄청나게 약한 암호이다. 하지만 기본 개념을 설명하기에는 좋다.
k1을 8로 하고, k2를 32로 하자. a=73 이면, (a * 8)=584이다. 이것은 2바이트를 차지한다. 그러므로 높은 바이트의 비트를 낮은 바이트의 비트에 더한다. 그러면 74가 나온다. 32를 가지고 이 과정을 반복하면 2368이 나온다. 다시 높은 바이트의 비트를 낮은 바이트의 비트에 더하면 다시 73이 나온다.
매우 어려운 함수와 함께 실제로 사용되는 이러한 수학적인 개념은 public key(PK) 암호법(cryptography)이라 알려져 있다, 그리고 RSA와 DSA 암호화의 개념을 형성한다.
[편집] 공개(Public) Key의 약점
공개 key 암호화는 제대로 사용된다면 아주 강력하다. 그러나, 이것은 몇가지 타고난 약점이 있다. 이러한 약점에 대한 전체적인 설명은 이 문서의 범위를 넘는것이다. 그러나, 몇가지 흔한 함정에 빠지지 않기 위해서 상위 레벨에서 이러한 약점을 이해하는 것은 중요하다. 흔히 언급되는 몇가지 공개 key 암호화의 약점은 다음과 같다:
- 신뢰할 수 있는 모델 key 교환 모델
- 패턴 민감성
- 부족한 데이터 약점
신뢰할 수 있는 모델 key 교환 모델
공개 key 암호화의 약점으로 가장 흔히 논의되는 것은 초기화 key 교환 과정 그 자체이다. 초기화 교환과정중에 누군가가 어떻게 key를 가로챘다면, 그 key를 가지고 의도한 모임으로 가는 메세지를 가로챌 수 있다. 이것은 man-in-the-middle 공격이라 알려져있다.
ssh같은 서비스는, 대부분의 사람들이 수동으로 서버로부터 key를 다른 곳으로 복사하거나, 간단히 초기의 key 교환이 성공적일거라고 가정한다. 대부분의 경우에, 이것으로 충분하다.
그러나, 특별히 민감한 경우에는 이것으로 충분하지 못하다. 그러한 이유로, key 서명(signing)이라고 알려진 과정이 있다. key 서명에 대한 두가지 기본적인 모델이 있다: central authority 모델과 the web of trust 모델이다.
central authority 모델은 아주 직관적이다. 중앙의 보증 대행인이 주어진 key에 서명한다, 그리고 key의 주인이 요청한 사람이 맞을거라 믿는다고 말한다. 여러분이 그 권위(authority)를 신뢰한다면, 그 협회(association)에 의해, 여러분은 key를 신뢰할 수 있다.
web of trust 모델은 조금 다르다. 중앙의 권위(central authority) 대신에, 개인이 다른 개인에 속한 key에 서명한다. 누군가의 key에 서명함으로써, 여러분은 그 사람이 실제로 요청한 사람인지 신뢰할 수 있고, key가 실제로 그사람에게 속해있는것을 믿을 수 있다고 말한다. 그러한 신뢰를 결정하는데 여러분이 사용한 방법은 궁극적으로 다른사람들이 여러분의 서명이 유효한지 믿는데 영향을 미친다.
신뢰를 결정하는 여러 다양한 방법이 있다, 그러므로 많은 그룹은 다른 누군가의 key에 서명해야할지 말아야할지 결정하는 그들만의 규칙을 갖고 있다. 이 규칙은 key가 서명하는 신뢰 단계에 따라 key의 신뢰 단계를 결정하기 위한 것이다.
그러나, central authorities와 web of trust 모델 사이에 선을 긋는 것은 여러분이 생각하는 것처럼 완전히 구분되지 않는다. 많은 central authorities는 권위(authority)의 계층이다, 그리고 어떤 경우에는, 실제로 그것은 많은 권위에 둘러싸인 webs of trust이다. 마찬가지로, 많은 web of trust는 key를 위한 중앙의 저장소를 포함하고 있을 수도 있다. 그러한 저장공간이 key에 대한 어떤 증명(certification)을 제공하지는 않지만, 중앙화된 접근을 제공하는 것이다. 결국, centralized authorities는 web of trust의 일부분으로 key에 서명할 수 있다.
web of trust와 중앙화된 증명(certification)을 설명하는 많은 웹사이트가 있다. http://world.std.com/~cme/html/web.html 에 그러한 모델들에 대한 일반적인 설명이 나와있다.
패턴 민감성과 부족한 메세지
존재하는 공개 key 암호화 알고리즘들은 부분으로 임의인(random) 데이터를 암호화하는 작업을 잘 수행한다. 어떤 패턴을 갖고 있는 암호화되는 데이터에는 충분하지 못하다. 이러한 패턴을 통해 우연히 key에 대한 정보가 드러날 수 있다. 개개의 패턴은 암호화 정책에 달려있다. 무심코 그러한 패턴을 알아맞춘다고 개인(private) key를 알아 낼 수는 없다. 그러나, 주어진 메세지를 해석하는데 필요한 처리를 줄여줄 수 있다.
부족한 데이터 약점은 패턴 민감성과 밀접하게 관련되있다. 여러분이 암호화하는 정보가 하나의 숫자(예를들어 숫자 1)로 구성되있다면, 결국 수학적으로 공개 key에 밀접하게 관련된 숫자를 얻게되는 것이다. 오직 개인 key를 갖고 있는 누군가가 원래 값을 얻도록 확신하는 것이 목적이라면, 이것은 문제가 된다.
다시 말해서, 공개 key 암호화 정책은 일반적으로 모든 패턴을 동일하게 잘 암호화하지 않는다. 이러한 이유로 (그리고 공개 key 암호화가 단일 key 암호화보다 더 느린 경향이 있어서), 최종 사용자 데이터를 암호화하는데 대체적으로 거의 사용되지 않는다. 대신에 session key를 암호화 하는데 사용된다. session key는 그리고 나서 3DES, AES, blowfish 등등의 공유 보안 방식을 사용하여 실제 데이터를 암호화하는데 사용된다.
[편집] 공개 key를 사용한 메세지 교환
공개 key 암호화는 많은 방식으로 사용이 가능하다. 두개의 key가 모두 개인 key라면, 데이터를 대등하게 주고받는식으로 사용이 가능하다. 그러나 이러한 사용은 공유 보안 방식보다 덜 유용하다. 실제로, 이 챕터의 앞부분에서 설명한 것과 같은 이유로, 종종 약점이 생긴다. 공개 key 암호화는 key하나를 공개적으로 만들면 더욱 강력해진다.
철수와 순이가 코드화된 메세지를 보내기를 원한다고 가정하자. 순이는 철수에게 그녀의 공개 key를 제공한다. 그 key는 중간에 가로채졌다거나, 다른 누군가의 key로 바뀌지 않는다고 가정한다. 순이가 갖고 있는 개인 key를 통해서만 공개 key로 암호화된 데이터를 풀 수 있기 때문에, 철수는 이제 순이에게 안전하게 데이터를 전송할 수 있다.
철수는 이 방식을 이용하여 공유 보안을 전송할 수 있다. 철수와 순이는 이제 공유 보안 방식을 이용하여 다른 제 3자가 중간에 보안을 가로채지 않았다고 확신하고, 서로 통신할 수 있다. 다른 방법으로, 철수가 순이에게 그의 공개 key를 제공할 수 있다, 그리고 각자의 공개 key를 사용하여 양쪽 모두 데이터를 암호화 할 수 있다, 또는 더욱 일반적으로 그 공개 key를 사용하여 session key를 암호화하고 그 session key를 가지고 데이터를 암호화할 수 있다.
[편집] 공개 key를 사용한 신원 확인
공개 key 암호화는 신원 확인에도 사용될 수 있다. 영희는 영수의 권리를 주장하는 인터넷상의 누군가가 진짜 영수가 맞는지 알고 싶다. 몇달전에 영수는 영희에게 그의 공개 key를 플로피 디스크에 담아 주었다. 그러므로, 영희는 이미 영수의 공개 key를 갖고 있기때문에(그리고 그 key의 출처를 신뢰할 수 있다), 그녀는 쉽게 영수의 신원을 확인할 수 있다.
이것을 위해서, 영희는 메세지를 전송하여 영수에 그것을 암호화해달라고 요청한다. 영수는 그의 개인 key를 사용하여 그것을 암호화한다. 영희는 그런 다음 영수의 공개 키를 사용하여 암호화된 내용을 해석한다. 결과로 나온 텍스트의 내용이 서로 일치한다면, 누군가 영수의 개인 key를 갖고 있지 않은 한은, 상대방은 영수임을 확신할 수 있다.
[편집] 공개 Keys를 사용한 데이터 무결성(integrity) 검사
마지막으로, 공개 key 암호화는 서명에 사용될 수 있다. 선영은 '외계어 사용 반대모임'이라 불리는 비밀 단체의 모임을 맡고 있다. 세영은 클럽의 회원이고, 단체의 친한 회원인 수영에게서 다음번 모임의 주의사항이 담긴 TIFF 파일 하나를 얻었다. 그러나 세영은 그 주의사항이 단체에 침투하려는 지영에게서 온건 아닌가 걱정하고 있다.
그러나 선영은 한발짝 더 나아가, 원본 메세지의 checksum을 얻고, 그녀의 개인 key를 사용하여 암호화하였다, 그리고 암호화된 checksum을 첨부물로 전송하였다. 세영은 선영의 공개키를 사용하여 checksum을 해독하고, 그 checksum이 실제 문서의 checksum과 일치하지 않는다는 것을 알아냈다. 그녀는 현명하게 그 모임을 가지 않았다. 그러나 주영은 메세지의 서명(signature)을 확인하는 것을 잊어버려서, 거기에 속아넘어가 자신이 '외계어 사용 반대모임'의 회원이라는 사실을 들켜버렸다.
이 이야기의 교훈이 무엇인가? TIFF를 공유하는 괴짜를 조심해야 한다. 즉, 어떤 데이터의 보안이 중요하고 두 응용프로그램, 컴퓨터, 사람 등등의 사이에서 통신하는 안전한 수단이 없다면, 서명(signatures), key, 그 밖의 어떤 다른 방법을 사용하여 통신의 신빙성을 검증해야 한다. 이렇게 함으로써 여러분의 정체와 여러분의 데이터를 지킬 수 있다.
[편집] 암호화 요약
초기 key 교환이 안전한 방법으로 이루어진다면, 암호화는 데이터의 안전성을 지킬 수 있는 강력한 기술이다. 그렇게 하기 위한 한가지 수단으로 공개 key를 잘 알려진(그리고 신뢰할 수 있는) 장소에 저장하는 것이 있다. 이것은 한방향의 암호화 통신을 허용하고, 나중에 두방향 암호화 통신을 위한 공유 보안의 전송에 사용될 수 있다.
암호화를 데이터 보호 뿐만 아니라, checksum을 암호화함으로써 데이터의 신빙성을 검증하는데 사용할 수도 있다. 클라이언트가 알맞은 암호화키를 갖고 있는지의 검증 수단으로써 어떤 임의의 데이터를 암호화하기를 요구함으로써, 클라이언트의 신원을 확인할 수도 있다.
그러나 암호화는 컴퓨터 보안에서 최후의 단어가 아니다. 신뢰할 수 있는 key 교환의 방법에 의존하기 때문에, 통신이 중간에 가로채질 수 있고, 변경될 수 있는 환경에서 완전한 보안을 달성하기 위해서 추가적인 하부 구조를 필요로 한다.
[편집] 콘솔 디버깅
전통적인 UNIX와 UNIX류의 시스템에서 콘솔은 루트에 의해 소유된다. 오직 루트만이 콘솔 메세지를 볼 수 있다. 이러한 이유로, 커널에서의 print 구문은 비교적 안전하다.
Mac OS X에서는, 어떤 사용자든지 Console 애플리케이션을 실행할 수 있다. 이것은 다른 UNIX류의 시스템과 주요한 차이를 보여준다. 원래 커널 디버깅 구문에 민감한 정보를 포함시키는 것이 절대 좋은 생각이 아니지만, 더욱이 Mac OS X에서는 그렇게 하지 말아야 한다. 콘솔에 출력되는 어떤 정보도 시스템의 어떤 사용자에게도 읽혀질 수 있다는 것을 가정해야 한다(콘솔이 사용자가 볼수있는 윈도우의 형태로 가상화되었기 때문이다).
디스크나 메모리내의 위치같은 민감한 데이터를 포함한 어떤 정보를 출력하는 것은 보안 구멍을 노출시키지만, 가벼운 수준이다. 그러므로 여러분은 여러분의 코드를 적절히 작성해야 한다. 사용자가 어딘가에서 디버깅 플래그를 설정한 경우에만 그러한 정보가 출력되도록 한다면, 분명히 신경이 덜 쓰이지만. 일반적인 사용에 있어서 잠재적으로 내부 정보를 콘솔에 출력하는 것은 왠만해선 안하는게 좋다.
물론, 난수 발생기에 넘겨지는 시작(seed) 값 같은, 암호 해쉬나 암호화 키를 생성하는데 사용되는 정보를 부주의하게 화면에 출력하지 않도록 주의해야 한다.
부득이하게 이것이 콘솔에 출력하는 것을 피해야 할 정보의 완전한 목록이 아니다. 여러분의 판단 기준에 의해 어떤 정보가 제 3자에게 보여진다면 가치가 있을런지 결정을 하고, 이것이 콘솔에 출력되는 것이 적당할지 결정하도록 하자.
[편집] 코드 넘기기
유저 공간으로 부터 실행가능한 코드를 커널로 넘기는 많은 방법이 있다. 이 섹션에서, 실행가능한 코드는 컴파일된 목적 코드에 국한되지 않는다. 그것은 커널로 넘겨져서 제어의 흐름에 영향을 미치는 어떤 명령도 포함할 수 있다. 넘겨지는 실행가능한 코드의 예로 많은 방화벽 디자인에서 필터링 코드 업로드같은 간단한 규칙부터 SCSI 카드를 위한 바이트코드 업로드 까지 있다.
코드가 사용자 영역에서 실행가능하다면, 코드를 커널에 넣는 것을 생각해서도 안된다. 그러나 다른 합리적인 해결책이 없는 경우라면, 어떤 형태의 실행가능한 코드를 커널로 넘기는게 필요할 것이다. 이 섹션에서는 코드를 커널에 집어넣는 것의 보안에 관련된 세부 사항과 일관된 작동을 보장하는데 필요한 검증 단계를 설명할 것이다.
다음은 잠재적인 보안 구멍을 최소화 할 수 있는 지침이다:
- 원시(raw) 목적 코드를 사용하지 마라.
유저 공간에서 넘어온 코드를 직접 실행하는 것은 매우 위험하다. 이런 종류의 문제에는 인터프리트 언어가 오직 적합한 해결책이다, 그리고 심지어 이것은 어려움 투성이다. 전통적인 머신 코드는 보안 요구사항을 확신할 충분한 검사를 수행할수가 없다. - 범위(Bounds) 검사.
커널이기 때문에, 올라가는 코드가 임의로 메모리에 접근하고 직접 하드웨어에 접근하지 않는지 확인할 필요가 있다. 여러분은 보통 이것을 바이트코드가 작동하는 데이터 요소에 대한 접근을 제한하는, 언어(language) 자체의 기능으로 만들어야 할 것이다. - 종료 검사.
정말 아주 약간의 예외적으로, 선택된 언어가 종료하는지를 검증할 수 있는 코드에 한정되어야 한다, 그리고 여러분은 적절하게 검증해야 한다. 여러분의 드라이버가 빡빡하게 도는 루프에 빠져버린다면, 그것의 작업이 불가능할 것이다, 그리고 프로세스의 전체적인 시스템 성능에 영향을 미칠것이다. (무한) 루프를 허용하지 않는 언어(예로, for는 허용하나 while이나 goto는 허용하지 않음)는 종료를 확신할 수 있는 한가지 방법이 될 수 있다. - 유효성 검사.
여러분의 바이트코드 인터프리터는 잠재적인 잘못된 작동을 검사하고 올라온 코드에 대해 적절한 피해 처리를 해야할 책임이 있을 수도 있다. 예로, 올라온 코드가 수학 작업을 하도록 허용된다면, 0으로 나눠지는 에러를 처리할 적절한 보호책이 있어야 한다. - 정상(Sanity) 검사.
여러분은 가능하다면 출력이 적절한 것인지 검증해야 한다. 출력이 올바른지 검증하는 것이 언제나 가능하지는 않다, 그러나 터무니없이 잘못된 출력을 막을 수 있는 규칙을 만드는 것은 일반적으로 가능하다.
예를 들면, 네트워크 필터 규칙은 패킷과 유사한 어떤 것을 출력할 것이다. 만약 checksum이 잘못되었다거나, 다른 정보가 빠져있다거나 잘못되었다면, 올라온 코드는 명백히 잘못된 것이다, 그리고 적절한 처리가 취해져야 한다. Mac OS X가 나쁜 네트웍 트래픽을 보내는 것은 아주 적절하지 않은 일이다.
일반적으로, 언어 집합이 더 제한적일수록, 보안의 위험성은 감소한다. 예로, 간단한 네트워크 라우팅 정책을 인터프리트하는 것은 패킷 재작성 규칙을 인터프리트하는 것보다 보안에 덜 문제가 되고, 이것은 커널에서 Java 바이트코드를 실행하는 것보다 덜 문제가 된다. 다른 것들 처럼, 여러분은 신중하게 잠재적인 이익과 잠재적인 결점을 비교 검토하고, 최고의 결정을 내려야 할 것이다.




