인증의 여러 방식을 아무리 정교하게 설계해도, 그 방식이 다루는 값들이 오가는 길과 머무는 자리가 허술하면 방어가 무너진다. 비밀번호, 세션 식별자, 토큰, 서명에 쓰는 비밀 같은 값들은 전송되는 도중에 엿보일 수 있고, 저장된 자리에서 새어 나갈 수 있다. 인증의 안전은 이 값들이 오가는 전송과 머무는 저장을 함께 지킬 때 비로소 온전해진다.
이번 편은 값이 오가는 전송을 어떻게 지키는지, 서명에 쓰는 비밀 같은 값을 어디에 어떻게 두는지, 저장된 값을 어떻게 보호하는지, 그 보호에 쓰는 열쇠를 어떻게 관리하는지, 그리고 저장하는 값 자체를 어떻게 줄이는지를 다룬다. 앞선 편들이 인증의 방식을 다루었다면, 이번 편은 그 방식이 딛고 선 바닥, 곧 값의 전송과 저장이라는 기반을 짚는다.
전송 도중을 지키기
인증에 쓰이는 값들은 서버와 사용자의 기기 사이를 오간다. 이 오가는 도중에 값이 엿보이면, 아무리 값을 잘 만들어도 소용이 없다. 그래서 인증 값은 암호화된 연결로만 오가야 한다. 암호화된 연결에서는 오가는 내용이 중간에서 읽히지 않으므로, 값이 전송 도중에 엿보이는 것을 막을 수 있다.
암호화된 연결은 내용을 감출 뿐 아니라, 지금 연결한 상대가 진짜 그 서버가 맞는지도 확인하게 해 준다. 중간에서 다른 쪽이 서버인 척 끼어들어 값을 가로채는 것을 막으려면, 연결한 상대가 진짜임을 확인할 수 있어야 한다. 암호화된 연결은 이 확인을 함께 제공해, 엿봄과 끼어듦을 동시에 막는다.
그래서 인증 값을 다루는 서비스는 모든 연결을 암호화된 것으로만 두어야 한다. 일부 경로만 암호화하고 나머지를 두면, 암호화되지 않은 경로로 값이 새어 나갈 수 있다. 암호화되지 않은 연결로 온 요청은 암호화된 연결로 옮기도록 유도하고, 인증 값은 암호화된 연결에서만 오가게 제한한다.
쿠키에 인증 값을 담을 때는 암호화된 연결에서만 전송되게 하는 속성을 붙여, 값이 실수로라도 암호화되지 않은 경로에 실리지 않게 한다. 전송을 지키는 것은 연결 자체를 암호화하는 일과, 인증 값이 그 암호화된 연결에서만 오가도록 못 박는 일이 함께 이루어져야 온전하다.
비밀을 어디에 둘 것인가
인증에는 서버만 알아야 하는 비밀이 있다. 토큰에 서명하는 비밀, 값을 보호하는 열쇠 같은 것들이다. 이 비밀이 새어 나가면 그것을 바탕으로 한 방어가 통째로 무너지므로, 비밀을 어디에 어떻게 두느냐가 인증의 안전을 크게 좌우한다.
가장 먼저 지켜야 할 것은 비밀을 코드에 적어 두지 않는 것이다. 비밀이 코드에 박혀 있으면, 코드가 공유되거나 새어 나갈 때 비밀도 함께 나간다. 코드를 다루는 여러 사람이 비밀을 함께 보게 되는 것도 문제다. 그래서 비밀은 코드에서 분리해, 코드와 다른 자리에 두어야 한다.
비밀은 그것만을 위한 안전한 저장소에 두는 것이 좋다. 이런 저장소는 비밀에 대한 접근을 통제하고, 누가 언제 비밀에 접근했는지를 남기며, 비밀을 필요할 때만 꺼내 쓰게 한다. 비밀을 흩어 두기보다 이런 자리에 모아 두면, 접근을 좁히고 관리를 집중할 수 있다. 비밀이 새어 나갈 표면이 그만큼 줄어든다.
비밀에 접근할 수 있는 범위도 최소로 좁혀야 한다. 그 비밀을 정말 필요로 하는 부분만 접근하게 하고, 그 밖에는 접근을 막는다. 접근할 수 있는 곳이 많을수록 그중 하나가 뚫렸을 때 비밀이 새어 나갈 위험이 커지기 때문이다. 최소 권한의 원칙이 비밀의 접근에도 그대로 적용된다.
저장된 값을 보호하기
저장소에 머무는 값 가운데 민감한 것은, 저장소가 새어 나가더라도 그대로 드러나지 않도록 보호해야 한다. 비밀번호는 되돌릴 수 없는 형태로 바꾸어 저장하고, 그 밖에 감추어야 할 값은 암호화해 두는 것이 그 보호다. 저장소가 언젠가 새어 나갈 수 있다는 전제 아래, 새어 나가도 값이 드러나지 않게 미리 대비하는 것이다.
여기서 되돌릴 수 없는 변환과 되돌릴 수 있는 암호화를 구분해야 한다. 비밀번호처럼 원래 값을 되돌릴 필요가 없고 대조만 하면 되는 값은 되돌릴 수 없는 형태로 바꾼다. 반면 나중에 원래 값을 다시 써야 하는 값은 열쇠로 풀 수 있는 암호화로 감춘다. 값의 쓰임에 따라 두 방식을 나누어 적용한다.
세션 식별자나 확인용 값처럼 서버가 대조에만 쓰는 값도, 저장할 때는 되돌릴 수 없는 형태로 바꾸어 두는 편이 안전하다. 저장소가 새어 나가더라도 그 값이 그대로 드러나지 않으므로, 새어 나간 저장소로 세션을 가로채거나 확인을 통과하는 일을 막을 수 있다. 대조만 하면 되는 값에는 이 방식이 잘 맞는다.
어떤 값을 보호할지는 그 값이 새어 나갔을 때의 피해로 정한다. 새어 나가면 계정이 넘어가거나 신원이 도용되는 값은 반드시 보호하고, 그렇지 않은 값은 가볍게 다룬다. 모든 값을 똑같이 무겁게 다루면 관리가 번거로워지므로, 위험이 큰 값에 보호를 집중하는 것이 실용적이다.
보호에 쓰는 열쇠를 관리하기
값을 암호화해 보호하면, 그 암호화에 쓰는 열쇠가 새로운 관심사가 된다. 열쇠가 새어 나가면 암호화한 값이 그대로 풀리므로, 보호의 안전이 결국 열쇠의 안전으로 옮겨 간다. 그래서 값을 보호하는 것만큼이나 그 열쇠를 관리하는 것이 중요하다.
열쇠는 그것이 보호하는 값과 다른 자리에 두어야 한다. 암호화한 값과 그것을 푸는 열쇠가 같은 자리에 있으면, 그 자리가 새어 나갈 때 값과 열쇠가 함께 나가 보호가 무의미해진다. 열쇠를 값과 분리해 안전한 저장소에 두면, 값이 새어 나가더라도 열쇠가 없어 풀 수 없다.
열쇠도 주기적으로 바꿀 수 있어야 한다. 하나의 열쇠를 오래 쓰면, 그것이 새어 나갔을 때의 피해가 그만큼 넓어진다. 그래서 열쇠를 정기적으로 새것으로 바꾸고, 옛 열쇠로 보호한 값을 새 열쇠로 다시 보호하는 절차를 마련한다. 여러 열쇠를 함께 다룰 수 있게 해 두면, 값을 한꺼번에 다시 보호하지 않고도 열쇠를 바꿔 나갈 수 있다.
열쇠에 대한 접근을 남겨 두고 살피는 것도 관리의 일부다. 누가 언제 열쇠에 접근했는지를 기록해 두면, 이상한 접근을 알아채고 대응할 수 있다. 열쇠는 인증 방어의 뿌리에 가까운 값이므로, 그 접근을 특히 좁히고 살피는 것이 안전하다. 열쇠의 관리가 무너지면 그 위에 쌓은 모든 보호가 함께 흔들린다.
저장하는 값을 줄이기
가장 안전한 값은 애초에 저장하지 않은 값이다. 저장하지 않으면 새어 나갈 것도 없기 때문이다. 그래서 인증을 설계할 때는, 무엇을 저장할지만이 아니라 무엇을 저장하지 않아도 되는지를 함께 따져야 한다. 꼭 필요하지 않은 값은 두지 않는 것이 가장 근본적인 방어다.
비밀번호를 원래 형태로 저장하지 않는 것이 이 원칙의 대표적인 적용이다. 대조만 하면 되므로 원래 값을 둘 이유가 없고, 두지 않으면 새어 나가도 드러나지 않는다. 토큰 방식에서 서버가 상태를 두지 않는 것도, 자주 바뀌는 정보를 토큰에 담지 않고 필요할 때 조회하는 것도 같은 맥락이다. 저장을 줄이면 지킬 것이 준다.
넘겨받는 정보의 범위를 좁히는 것도 저장을 줄이는 길이다. 외부에서 신원을 넘겨받을 때 필요한 만큼만 받으면, 저장할 것도 그만큼 준다. 편의를 위해 넓게 받아 두면 그 정보를 지킬 부담이 돌아오고 새어 나갔을 때의 피해도 커진다. 받는 것을 줄이는 절제가 저장의 부담과 위험을 함께 던다.
더 이상 필요 없어진 값은 제때 지우는 것도 중요하다. 만료된 세션, 쓰고 난 확인용 값, 탈퇴한 계정의 정보 같은 것을 그대로 두면, 지킬 이유가 없는 값이 저장소에 남아 위험만 남긴다. 값의 생애가 끝나면 지우는 절차를 함께 두어, 저장소에 죽은 값이 쌓이지 않게 한다. 저장을 줄이는 것은 두지 않는 일과 제때 지우는 일을 함께 포함한다.
새어 나갈 경로를 함께 막기
값을 잘 저장하고 전송해도, 그 값이 엉뚱한 경로로 새어 나가면 소용이 없다. 인증 값이 기록에 남거나, 주소에 실려 노출되거나, 화면에 드러나는 경로를 함께 막아야 한다. 저장과 전송을 지키는 것만큼이나 이런 부수적인 노출 경로를 살피는 것이 중요하다.
기록에 인증 값이 남는 것은 흔한 노출 경로다. 요청을 기록으로 남길 때 그 안에 담긴 토큰이나 비밀번호가 그대로 적히면, 기록을 보는 사람에게 값이 드러난다. 그래서 기록을 남길 때는 인증 값 같은 민감한 부분을 가리거나 빼고, 기록 자체도 보호한다. 방어를 위한 기록이 오히려 새어 나가는 통로가 되지 않게 하는 것이다.
주소에 인증 값을 싣는 것도 피해야 한다. 주소는 기록에 남고, 화면에 보이고, 다른 곳으로 옮겨질 때 함께 딸려 가기 쉽다. 그래서 인증 값은 주소가 아니라 값을 감추기 좋은 자리에 담아 보낸다. 확인용 링크처럼 값이 주소에 실려야 하는 경우에는, 그 값을 한 번만 쓰이고 곧 만료되게 해 노출의 위험을 줄인다.
오류를 알리는 화면이나 응답에 민감한 값이 새어 나가지 않게 하는 것도 잊지 말아야 한다. 문제가 생겼을 때 그 원인을 자세히 드러내다 보면, 내부 정보나 인증 값이 함께 노출될 수 있다. 그래서 오류를 알릴 때는 사용자가 대응할 만큼만 알리고, 내부의 세부는 감춘다. 새어 나갈 경로를 하나하나 막는 이 세심함이 저장과 전송의 방어를 완성한다.
기반을 점검하기
전송과 저장의 방어는 한 번 세워 두고 잊는 것이 아니라 꾸준히 점검해야 한다. 시간이 지나며 새 경로가 생기거나 옛 설정이 느슨해질 수 있기 때문이다. 그래서 인증 값이 암호화된 연결로만 오가는지, 비밀이 코드에 스며들지 않았는지, 저장된 값이 제대로 보호되는지를 주기적으로 확인한다.
비밀이 코드나 기록에 실수로 남지 않았는지 살피는 것은 특히 중요하다. 개발 과정에서 편의를 위해 임시로 적어 둔 비밀이 그대로 남거나, 기록에 민감한 값이 스며드는 일이 흔하기 때문이다. 이런 스며듦을 찾아 걷어 내는 점검을 정기적으로 두면, 비밀이 엉뚱한 자리에 남는 것을 막을 수 있다.
암호화된 연결의 설정이 시대에 뒤처지지 않았는지도 살펴야 한다. 전송을 지키는 방식은 시간이 지나며 약해질 수 있으므로, 그 설정을 최신의 안전한 수준으로 유지한다. 저장을 보호하는 변환의 강도도 마찬가지로, 시간이 지나며 약해지지 않도록 주기적으로 재검토해 방어의 수준을 유지한다.
점검은 값의 생애 전체를 따라가며 이루어져야 한다. 값이 만들어지고, 전송되고, 저장되고, 쓰이고, 지워지는 각 단계에서 새어 나갈 여지가 없는지를 살핀다. 어느 한 단계만 지키고 다른 단계를 방치하면, 방치된 곳으로 값이 새어 나간다. 값의 생애를 처음부터 끝까지 지키는 것이 전송과 저장 방어의 요체다. 값이 만들어질 때 예측 불가능한지, 오갈 때 암호화된 연결에 실리는지, 머무를 때 새어 나가도 드러나지 않는지, 쓰인 뒤에 제때 지워지는지를 각 단계마다 물어야, 어느 한 단계에 숨은 빈틈이 방어 전체를 무너뜨리는 일을 막을 수 있다.
정리하면 인증의 안전은 값이 오가는 전송과 머무는 저장을 함께 지킬 때 온전해진다. 값은 암호화된 연결로만 오가게 하고, 비밀은 코드에서 분리해 안전한 저장소에 두고 접근을 좁게 통제하며, 저장된 값은 새어 나가도 드러나지 않게 보호하고, 그 보호에 쓰는 열쇠는 값과 분리해 따로 관리한다. 저장하는 값 자체를 줄여 지킬 것을 덜고, 기록과 주소 같은 부수적인 노출 경로를 막으며, 암호화된 연결과 저장 보호의 강도가 시대에 뒤처지지 않도록 값의 생애 전체를 꾸준히 점검한다. 다음 편에서는 이 시리즈를 마무리하며 인증에서 되풀이되는 흔한 실수들을 짚는다.