1. 목표: 패스워드를 복구하거나 재설정하기

어떤 application이든 패스워드를 사용하게 마련이다. 하지만, 사용자들은 패스워드를 자주 잃어버리기 때문에 재설정을 하기 위한 방법을 제공한다.

2. 안티패턴:패스워드를 평문으로 저장하기

email을 이용하나 패스워드 복구 방법에서 사용자가 가장 많이 실수하는 것은 패스워드를 그대로 email로 보내주는 것이다.
이것은 보안에 심각한 문제를 담고 있다.

2.1 패스워드 저장

다음과 같은 테이블이 있다고 가정하자.

 
CREATE TABLE Accounts (
  account_id    SERIAL PRIMARY KEY,
  account_name  VARCHAR(20) NOT NULL,
  email         VARCHAR(100) NOT NULL,
  password      VARCHAR(30) NOT NULL
);

여기에 행을 삽입하여 계정을 생성할 수 있다.


INSERT INTO Accounts (account_id, account_name, email, password)
  VALUES (123, 'billkarwin', 'bill@example.com', 'xyzzy');

위와 같이 사용하는 경우 다음과 같은 위험이 있다.

  • 클라이언트 애플리케이션에서 데이터베이스 서버로 전송되는 네트워크 패킷을 가로채서 SQL문을 확인할 수 있다.
  • 데이터베이스 서버에서 SQL 쿼리 로그를 검색하여 확인할 수 있다.
  • 데이터베이스 백업 파일로부터 데이터를 읽을 수 있다.

2.2 패스워드 인증

사용자가 로그인을 시도할 때, 애플리케이션은 사용자의 입력과 데이터베이스에 저장된 패스워드를 비교하는데 일반적으로 다음과 같은 쿼리를 사용한다고 가정하자.


SELECT CASE WHEN password = 'opensesame' THEN 1 ELSE 0 END
  AS password_matches
FROM Accounts
WHERE account_id = 123;

위 쿼리는 두개의 값이 같으면 1 아니면 0을 return한다.
이와 같이 사용하는 경우 앞에서 얘기한것과 같이 공격자에게 패스워드가 노출될 위험이 증가한다.

참고
2개의 다른 조건을 한 덩어리로 만들지 마라.
일반적으로 다음과 같이 사용하는 경우가 많다.


SELECT * FROM Accounts
WHERE account_name = 'bill' AND password = 'opensesame';

이와 같이 사용하는 경우 account name이 달라서 빈 결과를 나타내는 것인지 패스워드가 달라서 빈 결과를 나타내는 것인지 확인할 수 없으므로, 정확한 인증 실패 원인을 파악할 수 없다.

2.3 이메일로 패스워드 보내기

패스워드가 평문으로 저장되기 때문에 패스워드를 검색하는 것도 다음과 같이 간단히 처리할 수 있다.


SELECT account_name, email, password
FROM Accounts
WHERE account_id = 123;

이렇게 나온 결과를 e-mail을 통해 전달하기도 하는데 이와 같은 경우 공격자가 가로채서 따로 저장할 가능성이 높다.

3. 안티패턴 인식 방법

다음과 같은 경우가 안티패턴이 된다.

  • 패스워드를 평문으로 저장한다.
  • application에서 역변환하여 암호를 볼 수 있는 방법이 있다.

4. 안티패턴 사용이 합당한 경우

다음과 같은 경우에 사용이 합당하다.

  • application에서 패스워드를 이용해서 다른 서비스에 접근해야 하는 경우
  • 신원확인과 인증을 구분하여 처리한다. 즉, 인증만 필요한 경우 패스워드를 통해 처리하고, 중요한 서비스인 경우 신원확인 프로세스를 따로 구성하여 처리한다.

5. 해법: 패스워드의 소금 친 해시 값을 저장한다.

다음과 같은 방법을 사용하여 좀더 안전하게 패스워드를 사용하도록 하자.

5.1 해시 함수 이해하기

해시 함수를 사용하여 암호를 encode한다.
안전하게 사용하기 위해서는 다음과 같은 알고리즘을 사용하는 것이 좋다.

  • SHA-256
  • SHA-384
  • SHA-512

5.2 SQL에서 해시 사용하기

다음은 account 테이블을 다시 정의 한 것이다. SHA-256을 사용한다고 가정한 경우이다.


CREATE TABLE Accounts (
  account_id     SERIAL PRIMARY KEY,
  account_name   VARCHAR(20),
  email          VARCHAR(100) NOT NULL,
  password_hash  CHAR(64) NOT NULL
);

INSERT INTO Accounts (account_id, account_name, email, password_hash)
  VALUES (123, 'billkarwin', 'bill@example.com', SHA2('xyzzy'));

SELECT CASE WHEN password_hash = SHA2('xyzzy') THEN 1 ELSE 0 END
  AS password_matches
FROM Accounts
WHERE account_id = 123;

5.3 해시에 소금 추가하기

공격자가 dictionary attack하는 것을 막기위해 암호를 encode할 때 salt를 추가한다. salt는 해시 값을 구하기 전에 사용자의 패스워드에 덧붙이는 무의미한 바이트열을 의미한다.
이와 같이 사용하는 경우 사용자가 사전에 있는 단어를 사용하더라도, 공격자가 풀어낼 수 없게 할 수 있다.
패스워드 마다 다른 소금값을 사용하는 경우 salt 정보를 가지고 있어야 한다.


CREATE TABLE Accounts (
  account_id     SERIAL PRIMARY KEY,
  account_name   VARCHAR(20),
  email          VARCHAR(100) NOT NULL,
  password_hash  CHAR(64) NOT NULL,
  salt           BINARY(8) NOT NULL
);

INSERT INTO Accounts (account_id, account_name, email,
    password_hash, salt)
  VALUES (123, 'billkarwin', 'bill@example.com',
    SHA2('xyzzy' || '-0xT!sp9'), '-0xT!sp9');

SELECT (password_hash = SHA2('xyzzy' || salt)) AS password_matches
FROM Accounts
WHERE account_id = 123;

5.4 SQL에서 패스워드 숨기기

사용하는 SQL문장에서도 패스워드가 바로 노출되지 않도록 해야 한다.
SQL 쿼리에 패스워드를 평문으로 넣지 않고 대신 애플리케이션 코드에서 해시 값을 계산해 SQL 쿼리에서 이 해시 값만 사용하면, 이런 종류의 노출을 피할 수 있다.
다음은 PHP 코드이다.


<?php

$password = 'xyzzy';

$stmt = $pdo->query(
   "SELECT salt
    FROM Accounts
    WHERE account_name = 'bill'");

$row = $stmt->fetch();
$salt = $row[0];

$hash = hash('sha256', $password . $salt);

$stmt = $pdo->query("
  SELECT (password_hash = '$hash') AS password_matches;
  FROM Accounts AS a
  WHERE a.account_name = 'bill'");

$row = $stmt->fetch();
if ($row === false) {
  // account 'bill' does not exist
} else {
  $password_matches = $row[0];
  if (!$password_matches) {
    // password given was incorrect
  }
}

또한, 사용자가 패스워드를 입력하고 인증을 받으려고 할 때 평문상태로 보내게 되는데 이때 패스워드가 노출되는 위험이 있게 된다. 이 부분에서 문제를 보완하기 위해서는 https와 같은 보안 프로토콜을 사용하는 것이 좋다.

5.5 패스워드 복구가 아닌 패스워드 재설정 사용하기

패스워드를 잃어버린 경우 복구를 하는 것보다 임시 패스워드를 전달하여 재설정하게 유도하는 것이 좋다.
아니면, 요청 내용을 기록하여 그 사람만 접근할 수 있는 변경 url을 메일을 통해 전달하는 것이다.