메뉴 바로가기 검색 및 카테고리 바로가기 본문 바로가기

한빛출판네트워크

한빛랩스 - 지식에 가능성을 머지하다 / 강의 콘텐츠 무료로 수강하시고 피드백을 남겨주세요. ▶︎

IT/모바일

.NET의 암호화 API 사용하기(1)

한빛미디어

|

2007-06-19

|

by HANBIT

13,504

제공 : 한빛 네트워크
저자 : Wei-Meng Lee
역자 : 이대엽
원문 : Using the Cryptography APIs in .NET

.NET 프레임워크에는 여러분의 .NET 애플리케이션에 보안 서비스를 통합할 수 있도록 해주는 여러 가지 암호화 서비스가 포함되어 있다. 이러한 라이브러리들은 System.Security.Cryptography 네임스페이스에 있으며, 데이터의 암호화 및 복호화 함수들뿐만 아니라 이를 테면 해싱(hashing) 및 난수 발생(random-number generation)과 같은 다른 연산에 대한 갖가지 함수들도 제공해 준다. 이 기사에서는 여러분의 .NET 애플리케이션을 좀 더 안전하게 만들어주는, 몇 가지 일반적으로 사용되는 보안 API의 사용법에 대해 알아보도록 하겠다.

해싱(Hashing)

첫 번째로 여러분이 실행해 볼 가장 일반적인 보안 함수는 바로 해싱이다. 사용자가 여러분의 애플리케이션을 사용할 수 있기 전에 그 사용자를 인증하는 함수를 만들어야 하는 상황을 생각해 보자. 여러분은 사용자가 로그인 정보를 제공하도록 할 필요가 있을 수도 있는데, 그러한 정보에는 가장 일반적으로 사용자의 이름과 비밀번호가 포함된다. 이러한 로그인 정보는 데이터베이스에 저장되어 있을 필요가 있다. 그리고 일반적으로 개발자가 사용자의 비밀번호를 그대로 데이터베이스에 저장하지는 않는다. 이는 보안상 매우 위험한데, 왜냐하면 사용자 데이터베이스를 엿볼 기회를 가진 해커가 사용자의 비밀번호를 획득할 수도 있기 때문이다. 이에 대한 좀 더 나은 접근법은 사용자의 비밀번호를 그대로 저장하는 것이 아니라 사용자 비밀번호에 대한 해시값(hash values)을 저장하는 것이다. 해싱 알고리즘은 다음의 특징들을 가진다:
  • 임의의 길이를 가진 문자열을 고정된 길이의, 해시 값(hash value)이라 알려진 다소 작은 숫자의 이진 값으로 맵핑한다.
  • 한 문자열에 대한 해시값은 유일하며 원래의 문자열에 가해진 자그마한 변경사항은 전혀 다른 해시 값을 만들어낸다.
  • 두 개의 서로 다른 문자열이 동일한 해시값을 만들어낼 가능성은 극히 희박하다.
  • 동일한 해시값을 이용하여 원래의 문자열을 알아내는 것은 불가능하다.
사용자의 비밀번호를 그대로 데이터베이스에 저장하는 것이 아니라 여러분은 그것의 해시 값을 저장해야 한다. 사용자가 여러분의 애플리케이션에 로그인할 때 입력하는 비밀번호는 데이터베이스 안에 저장되어 있는 해시값과 비교된다. 이러한 방식으로 해커가 실제로 사용자 데이터베이스를 훔쳤을지라도 실제 비밀번호는 노출되지 않는다. 한 가지 사용자 비밀번호의 해시 값을 저장하는 것의 단점은 사용자가 자신의 비밀번호를 잊어버렸을지라도 그것을 알아낼 수 있는 방법은 없다는 것이다. 이러한 경우 여러분은 그 사용자에 대한 새로운 비밀번호를 만들고 사용자가 그 비밀번호를 즉시 변경하도록 요청해야 할 필요가 있다. 그러나 이 정도의 불편함은 여러분의 애플리케이션의 보안을 강화하기 위해 지불해야 할 비용에 비하면 작은 부분에 지나지 않는다. .NET에서 사용할 수 있는 해싱 알고리즘은 여러 가지가 있지만 가장 널리 사용되는 것은 SHA1과 MD5 구현이다. 그것들을 .NET에서 어떻게 사용하는지 알아보도록 하자.

비주얼 스튜디오 2005를 사용하여 비주얼 베이직을 사용하는 콘솔 애플리케이션 프로젝트를 하나 만들고, 다음의 네임스페이스를 임포트(import)한다:
Imports System.Text.Encoding
Imports System.Security.Cryptography
Imports System.IO

Define the following subroutine:
다음의 서브루틴을 정의한다:
    Private Sub Hashing_SHA1()
        "--- 사용자에게 비밀번호를 입력하도록 요청 ---
        Console.Write("Please enter a password: ")
        Dim password As String = Console.ReadLine()

        "---비밀번호를 해싱함 ---
        Dim data() As Byte = ASCII.GetBytes(password)
        Dim passwordHash() As Byte
        Dim sha As New SHA1CryptoServiceProvider()
        passwordHash = sha.ComputeHash(data)

        "--- 사용자에게 동일한 비밀번호를 한번 더 입력하도록 요청 ---
	  Console.Write("Please enter password again: ")
        password = Console.ReadLine()

        "--- 두 번째 비밀번호를 해싱한 다음 첫 번째 것과 비교 ---
        data = System.Text.Encoding.ASCII.GetBytes(password)

        If ASCII.GetString(passwordHash) = _
           ASCII.GetString(sha.ComputeHash(data)) Then
            Console.WriteLine("Same password")
        Else
            Console.WriteLine("Incorrect password")
        End If
    End Sub
이 서브루틴에서 여러분은 먼저 사용자에게 비밀번호를 입력해 달라고 요청하고 난 다음 SHA1 구현을 사용하여 입력된 비밀번호를 해싱한다. 그런 다음 사용자에게 동일한 비밀번호를 다시 한번 입력해 달라고 요청한다. 두 번째로 입력된 비밀번호가 첫 번째 것과 일치하는지 확인하기 위해 두 번째로 입력된 비밀번호를 해싱한 다음 두 해시값을 서로 비교한다. SHA1 구현에서는 생성되는 해시 값의 길이는 160비트이다(passwordHash 바이트 배열은 20개의 멤버[8비트ⅹ20 = 160비트]를 가짐). 위 예제에서는 해시 값을 문자열로 변환한 다음 비교를 하였는데, 그것들을 Base64 인코딩으로 변환한 다음 비교할 수도 있다. 아니면 두 해시 값의 바이트 배열을 이용하여 바이트 대 바이트로 비교할 수도 있다. 한 바이트라도 서로 일치하지 않을 경우 두 해시 값이 서로 동일하지 않다고 결론 내릴 수 있다.

서브루틴을 테스트하려면 단순히 Main()서브루틴에서 Hashing_SHA1()을 호출하기만 하면 된다:
    Sub Main()
        Hashing_SHA1()
        Console.Read()
    End Sub
[그림 1]은 서브루틴을 실행한 결과를 보여준다.


[그림 1] 인증에 해싱 사용하기

다음의 서브루틴에서 볼 수 있는 것과 같이 해싱을 수행하는데 MD5 구현을 사용할 수도 있다:
    Private Sub Hashing_MD5()
        "--- 사용자에게 비밀번호를 입력해 달라고 요청 ---
        Console.Write("Please enter a password: ")
        Dim password As String = Console.ReadLine()

        "--- 비밀번호를 해싱 ---
        Dim data() As Byte = ASCII.GetBytes(password)
        Dim passwordHash() As Byte
        Dim md5 As New MD5CryptoServiceProvider()
        passwordHash = md5.ComputeHash(data)

        "--- 사용자에게 다시 한번 동일한 비밀번호를 입력해 달라고 요청 ---
        Console.Write("Please enter password again: ")
        password = Console.ReadLine()

        "--- 두 번째로 입력된 비밀번호를 해싱한 다음 첫 번째 것과 비교 ---
        data = ASCII.GetBytes(password)
        If ASCII.GetString(passwordHash) = _
           ASCII.GetString(md5.ComputeHash(data)) Then
            Console.WriteLine("Same password")
        Else
            Console.WriteLine("Incorrect password")
        End If
    End Sub
두 방식간의 가장 큰 차이점은 MD5로 해싱된 해시값의 길이는 128비트라는 것이다.

Salted Hash

해싱을 이용하여 여러분은 사용자 비밀번호의 해시 값을 데이터베이스에 간단히 저장할 수 있다.그러나 만약 두 사용자가 동일한 비밀번호를 사용하게 되면 이러한 두 비밀번호의 해시값들은 똑같을 것이다. 해커가 두 해시 값이 똑같다는 것을 알게 된다고 상상해 보라. 그렇게 되면 해커가 두 비밀번호가 서로 동일하다는 것을 추측하는 것은 어렵지 않을 것이다. 가령 사용자들은 자신의 이름(혹은 생년월일이나 사전에서 쉽게 찾아 볼 수 있는 단어)을 비밀번호로 사용하길 좋아한다. 그래서 종종 해커들은 사용자의 비밀번호를 정확하게 추측하기 위해 사전 공격(dictionary attack) 기법을 이용하기도 한다. 이러한 사전 공격의 기회를 줄이기 위해 여러분은 해싱 과정에 “salt(소금)”을 첨가하여 두 개의 똑 같은 비밀번호가 동일한 해시 값을 생성하지 못하도록 할 수 있다. 예를 들어 사용자의 비밀번호를 해싱하지 않고 비밀번호와 다른 정보, 즉 전자메일 주소, 생년월일, 성, 이름 등과 같은 것을 함께 해싱할 수 있다. 이러한 착안은 각 사용자가 유일한 비밀번호 해시 값을 가질 것을 보장해 줄 것이다. 비록 사용자 정보를 해싱 과정상에 첨가하는 “salt”로 이용한다는 착안은 좋은 생각이긴 하지만, 해커가 추측하기에도 상당히 용이하다. 좀 더 나은 접근법은 “salt”로 사용할 숫자를 먼저 무작위로 생성한 다음 그것을 사용자의 비밀번호와 함께 해싱하는 것일 것이다.

다음의 Salted_Hashing_SHA1() 서브루틴은 무작위로 생성되는 바이트(salt 역할)의 리스트를 반환하는 RNGCryptoServiceProvider 클래스를 이용하여 난수를 생성한다. 그런 다음 salt와 원래 비밀번호를 결합하여 해싱을 수행한다.
    Private Sub Salted_Hashing_SHA1()
        "--- 난수 생성기(Random Number Generator)---
        Dim salt(8) As Byte
        Dim rng As New RNGCryptoServiceProvider
        rng.GetBytes(salt)

        "--- 사용자에게 비밀번호를 입력해 달라고 요청 ---
        Console.Write("Please enter a password: ")
        Dim password As String = Console.ReadLine()

        "--- salt를 비밀번호에 추가 ---
        password &= ASCII.GetString(salt)

        "--- 비밀번호를 해싱 ---
        Dim data() As Byte = ASCII.GetBytes(password)
        Dim passwordHash() As Byte
        Dim sha As New SHA1CryptoServiceProvider()
        passwordHash = sha.ComputeHash(data)

        "--- 사용자에게 다시 한번 동일한 비밀번호를 입력해 달라고 요청 ---
        Console.Write("Please enter password again: ")
        password = Console.ReadLine()
        Console.WriteLine(ASCII.GetString(salt))

        "--- 두 번째 비밀번호에도 salt 추가 ---
        password &= ASCII.GetString(salt)

        "--- 두 번째로 입력된 비밀번호를 해싱한 다음 첫 번째 것과 비교 ---
        data = ASCII.GetBytes(password)
        If ASCII.GetString(passwordHash) = _
           ASCII.GetString(sha.ComputeHash(data)) Then
            Console.WriteLine("Same password")
        Else
            Console.WriteLine("Incorrect password")
        End If
    End Sub
한 가지 주의할 점은 만약 여러분이 비밀번호를 저장하는 데 있어 salted 해싱을 사용할 경우 각 비밀번호에 사용하는 “salt”는 메인 데이터베이스로부터 분리시켜 저장하여 해커가 그것을 쉽게 획득할 기회를 얻지 못하도록 해야 한다는 것이다.

암호화와 복호화(Encryption and Decryption)

이전 섹션에서 여러분은 해싱이 어떻게 이루어지는에 관해 살펴 보았다. 해싱은 단방향(one-way) 처리과정인데, 이는 한번 값이 해싱되고 나면 여러분은 그 과정을 역으로 밟는다고 해서 원래의 값을 얻을 수는 없다는 것을 의미한다. 이러한 특성은 특히 인증뿐만 아니라 문서에 대한 디지털 서명에 이용하는데 적합하다.

실제로는 정보가 양방향(two-way)으로 처리될 필요가 있는 상황이 여럿 있다. 예를 들면 여러분이 어떤 비밀 메시지를 수신자에게 보낼 경우 여러분은 그 메시지를 “뒤섞어(scramble)” 수신자만이 그 메시지를 볼 수 있도록 하고자 할 수도 있다. 이러한 뒤섞는 처리과정를 암호화(encryption)라 한다. 반면 뒤섞는 처리과정을 반대로 하여 원래의 메시지를 획득하는 것을 복호화(decryption)라 한다. 암호화의 유형에는 크게 대칭형(symmetric)과 비대칭형(asymmetric)으로 두 가지가 있다.
TAG :
댓글 입력
자료실

최근 본 상품0