jacobhan.me

Engineering · Web Platform

직접 만들지 말라

암호학의 오래된 격언이 웹 인터페이스에 던지는, 사실은 같은 질문

2026년 5월 26일 · 읽는 데 약 14분

검증되지 않은 것을 손수 다시 만들지 말라. 보안 분야에는 이 한 문장으로 요약되는 오래된 규범이 있다. 처음에는 암호 알고리즘을 직접 짜지 말라는 좁은 경고였지만, 시간이 지나면서 이 원칙은 웹 브라우저가 제공하는 기본 동작, 나아가 프로그래밍 언어의 선택에까지 같은 모양으로 번졌다. 표면적으로는 서로 무관해 보이는 세 영역이 하나의 직관을 공유한다. 이미 잘 검증된 것을 무신경하게 다시 만들면, 거의 항상 손해를 본다는 직관이다.

그런데 이 격언은 자주 오해된다. 가장 흔한 오해는 이것을 절대 금지로 받아들이는 것이다. 실제 원칙은 그보다 미묘하고, 그 미묘함을 놓치면 격언은 교리가 되어 버린다. 이 글은 암호에서 출발해 웹 UI(User Interface, 사용자 인터페이스)를 거쳐 메모리 안전성까지, 같은 논리가 어떻게 확장되었고 어디서 균열이 생기는지를 따라간다.

01격언의 출발점은 암호였다

출발점은 암호학이었다. 정확한 출처를 한 사람에게 돌리기는 어렵지만, 이 격언은 오랫동안 보안 실무자와 암호학자 사이에서 통용되어 왔다. 종종 함께 인용되는 것이 슈나이어의 법칙(Schneier's Law)이다. 누구나 자기 머리로는 깰 수 없는 암호를 설계할 수 있다는 것. 문제는 자신이 깰 수 없다는 사실이 남들도 깰 수 없다는 보장이 전혀 되지 못한다는 데 있다.

암호가 까다로운 이유는 수학과 공학이 맞물려 있고, 작은 실수 하나가 시스템 전체를 무력화한다는 데 있다. 부적절한 초기화 벡터(IV, Initialization Vector), 예측 가능한 키스트림, 평문 일부가 새어 나가는 결함 같은 것들은 추상적인 위험이 아니라 실제 자체 구현에서 반복적으로 발견된 결함이다. 알고리즘은 멀쩡해 보여도 그것을 쓰는 방식, 즉 모드 선택과 프로토콜 설계 단계마다 함정이 도사린다. 표준 알고리즘을 호출했다고 해서 안전이 보장되지는 않는다. 어느 숙련된 엔지니어가 AES(Advanced Encryption Standard)를 ECB(Electronic Codebook) 모드로 쓰면서, 자기는 AES를 직접 구현한 게 아니라 라이브러리를 불렀으니 직접 만든 게 아니라고 주장했다는 일화는 이 함정을 잘 보여 준다. 알고리즘을 빌려 와도, 그것을 잘못 조합하면 결국 안전하지 않은 암호를 손수 만든 셈이 된다.

검증의 비대칭성도 핵심이다. 공개된 알고리즘은 수십 년에 걸쳐 공개적으로 분석되고, 공격받고, 개선되고, 표준화된다. 우리가 그것을 신뢰하는 이유는 비밀이어서가 아니라 오랜 공격을 견뎌 냈기 때문이다. 반대로 비공개의 자체 구현은 같은 검증을 거치지 않은 채, 틀린 상태로 조용히 남아 있을 가능성이 높다.

아래로 갈수록 전문성·위험 ↑ 애플리케이션 종단간 암호화 · 저장 암호화 · 사용자 인증 라이브러리 / API 검증된 래퍼 (대부분의 개발자가 의존해야 할 지점) 프로토콜 TLS 등 (안전한 통신 절차의 설계) 기본 요소 (Primitive) AEAD · MAC · 디지털 서명 · 공개키 암호 단방향 함수 · 수학적 토대 사슬의 맨 아래일수록 직접 손대기 가장 위험
암호는 단일한 코드 한 줄이 아니라 토대에서 응용까지 이어지는 신뢰의 사슬이다. 라이브러리를 호출한다는 것은 이 사슬의 위쪽을 빌려 오는 일이며, 아래쪽으로 내려갈수록 직접 손대는 데 따르는 위험이 커진다.

02격언의 진짜 뜻은 "쓰지 말라"가 아니다

여기서 흔한 오독이 생긴다. 직접 만들지 말라는 말은 아무도 암호 코드를 작성해서는 안 된다는 뜻이 아니다. 누군가는 알고리즘을 만들고, 누군가는 기본 요소를 쌓고, 누군가는 프로토콜과 라이브러리를 만든다. 그렇지 않았다면 우리가 신뢰하는 암호 라이브러리 자체가 존재하지 않았을 것이다. 격언의 본의는 가능한 한 검토되고 검증된 패키지와 도구를 쓰라는 쪽에 가깝다.

실제로 이 원칙에는 정당한 예외가 있다. 모든 문제에 표준이 존재하는 것은 아니고, 표준이 있다고 해서 그것이 늘 최선인 것도 아니다. 기존 알고리즘이 요구하는 보안 속성이나 성능을 충족하지 못하는 상황도 있다. 명세를 충실히 구현하고, 비교할 기존 구현이 있고, 충분한 시간과 역량을 들이며, 무엇보다 제3자 검증을 받는다면, 운영 환경에서 직접 구현하는 것도 받아들일 수 있다. 다만 완전히 새로운 알고리즘이나 기본 요소는 다르다. 그것은 논문 공개나 경연을 통해 수년에 걸친 외부 분석을 거친 뒤에야 신뢰받을 자격을 얻는다.

그렇다면 대부분의 개발자는 어떻게 라이브러리의 안전성을 판단할까. 솔직한 답은, 직접 판단하지 못한다는 것이다. 코드를 끝까지 읽어 내려갈 능력이 있는 사람은 많지 않다. 그래서 우리는 비기술적 신호에 의존한다. 만든 사람의 이력과 자격, 그리고 외부 보안 감사다. 어떤 경량 암호 라이브러리가 외부 전문가들의 감사를 거쳤다는 사실은, 그 코드를 직접 읽지 못하는 다수에게 신뢰의 근거가 된다. 자격이라는 신호가 늘 완벽하지는 않지만, 더 직접적인 방법이 마땅치 않은 현실에서 꽤 잘 작동한다.

비유

자체 암호를 짜지 말라는 것은 자기 손으로 항공기 엔진을 설계하지 말라는 것과 비슷하다. 할 수 없어서가 아니라, 이미 충분히 연구되고 시험을 통과한 선택지가 있기 때문이다. 엔진을 새로 설계하려면 풍동 시험과 수만 시간의 검증이 필요하듯, 새 암호도 공개 분석이라는 시험을 통과해야 한다. 설계 자체가 금지된 것이 아니라, 검증되지 않은 설계를 곧바로 승객을 태운 비행기에 다는 일이 금지된 것이다.

03같은 논리, 다른 영역 - 웹 브라우저

웹사이트 디자인은 암호가 아니다. 잘못 만든 스크롤이 키를 유출하지는 않는다. 그러나 구조적으로 닮은 데가 있다. 브라우저는 링크 추적, 텍스트 선택, 스크롤, 입력 처리 같은 기능을 이미 매우 잘 처리하고 있고, 사용자는 매일 그 동작에 의식하지 않은 채 의존한다. 검증되고 모두가 기대는 기능을 다시 구현하면, 암호에서 그랬듯 거의 항상 새로운 문제를 들여오게 된다.

네이티브 입력 요소가 사용자에게 무엇을 자동으로 제공하는지 한 번 헤아려 보면 그 무게를 알 수 있다. 가짜 UI는 겉모습만 흉내 낼 뿐, 그 아래에 연결된 생태계 전체를 잃는다.

강력 비밀번호 생성 비밀번호 관리자 연동 자동완성 · 모바일 키보드 스크린리더 접근성 안전치 않은 전송 경고 운영체제 보조 도구 <input type="password">
비밀번호 입력 필드 하나를 네이티브로 두면, 그 뒤로 여섯 갈래의 기능이 별도 작업 없이 따라온다. 일반 텍스트 필드를 직접 마스킹해 흉내 내면 운영체제와 보조 도구가 그 값을 평문처럼 다룰 위험이 있고, 위 연결망이 통째로 끊긴다.

04사례로 보는 경계선

원칙을 사례에 대 보면, 어디까지가 분별 있는 위임이고 어디서부터가 정당한 예외인지가 드러난다. 흥미롭게도 모든 사례가 똑같이 단호하지는 않다.

스크롤을 가로채는 일

네이티브 스크롤은 마우스, 트랙패드, 키보드 입력에 일관되게 반응하고, 운영체제가 다듬어 온 물리 감각을 그대로 따른다. 이를 직접 구현하면 페이지가 지나치게 느리거나 빠르게 움직이고, 키보드 스크롤이 먹통이 되기도 한다. 더 근본적인 문제는 성능이다. 스크롤 이벤트마다 자바스크립트(JavaScript)가 끼어들면 브라우저의 메인 스레드에 부하가 쌓여, 버튼 클릭에 반응하는 속도를 재는 지표인 INP(Interaction to Next Paint)가 무너지고, 구형 노트북에서는 프레임이 끊긴다. 그래서 세로 스크롤 영역을 만들기 위해 스크롤을 가로채는 일에는 정당한 사용 사례가 사실상 없다는 강경론이 나온다. 다만 스크롤을 비(非)스크롤 동작으로 재매핑하는 경우, 가령 지도에서 스크롤을 확대·축소로 매핑하는 관례에는 예외의 여지가 있다.

링크 내비게이션과 클라이언트 측 라우팅

링크를 따라가는 일은 브라우저의 핵심 기능이다. 자바스크립트가 클릭을 가로채 직접 처리하면, 차라리 새 탭으로 링크를 여는 편이 더 빠를 때가 있다. 그렇다고 클라이언트 측 라우팅(CSR, Client-Side Routing)이 늘 나쁜 것은 아니다. 웹메일처럼 복잡한 애플리케이션에서는 오히려 권장되고, 단순한 콘텐츠 사이트에는 부적합하며, 그 사이 어딘가에 놓인 서비스도 많다.

핵심은 어떤 방식을 쓰든 실제 <a href> 요소를 살려, 새 탭 열기나 가운데 버튼 클릭 같은 브라우저 네이티브 기능이 동작하게 두는 것이다. 문제는 CSR이 너무 자주 허술하게 구현되고, 나쁜 네트워크 조건에 강건하게 다듬어지지 않는다는 데 있다. 브라우저는 이런 상황에 강하다. 탭이 로딩 중임을 표시해 주고, 뒤로·앞으로 가기를 빠르게 만드는 캐시인 bfcache(Back-Forward Cache, 앞뒤 페이지 캐시) 같은 최적화를 제공한다. 직접 만든 라우팅은 종종 이런 최적화를 방해한다.

텍스트 선택과 컨텍스트 메뉴

텍스트 선택을 직접 구현할 이유는 거의 없다. 손가락을 형광펜처럼 쓰는 모바일 주석 앱 정도가 드문 예외다. 오히려 선택 영역의 색만 바꾸는 ::selection 규칙을 잘못 써서 선택한 글자가 보이지 않게 만드는 것이 흔한 실수다. 반면 컨텍스트 메뉴는 다르다. 이메일 클라이언트, 파일 관리자, 다이어그램 편집기처럼 노드마다 고유한 작업이 필요한 앱에서는 자체 메뉴 항목이 정당하게 필요하다. 브라우저가 우클릭 메뉴를 확장하는 깔끔한 방법을 제공하지 않기 때문에, 완전한 커스텀 메뉴가 현실적인 선택이 된다. 다만 네이티브 메뉴를 완전히 가려 버리면 사용자가 불편을 겪으므로, 일부 브라우저가 제공하는 Shift+우클릭 같은 우회로를 남겨 두는 배려가 필요하다.

날짜 선택기 - 원칙이 가장 흔들리는 곳

날짜 선택기는 이 원칙의 한계가 가장 또렷하게 드러나는 사례다. 네이티브 <input type="date">는 자동완성, 지역별 표기 형식, 그리고 어떤 화면 형식이든 제출 시 yyyy-mm-dd로 정규화해 주는 등 적지 않은 이점을 준다. 그런데 한계도 만만치 않다. 날짜 범위 선택을 직접 지원하지 않고, 특정 날짜를 빼는 기능이 없어 예약 서비스에는 출발점부터 쓰기 어렵다. iOS의 사파리(Safari)는 최소·최대 날짜를 지정하는 min·max 속성을 제대로 지원하지 않으며, 데스크톱 사파리는 2021년 14.1 버전에 와서야 네이티브 날짜 선택기를 갖췄다. 브라우저마다 모양과 동작이 제각각이라는 점도 일관성을 해친다.

그렇다고 커스텀 선택기가 답인 것도 아니다. 자바스크립트로 만든 날짜 선택기는 키보드와 스크린리더에 제대로 대응하지 못하는 접근성 함정인 경우가 많다. 40년 전 생년월일을 고르려면 마우스로 이전 달 버튼을 수십 번 눌러야 하는 구현도 흔하다. 그럼에도 커스텀이 빛나는 지점이 있다. 항공권이나 호텔을 찾을 때 날짜 칸마다 가격을 띄워 어느 날이 싼지 보여 주는 기능은 네이티브로는 불가능하다. 날짜마다 메타데이터를 붙일 수 있다는 점이 커스텀의 진짜 가치다.

관점네이티브 input type=date커스텀 선택기
접근성대체로 양호 (브라우저가 처리), 단 편차 존재구현마다 천차만별, 함정이 많음
일관성제출값은 정규화되나 화면 UI는 브라우저별 상이모든 브라우저에서 동일한 모양 가능
지역 형식운영체제·브라우저 설정에 자동 적응직접 구현해야 함
날짜 범위단일 컨트롤로는 미지원 (input 두 개로 우회)하나의 컨트롤로 표현 가능
날짜별 메타데이터불가 (가격·재고 표시 등)가능 (커스텀의 핵심 이점)
생년월일 등 먼 날짜입력 가능하나 기본 화면이 현재 달이라 불편잘 만들면 더 낫지만 보통은 더 나쁨

가장 균형 잡힌 절충은 네이티브를 대체하는 것이 아니라 보완하는 것이다. 네이티브 선택기를 그대로 두고, 같은 입력 칸을 조작하는 보조 위젯을 곁에 더해 주면, 네이티브를 편하게 쓰는 사용자를 잃지 않으면서 부족함을 느끼는 사용자에게도 길을 열어 줄 수 있다. 날짜 범위 역시 개념상으로는 하나의 컨트롤이지만, 입력 칸 두 개로 나누면 시각적으로 무관해 보이는 두 화면으로 쪼개져 오히려 불편해질 수 있다. 정답이 하나로 떨어지지 않는다는 사실 자체가, 절대 명제로서의 격언이 어디서 무너지는지를 보여 준다.

비유

네이티브 컨트롤은 모든 사람에게 맞춘 표준 사이즈 신발과 같다. 대다수에게 무난히 맞고, 따로 만들 필요도 없으며, 어디서 신어도 비슷하다. 하지만 발이 특별히 크거나, 등산처럼 특수한 용도가 있는 사람에게는 맞춤 신발이 필요하다. 모두에게 표준화만 강요하면 그 소수가 곤란해지고, 모두에게 맞춤만 강요하면 비용과 품질 편차가 폭발한다. 분별 있는 선택은 기본값을 표준으로 두되, 분명한 이유가 있을 때 맞춤을 더하는 쪽이다.

05잦은 UI 변경의 숨은 비용

폼 컨트롤을 함부로 바꾸면 기존 문제를 풀면서 거의 항상 새로운 문제를 들여온다. 그런데 더 조용하고 누적적인 비용이 따로 있다. 웹사이트의 레이아웃과 인터페이스를 몇 달마다 갈아엎으면, 일부 사용자는 적응하더라도 누군가는 매번 새 도구를 처음부터 배우는 부담을 진다. 특히 나이 든 사용자에게는 이 재학습이 큰 장벽이 된다.

웹 UI는 결국 사용자가 일을 끝내기 위해 쓰는 도구다. 사용자가 의식하지 않고 쓰던 동작이 낯선 동작으로 바뀌면, 본래 하려던 일보다 도구를 다시 익히는 데 시간을 쓰게 된다. 여러 사이트가 동시에 인터페이스를 바꿔 대면, 사용자는 기능적 이득도 없이 익숙한 것을 다시 배우는 데 상당한 시간을 잃는다.

비유

세탁기 버튼 배치가 매일 아침 바뀐다고 상상해 보자. 더 예쁜 배치라 해도, 빨래를 돌리려는 사람에게는 매일 사용법을 다시 익히는 고역이 된다. 리눅스(Linux) 배포판이 몇 달마다 핵심 명령어와 옵션을 전부 재설계한다면 어떨까. 안정적인 기본 동작의 가치는, 바로 그것이 더 이상 신경 쓸 거리가 아니라는 데 있다.

06더 깊은 확장 - 메모리 안전성

같은 직관이 한 단계 더 아래로, 프로그래밍 언어의 선택으로까지 번졌다. 메모리를 직접 다루는 비안전 언어로는 아무것도 새로 쓰지 말라는 주장이다. 근거가 되는 숫자는 자주 인용된다. 마이크로소프트(Microsoft)는 자사가 매년 할당하는 보안 취약점 식별 번호인 CVE(Common Vulnerabilities and Exposures, 공통 취약점 및 노출) 중 약 70%가 10여 년에 걸쳐 메모리 안전성 문제였다고 밝혔다. 크로미엄(Chromium) 프로젝트는 2015년 이후 분석한 912건의 고·최고 심각도 보안 버그 가운데 약 70%가 메모리 안전성 문제였고, 그중 절반이 이미 해제한 메모리를 다시 쓰는 use-after-free 결함이라고 보고했다. 안드로이드(Android)에서는 2018년 고심각도 취약점의 약 90%가 메모리 안전성 버그에서 비롯됐다.

이 데이터를 바탕으로 크로미엄은 신규 코드에 둘의 규칙(Rule of Two)이라는 보수적 기준을 세웠다. 아래 세 조건 가운데 둘을 넘겨 충족해서는 안 된다는 것이다.

신뢰할 수 없는 입력 처리 샌드박스 없이 실행 메모리 비안전 언어 금지 구역
둘의 규칙. 세 위험 조건이 모두 겹치는 가운데 영역은 금지된다. 신뢰할 수 없는 입력을 다루면서 샌드박스도 없이 비안전 언어로 짠 코드가 가장 위험하다는 경험칙이다. 셋 중 둘까지만 허용해, 적어도 한 겹의 방어선을 강제한다.

그러나 이 확장에도 반론이 따른다. 치명적 취약점의 70%가 메모리 안전성 문제라 해도, 실제 원인을 들여다보면 작은 객체마다 명시적으로 메모리를 할당하거나, 끝을 알리는 표식으로만 길이를 판단하는 문자열 처리에서 상당수가 비롯됐다는 지적이다. 미리 묶어 할당하는 아레나(arena) 방식이나 길이 정보를 함께 들고 다니는 슬라이스(slice) 같은 기법을 쓰면, 같은 언어 안에서도 그 부류의 문제를 크게 줄일 수 있다. 한 관점은 이렇게 요약한다. 옛 언어의 원죄는 배열을 넘길 때 경계 정보를 잃고 단순 포인터로 붕괴되는 데 있다는 것이다. 물론 신뢰할 수 없는 입력을 다루는 고위험 영역에서는 특정 버그 부류를 통째로 제거하는 메모리 안전성이 여전히 최우선이다.

또 다른 결은 이른바 보안 정통파에 대한 경계다. 새 코드를 쓰려면 특정 내부 집단에 속해야 한다고 단정하거나, 자격을 증명하지 못하면 아예 손대지 말라고 선을 긋는 태도는 격언을 교리로 만든다. 자격과 외부 감사가 신뢰의 신호로 꽤 잘 작동한다는 점과, 그 신호를 가진 사람만 무언가를 만들 자격이 있다고 못 박는 일은 전혀 다른 문제다. 격언의 가치는 위계를 세우는 데 있지 않다.

07그래서 언제 만들고, 언제 맡기는가

세 영역을 관통하는 결론은 절대 금지가 아니다. 격언의 진짜 효용은 기본값을 검증된 것으로 두라는 보수적 출발점에 있다. 새 기능을 얹을 때마다 화려함을 더할지, 브라우저와 표준의 기본 동작에 맡길지를 보수적으로 저울질하라는 것이다. 그 저울질에는 대략 다음 질문들이 쓸모 있다. 브라우저나 표준이 이미 이 일을 잘 처리하고 있는가. 직접 구현하면 어떤 기존 기능이 깨지는가. 직접 구현으로 얻는 이득이 그 손실을 정말로 넘어서는가. 그 구현은 검증이나 감사를 받을 수 있는가. 그리고 기존 문제를 풀면서 새 문제를 들여오지는 않는가.

이 질문들에 정직하게 답하면, 대부분의 경우 기본값에 맡기는 쪽이 옳다는 결론이 나온다. 그러나 분명한 이유와 충분한 역량, 그리고 검증의 경로가 갖춰진 소수의 경우에는 직접 만드는 것이 최선일 수 있다. 날짜 선택기에 가격을 띄우는 일이나, 표준이 채우지 못하는 보안 속성을 구현하는 일이 그렇다.

숙련이란 직접 만들 지식과 기술을 갖추고, 동시에 언제 그렇게 하지 말아야 하는지를 아는 지혜를 함께 갖는 것이다.— 이 격언이 결국 가리키는 지점

한 가지 덧붙일 만한 변화가 있다. 그동안 웹 상호작용의 표준 동작은 업계 안에서 암묵적으로 승자가 정해질 뿐, 아무도 그것을 제대로 적어 두지 않아 같은 실수가 세대를 건너 반복되곤 했다. 최근 들어 브라우저 진영이 모범 사례를 문서와 도구 형태로 공개하기 시작한 것은, 바로 이 누락된 지식 전수의 문제에 대한 뒤늦은 대응이다. 무엇을 다시 만들지 말아야 하는지를 명료하게 적어 두는 일은, 절대 금지라는 유치한 모델보다 훨씬 생산적이다.

08마무리

직접 만들지 말라는 말은 손을 떼라는 명령이 아니라, 검증되지 않은 자기 구현을 기본값으로 삼지 말라는 조언이다. 암호에서는 그 조언이 거의 절대에 가깝고, 웹 UI에서는 사례마다 예외의 여지가 넓으며, 언어 선택에서는 그 사이 어디쯤에 있다. 같은 직관이 영역에 따라 다른 강도로 적용된다는 사실은 결함이 아니라 성숙의 표시다. 바퀴를 다시 만들지 말라는 격언의 진짜 무게는, 바퀴를 만들 줄 알면서도 대부분의 날에는 굳이 만들지 않기로 선택하는 분별에 있다.


· · ·