HTTP는 요청을 누군가가 들여다볼 경우, 요청의 내용을 그대로 볼 수 있었다.
HTTPS는 요청의 내용을 한 번 암호화 하기 때문에, 키가 없으면 요청의 내용을 정확히 파악할 수 없다.
우리가 접속한 사이트가 보안이 된 웹사이트인지 알 수 있는 방법은 현재 화면의 주소 옆에 자물쇠 아이콘 을 클릭해보면 된다.
자물쇠 아이콘을 눌러보면 이 사이트는 보안 연결(HTTPS)이 사용되었습니다.
라는 메세지를 확인할 수 있다.
대부분의 웹사이트의 경우 HTTPS를 사용하고 있는데, 만약 HTTP를 사용하는 웹사이트에 접속했다면 Not Secure
메세지를 볼 수 있다.
HTTPS는 HTTP 요청을 SSL 혹은 TLS 알고리즘을 이용하여 HTTP 통신을 하는 과정에서 데이터를 암호화해 전송하는 방법이다.
첫 번째는 제3자가 서버와 클라이언트가 주고받는 정보를 탈취할 수 없게 하는 것이다. 이를 위해 서버와 클라이언트는 서로 합의한 방법으로 데이터를 암호화해 주고받는다.
HTTP는 요청이나 응답이 탈취되면 전달되는 데이터 내용을 제3자가 확인할 수 있다.
데이터를 암호화해 전송하는 HTTPS를 사용하면 비밀번호 같은 중요한 데이터가 유출될 가능성이 낮아진다.
HTTPS는 클라이언트와 서버가 데이터를 암호화하여 주고받기 위해 대칭키 방식과 비대칭키 방식을 혼용하여 사용한다.
대칭키와 비대칭키
대칭키 암호화는 같은 개인키를 클라이언트와 서버가 공유하여 정보를 암호화, 복호화하는 것이다.
비대칭키 암호화는 공개키와 비밀키(개인키)를 각각 암호화, 복호화에 적용하는 것이다.
- 상대방의 공개키를 통한 암호화 & 상대방이 개인키를 통한 복호화 : 정보 자체에 대한 암호화가 필요할 때 사용
- 자신의 개인키를 통한 암호화 & 자신의 공개키를 통한 복호화 : 정보를 송신한 사람의 신원에 대한 정보가 필요할 때 사용
🔐 대칭키와 비대칭키
클라이언트와 서버가 데이터를 주고받을 때는 대칭키를 사용한다. 비대칭키 알고리즘이 훨씬 복잡해서 대칭키를 사용해 데이터를 암호화 및 복호화하는 것이 컴퓨터에 부담을 덜 주기 때문이다.
그런데 대칭키를 주고받는 과정에서 정보가 탈취된다면?
그래서 HTTPS는 대칭키를 주고받을 때는 비대칭키 방식으로 주고받게 한다. 비대칭키는 공개키로 암호화한 정보는 개인이 가진 비밀키로만 풀 수 있기 때문에, 중간에 대칭키가 탈취되어도 개인키가 없으면 복호화 할 수 없다.
HTTPS 프로토콜은 다른 키 한 쌍으로 암호화, 복호화를 할 수 있다. A키로 암호화를 진행했다면, B키로만 복호화가 가능하다.
한 쌍의 키 중에서 어느 한 키는 공개하지 않고(개인키
) 다른 키를 클라이언트에게 공개(공개키
)하여 데이터를 안전하게 전달한다.
다만, 모든 통신 과정에서 공개키 방식을 사용하지는 않는다. 매번 공개키 방식을 사용하기에는 알고리즘이 복잡하기 때문에, 통신의 초반에서 개인키로 사용하기 위한 키를 만들어내기 위해 사용한다.
HTTPS의 또 다른 특징은 브라우저가 서버의 응답과 함께 전달된 인증서를 확인할 수 있다는 것이다.
인증서는 서버의 신원을 보증한다. 우리가 접속한 사이트가 해커가 따라한 가짜 사이트가 아님을 보장해주는 역할을 한다.
이를 보증할 수 있는 제3자를 CA(Certificate Authority)라고 한다.
CA는 인증서를 발급해주는 공인 기관들이다. CA는 서버의 공개키와 정보를 CA의 비밀키로 암호화하여 인증서를 발급한다.
만약 이메일과 관련된 정보를 얻을 때, 어떤 인증 과정도 거치지 않는다면 보안상의 이슈가 발생할 것이다. 누구라도 이메일 정보만 알고 있으면 모든 정보에 접근할 수 있기 때문이다.
그러면 비밀번호를 통한 인증 흐름을 설계해보면 어떨까?
서버는 이메일과 비밀번호 정보를 받고, DB에서 이를 비교한다. 유저 정보가 일치한다면 해당 이메일을 가지고 클라이언트가 요청하는 데이터를 DB에서 찾아 클라이언트에게 전달할 수 있을 것이다.
단, 이메일은 공개되어도 되지만 비밀번호는 공개되면 안 된다. 비밀번호를 DB에 저장할 때, 암호화해서 저장한다면 비밀번호가 탈취되는 문제는 발생하지 않을 것이다.
인증 과정에서 shiftBy 알고리즘을 사용해 서버에 적용하면, 이메일과 비밀번호를 이용해 서버에 요청을 보내는 과정은 이전 그림과 동일하다. 다만, 서버가 DB에 저장된 유저 정보를 비교하기 전에 한 단계가 추가된다.
요청과 함께 전달 받은 비밀번호를 서버에서 암호화를 시키고, DB에 저장된 유저 정보와 대조를 한다.
이때, 비밀번호는 원본을 그대로 저장하지 않고 어떤 알고리즘을 거친 후의 결과를 DB에 저장한다.
비밀번호를 이용한 인증 요청(로그인 등)이 올 때마다 알고리즘을 통해 암호화를 시키고, DB에 저장된 값을 확인하여 맞으면 요청한 데이터를 가져와서 클라이언트에게 반환하는 과정을 갖게 된다.
Hashing은 어떠한 문자열에 임의의 연산을 적용해 다른 문자열로 변환하는 것이다.
Hashing을 서비스에 적용할 때 지켜야 할 철칙
- 모든 값에 대해 해시값을 계산하는데 시간이 오래 걸리지 않아야 한다.
해시값을 해독할 때는 오랜 시간이 걸려야하지만, 해시값을 만드는 데는 오래 걸리지 않아야 한다.
해시값을 만드는데 10초의 시간이 걸린다면, 사용자는 로그인 할 때마다 10초를 기다려야 한다.
ᅠ- 해시 값은 최대한 다른 해시값을 피해야 하며, 모든 값은 고유한 해시값을 갖는다.
ᅠ- 아주 작은 단위의 변경이라도 완전히 다른 해시 값을 가져야 한다.
한 글자만 바꾸더라도 해시값으로 추측할 수 없도록 완전히 다른 값을 반환해야 한다.
대표적인 해시 알고리즘은 SHA1이 있으며, 최근에는 SHA256을 많이 사용한다.
Salt를 해석하면 소금이다. 요리에 소금을 첨가해 더 맛있게 만들듯이, 암호화에서의 Salt도 암호화 할 값에 별도의 값을 추가하여 결과를 변형시킨다.
특정 알고리즘을 통한 해시는 결과가 항상 같다. 해시된 값과 원본 값을 대조한 테이블이 존재하면 쉽게 비밀번호를 알아낼 수 있다.
그러므로 Hashing만을 사용하기에는 불안한 부분이 있어, 이를 보완하기 위해 기존 문자열의 Salt값을 추가한 후 Hashing하면 예상하기 어려운, 전혀 다른 해시값이 만들어진다.
📢 Salt 사용 시의 주의사항
Salt는 유저와 패스워드 별로 유일한 값을 가져야 한다.
사용자 계정을 생성할 때와 비밀변호를 변경할 때마다 새로운 임의의 Salt를 사용해서 해싱해야 한다.
Salt는 절대 재사용하지 않아야 한다.
Salt는 DB의 유저 테이블에 같이 저장되어야 한다.
쿠키는 서버에서 클라이언트에 데이터를 저장하는 방법 중 하나다. 서버는 클라이언트에서 쿠키를 이용해 데이터를 가져올 수 있다.
쿠키를 이용한다는 것은 서버에서 클라이언트에 쿠키를 전송하는 것 뿐만 아니라, 클라이언트에서 서버로 쿠키를 전송하는 것도 포함된다.
서버는 클라이언트에 데이터를 저장할 수 있다.
서버는 쿠키를 이용해 데이터를 저장하고 원할 때 데이터를 다시 불러와 사용할 수 있다.
하지만 데이터를 저장한 이후, 아무때나 데이터를 가져올 수는 없다. 특정 조건들이 만족하는 경우에만 다시 가져올 수 있다.
조건들은 쿠키 옵션으로 표현할 수 있다.
Domain
도메인이란 www.google.com
과 같은 서버에 접속 가능한 이름이다.
쿠키 옵션에서의 도메인은 포트와 서브 도메인 정보, 세부 경로를 포함하지 않는다.
ᅠ
서브 도메인이란 www
같이 도메인 앞에 추가로 작성되는 부분이다.
요청해야 할 URL이 http://www.localhost.com:8080/users/login
이라면, 여기서 도메인은 localhost.com
이다.
ᅠ
쿠키 옵션에서 도메인 정보가 존재하면 클라이언트에서는 쿠키 도메인 옵션과 서버 도메인이 일치해야만 쿠키를 전송할 수 있다.
즉, naver.com
에서 받은 쿠키를 google.com
에 전송하는 일을 방지할 수 있다.
Path
Path(세부 경로)는 서버가 라우팅할 때 사용하는 경로다.
요청해야 할 URL이 http://www.localhost.com:8080/users/login
이라면, 여기서의 Path는 /user/login
이다.
ᅠ
설정된 Path를 전부 만족하는 경우, 요청하는 Path가 추가로 더 존재해도 쿠키를 서버에 전송할 수 있다.
즉, Path가 /user
로 설정되어 있고 요청하는 세부 경로가 /user/mypage
인 경우라면 쿠키 전송이 가능하다.
MaxAge or Expires
쿠키 유효기간을 설정한다. 쿠키가 영원히 남아있으면 탈취되기도 쉽기 때문에 유효기간을 설정하는 것이 보안 측면에서 중요하다.
MaxAge는 앞으로 몇 초 동안 쿠키가 유효한지 설정하는 옵션이고, Expires는 언제까지 유효한지 Date
를 지정한다.
ᅠ
쿠키는 옵션 여부에 따라 세션 쿠키(Session Cookie)와 영속성 쿠키(Persistent Cookie)로 구분된다.
세션 쿠키는 MaxAge & Expires 옵션이 없는 쿠키로, 브라우저가 실행 중일 때 사용할 수 있는 임시 쿠키다. 브라우저를 종료하면 해당 쿠키는 삭제된다.
영속성 쿠키는 브라우저의 종료 여부와 상관없이 MaxAge & Expires 옵션에 지정된 유효기간만큼 사용 가능한 쿠키다.
Secure
쿠키를 전송해야 할 때 사용하는 프로토콜에 따른 쿠키 전송 여부를 결정한다.
만약 해당 옵션이 true
인 경우, HTTPS 프로토콜을 이용하여 통신하는 경우에만 쿠키를 전송 할 수 있다.
Secure 옵션이 없다면, 프로토콜에 상관없이 http://www.google.com
또는 https://www.google.com
에 모두 쿠키를 전송할 수 있습니다.
HttpOnly
자바스크립트에서 브라우저의 쿠키에 접근 여부를 결정한다.
옵션이 true
로 설정된 경우, 자바스크립트에서 쿠키에 접근이 불가하다. 명시되지 않으면 기본으로 false
로 지정되어 있는데, false
인 경우 자바스크립트에서 쿠키에 접근이 가능해 XSS 공격에 취약하다.
SameSite
Cross-Origin 요청을 받은 경우 요청에서 사용한 메소드와 해당 옵션(GET
, POST
, PUT
, PATCH
, ...)의 조합으로 서버의 쿠키 전송 여부를 결정한다.
ᅠ
⬇ 사용 가능한 옵션
same-site는 요청을 보낸 Origin과 서버의 도메인, 프로토콜, 포트가 같은 경우이다. 이 중 하나라도 다르다면 Cross-Origin으로 구분된다.
💡 Same-origin policy
쿠키를 이용한 상태 유지
이러한 쿠키의 특성을 이용해서 서버는 클라이언트에 인증정보를 담은 쿠키를 전송하고, 클라이언트는 전달받은 쿠키를 요청과 같이 전송하여 Stateless한 인터넷 연결을 Stateful하게 유지할 수 있다.
ᅠ
기본적으로 쿠키는 오랜 시간 동안 유지될 수 있고, 자바스크립트를 이용해서 쿠키에 접근할 수 있어 쿠키에 민감한 정보를 담는 것은 위험하다.
인증 정보를 탈취하여 서버에 요청을 보낸다면 서버는 누가 요청을 보낸 건지 상관하지 않고 인증된 유저의 요청으로 취급하기 때문에, 개인 유저 정보 같은 민감한 정보에 접근이 가능하다.
사용자가 웹사이트에서 아이디와 비밀번호를 이용해 로그인을 시도한 상황을 생각해보자.
정확한 아이디와 비밀번호를 입력했다면, 서버는 인증에 성공했다고 판단할 것이다.
ㅤ
이후에 인증을 필요로 하는 작업인 상품을 장바구니에 추가하는 등의 경우 다시 로그인 과정을 거치지는 않는다.
이때 서버는 사용자가 인증에 성공했음을 알고 있어야 하며 클라이언트는 인증 성공을 증명할 수단을 갖고 있어야 한다.
➡️ 쿠키를 통해 유효한 세션 아이디가 서버에 전달되고, 세션 스토어에 해당 세션이 존재하면 서버는 해당 요청에 접근이 가능하다고 판단한다.
반면에 쿠키에 세션 아이디 정보가 없는 경우, 서버는 해당 요청이 인증되지 않았음을 알려준다.
세션 아이디가 담긴 쿠키는 클라이언트에 저장되어 있고, 서버는 세션을 저장하고 있다.
서버는 세션 아이디로만 인증 여부를 판단한다.
💡 서버는 클라이언트의 쿠키를 임의로 삭제할 수 없지만, set-cookie로 클라이언트에게 쿠키를 전송할 때 세션 아이디의 키값을 무효한 값으로 갱신할 수 있다.