Cheondi
개발 · 보안

암호화만 하면 끝인 줄 알았는데

WebGL 로컬 저장 데이터를 살펴보며 암호화의 역할과 한계를 처음 정리한 기록입니다.

  • #security
  • #webgl
  • #local-storage

WebGL에서 설정 값을 브라우저 저장소에 넣는 코드를 보다가 개발자 도구에서 내용이 그대로 보인다는 걸 알았다. 파일처럼 앱 안에 숨겨져 있을 거라고 막연히 생각했는데, 사용자는 자신의 브라우저 저장소를 쉽게 열어볼 수 있었다. 처음 든 생각은 “그럼 전부 암호화하면 되겠다”였다.

암호화를 붙이면 읽기 어려워지는 건 맞지만 무엇을 막으려는지 정하지 않으면 안전하다고 말하기 어려웠다. 브라우저에서 복호화해야 한다면 키와 코드도 결국 클라이언트에 전달될 수 있었다.

먼저 저장하면 안 되는 값을 나눴다

세션 토큰, 단순 화면 설정, 서버에서 다시 받을 수 있는 캐시를 같은 방식으로 다루면 안 됐다. 가장 안전한 저장은 민감한 값을 아예 클라이언트에 오래 남기지 않는 것이었다.

서버에만 둘 값      : 비밀번호, 장기 비밀키
짧게 유지할 값      : 만료가 짧은 세션 정보
로컬에 둘 수 있는 값: 테마, 차트 표시 옵션, 비민감 캐시

업무 규칙상 저장이 필요한 값은 노출됐을 때 영향과 변조됐을 때 영향을 따로 봤다.

암호화와 무결성은 다른 문제였다

값을 읽지 못하게 암호화해도 공격자가 암호문을 바꾸는 것은 막지 못할 수 있다. 인증된 암호화 방식은 복호화 과정에서 변조 여부도 확인한다. 직접 조합한 XOR이나 고정 키는 암호화처럼 보여도 보호 효과가 약했다.

public record EncryptedPayload(
    int Version,
    string Nonce,
    string CipherText,
    string Tag);

실제 암호화 구현은 검증된 라이브러리와 서버·클라이언트의 키 관리 정책을 따라야 했다. 이 구조는 저장 형식에 필요한 문맥을 보여주는 예시였다.

키가 코드에 있으면 한계가 분명했다

WebGL 빌드 안에 고정 키를 넣으면 숙련된 사용자가 분석해 찾을 수 있다. 그래서 클라이언트 암호화를 서버 비밀을 지키는 수단처럼 설명하면 안 됐다. 우연한 노출을 줄이고 저장 내용을 바로 읽기 어렵게 하는 효과와, 서버 권한을 보호하는 보안을 구분했다.

중요한 권한 검사는 언제나 서버에서 다시 했다. 로컬 설정이 변조돼도 서버 동작이나 다른 사용자 데이터에 영향을 주지 않도록 경계를 두는 편이 더 중요했다.

보안은 기능 앞뒤의 흐름이었다

암호화 적용 여부만 체크하면 일이 끝나는 줄 알았다. 실제로는 저장 필요성, 키의 위치, 변조 처리, 복호화 실패, 이전 형식 호환까지 이어졌다. 값이 깨졌을 때 앱이 멈추지 않고 안전한 기본값으로 돌아가는지도 봐야 했다.

이 시기에는 깊은 보안 설계를 한 게 아니라 질문을 바꾸는 법을 배웠다. “암호화했나?”보다 “무엇을 누구로부터 보호하며, 실패하면 어떤 영향이 있나?”를 먼저 묻게 됐다. 보안 기능은 버튼처럼 추가하는 것이 아니라 데이터 흐름 전체의 성질이었다.