Secure Coding)해시함수 응용편

시큐어 코딩

Featured image

@

Adaptive Key Derivation Functions

다이제스트를 생성할 때 솔팅(Salting)과 키 스트레칭(key stretching)을 반복하며 솔트와 패스워드 외에도 입력 값을 추가하여 공격자가 쉽게 다이제스트를 유추할 수 없도록 하고 보안의 강도를 선택할 수 있다. 이 함수들은 GPU와 같은 장비를 이용한 병렬화를 어렵게 하는 기능을 제공한다. 이와 같은 기능은 프로그램이 언어에서 제공하는 라이브러리만으로는 구현하기 어렵다.

PBKDF2

가장 많이 사용되는 함수는 PBKDF2(Password-Based Key Derivation Function)이다. 해시 함수의 컨테이너인 PBKDF2는 솔트를 적용한 후 해시 함수의 반복 횟수를 임의로 선택할 수 있다. PBKDF2는 아주 가볍고 구현하기 쉬우며, SHA와 같이 검증된 해시 함수만을 사용한다.

import CryptoSwift

let password: Array<UInt8> = Array("s33krit".utf8)
let salt: Array<UInt8> = Array("nacllcan".utf8)

do {
    /// password: 패스워드, salt: 암호학 솔트, iterations: 원하는 iteration 반복 수, keyLength: 원하는 다이제스트 길이, variant: 해시함수
    let PBKDF2 = try PKCS5.PBKDF2(password: password, salt: salt, iterations: 4096, keyLength: 32, variant: .sha256).calculate()
    print(PBKDF2)
} catch let err {
    print(err.localizedDescription)
}

bcrypt

bcrypt는 애초부터 패스워드 저장을 목적으로 설계되었다. Niels Provos와 David Mazières가 1999년 발표했고 현재까지 사용되는 가장 강력한 해시 메커니즘 중 하나이다. bcrypt는 보안에 집착하기로 유명한 OpenBSD에서 기본 암호 인증 메커니즘으로 사용되고 있고 미래에 PBKDF2보다 더 경쟁력이 있다고 여겨진다. bcrypt에서 “work factor” 인자는 하나의 해시 다이제스트를 생성하는 데 얼마만큼의 처리 과정을 수행할지 결정한다. “work factor”를 조정하는 것만으로 간단하게 시스템의 보안성을 높일 수 있다. 다만 PBKDF2나 scrypt와는 달리 bcrypt는 입력 값으로 72 bytes character를 사용해야 하는 제약이 있다.

import BCrypt

// Hash
let digest = try BCrypt.Hash.make(message: "foo")
print(digest.string)

// Verify
let digest = "$2a$04$TI13sbmh3IHnmRepeEFoJOkVZWsn5S1O8QOwm8ZU5gNIpJog9pXZm"
let result = try BCrypt.Hash.verify(message: "vapor", matches: digest)
print(result)

scrypt

PBKDF2와 유사한 adaptive key derivation function이며 Colin Percival이 2012년 9월 17일 설계했다. scrypt는 다이제스트를 생성할 때 메모리 오버헤드를 갖도록 설계되어, 억지 기법 공격(brute-force attack)을 시도할 때 병렬화 처리가 매우 어렵다. 따라서 PBKDF2보다 안전하다고 평가되며 미래에 bcrypt에 비해 더 경쟁력이 있다고 여겨진다. scrypt는 보안에 아주 민감한 사용자들을 위한 백업 솔루션을 제공하는 Tarsnap에서도 사용하고 있다. 또한 scrypt는 여러 프로그래밍 언어의 라이브러리로 제공받을 수 있다.

import CryptoSwift

let password: Array<UInt8> = Array("s33krit".utf8)
let salt: Array<UInt8> = Array("nacllcan".utf8)

do {
    /// password: 패스워드, salt: 암호학 솔트, dkLen: 원하는 다이제스트 길이, N: CPU 비용, r: 메모리 비용, p: 병렬화(parallelization)
    let scrypt = try Scrypt(password: password, salt: salt, dkLen: 64, N: 16384, r: 8, p: 1).calculate()
    print(scrypt)
} catch let err {
    print(err.localizedDescription)
}

마치며

전에 쓴 글에서 MD5, SHA 등의 해시 함수들은 암호화처럼 사용할 순 있었지만 시간이 지나고 기술이 발달하면서 취약점이 발견되어 사용할 수 없게되었고 간단한 메시지 인증과 무결성 체크로 활용하게 되었다. 취약점을 보안한 위와 같은 key derivation function이 생겨나게 되었고 암호화를 사용할거라면 위의 기술들을 사용하도록 하자.

출처