"Pung!"은 종단간 암호화(End-to-End Encryption) 를 적용하여 사용자의 프라이버시를 최우선으로 보호하는 비공개 메모 공유 서비스입니다. 서버는 암호화된 데이터만 저장할 뿐, 원본 내용을 절대 알 수 없습니다. 메모는 설정된 조건에 따라 자동으로 파기되어, 민감한 정보를 안전하게 공유하고 흔적을 남기지 않을 수 있습니다.
본 프로젝트는 데이터의 보안과 자동 소멸을 위해 다음과 같은 기술 스택과 아키텍처를 사용합니다.
- 암호화 주체: 서버가 아닌 **사용자의 브라우저(클라이언트)**에서 모든 암호화와 복호화가 이루어집니다.
- 암호화 알고리즘:
AES-256-CBC(Advanced Encryption Standard 256-bit in Cipher Block Chaining mode)를 표준으로 채택하여 강력한 보안을 제공합니다.- 키 (Key):
CryptoJS.lib.WordArray.random(32)를 통해 암호학적으로 안전한 256비트(32바이트) 비밀키를 생성합니다. - 초기화 벡터 (IV): CBC 모드의 보안성을 높이기 위해, 암호화 시마다 새로운 128비트(16바이트) IV를 생성하여 사용합니다.
- 키 (Key):
- 암호화 대상: 메모의 제목, 본문, 첨부 파일명, 첨부 파일 데이터 등 사용자가 입력하는 모든 민감한 정보가 암호화 대상입니다.
- 키 생성 (Client): 사용자가 '저장' 버튼을 누르면, 브라우저에서
CryptoJS를 통해 **AES-256 비밀키(Base64 인코딩)**를 생성합니다. - 데이터 암호화 (Client): 생성된 비밀키를 사용하여 메모 제목, 내용, 파일 데이터 등 모든 정보를
AES-256-CBC방식으로 암호화합니다.- 암호화된 데이터는
[IV + Ciphertext]형태로 결합된 후,Base64로 인코딩되어 서버 전송에 용이한 형태로 변환됩니다.
- 암호화된 데이터는
- 서버 전송 (Client → Server): 암호화된 데이터 덩어리(Blob)와 만료 시간(TTL), 조회수 제한 등 메타데이터를
FormData에 담아 Spring Boot 백엔드로 전송합니다. 이 단계에서 비밀키는 절대 서버로 전송되지 않습니다. - 데이터 저장 (Server):
- Redis: 서버는 전달받은 암호화된 데이터를 그대로 Redis에 저장합니다. 이때 사용자가 설정한 TTL(Time To Live)이 적용되어, 지정된 시간이 지나면 Redis가 자동으로 해당 메모를 삭제합니다. 이는 빠른 접근과 자동 소멸을 담당하는 캐시 저장소 역할을 합니다.
- AWS RDS DB (Write-Through): Redis 저장과 동시에, 영속적인 관리를 위해 암호화된 데이터와 메타데이터를 AWS RDS DB에도 저장합니다. (Write-Through 전략). 이는 사용자의 '내 메모 목록' 조회 및 만료 상태 관리에 사용됩니다.
- AWS S3: 암호화된 파일 데이터는 AWS S3 버킷에 업로드됩니다. 파일명 또한 암호화된 상태로 저장됩니다.
- 키 저장 및 공유 링크 생성 (Client):
- IndexedDB: 서버로부터 메모 고유 ID를 응답받으면, 브라우저는
(ID, 비밀키)쌍을 IndexedDB에 저장합니다. 이는 사용자가 나중에 동일한 브라우저에서 메모를 다시 조회할 때 키를 찾기 위함입니다. - URL Fragment: 사용자를 즉시 조회 페이지로 안내하기 위해, 생성된 비밀키를 URL의 프래그먼트(해시,
#) 부분에 포함시켜 리다이렉트합니다. (예:.../view?id=...#SECRET_KEY) 프래그먼트는 서버로 전송되지 않는 클라이언트의 고유 영역이므로, 비밀키가 네트워크를 통해 노출될 위험이 없습니다.
- IndexedDB: 서버로부터 메모 고유 ID를 응답받으면, 브라우저는
메모의 생명 주기는 '자동 만료'와 '사용자 직접 삭제' 두 가지 시나리오로 관리됩니다.
시간(TTL) 또는 조회수 제한에 도달했을 때 발생하는 시나리오입니다.
- 데이터 접근 차단 (Server): 만료 조건이 충족되면, 서버는 Redis에 캐시된 암호화된 본문과 S3에 저장된 암호화된 파일을 삭제합니다. 이로써 해당 데이터는 더 이상 접근 및 복호화가 불가능해집니다.
- 상태 변경 (Server): 데이터 삭제와 함께, RDS DB에 저장된 메모 메타데이터의
is_deleted플래그를true로 업데이트합니다. 이는 메모의 내용이 사라졌음을 의미하는 논리적 삭제입니다. 메모의 제목, 생성일 등 메타데이터 자체는 DB에서 삭제되지 않습니다. - 결과: 사용자의 '내 메모 목록'에서는 해당 메모가 '만료됨'으로 표시되어 더 이상 접근할 수 없습니다. 하지만 클라이언트의 IndexedDB에는 복호화 키가 그대로 남아있습니다.
사용자가 목록에서 직접 메모를 삭제할 때 발생하는 시나리오입니다.
- 완전 삭제 요청 (Client → Server): 사용자가 삭제 버튼을 누르면, 클라이언트는 서버에 해당 메모의 완전 삭제를 요청합니다.
- 모든 서버 데이터 삭제 (Server): 서버는 요청받은 메모 ID와 관련된 모든 정보를 각 저장소에서 영구적으로 삭제합니다.
- Redis: 캐시된 데이터 삭제
- AWS S3: 업로드된 파일 삭제
- RDS DB: 메타데이터를 포함한 해당 메모의 테이블 행(Row) 전체 삭제
- 클라이언트 키 삭제 (Client): 이상적으로, 클라이언트는 서버의 삭제 처리 성공에 맞춰 IndexedDB에 저장된 해당 메모의 복호화 키를 함께 삭제하여 모든 흔적을 제거합니다.
데이터베이스는 User와 MemoList 두 개의 핵심 테이블로 구성되며, 관계는 다음과 같습니다.
사용자 계정 정보를 저장하는 테이블입니다.
| 컬럼명 | 데이터 타입 | 설명 |
|---|---|---|
id |
BIGINT (PK) |
사용자의 고유 식별자 (Auto Increment) |
uid |
VARCHAR |
사용자 로그인 ID (Unique) |
upw |
VARCHAR |
BCrypt로 해싱된 비밀번호 |
메모의 메타데이터를 저장하는 테이블입니다. 실제 내용은 암호화되어 Redis/S3에 저장되므로, 여기서는 관리에 필요한 정보만 다룹니다.
| 컬럼명 | 데이터 타입 | 설명 |
|---|---|---|
memo_id |
VARCHAR (PK) |
메모의 고유 식별자 (UUID) |
user_id |
BIGINT (FK) |
메모를 작성한 사용자의 ID (User 테이블 참조) |
memo_title |
VARCHAR |
메모 제목 (암호화되지 않은 원본) |
view_limit |
INT |
열람 가능 횟수 (0이면 무제한) |
view_count |
INT |
현재까지의 열람 횟수 |
file_url |
VARCHAR |
S3에 저장된 암호화된 파일의 URL |
original_file_name |
VARCHAR |
암호화된 원본 파일명 |
ttl_minutes |
INT |
설정된 만료 시간(분) |
expire_at |
DATETIME |
예상 만료 시각 |
created_at |
DATETIME |
메모 생성 시각 |
is_deleted |
BOOLEAN |
만료(소프트 삭제) 여부를 나타내는 플래그 |
User와MemoList는 1:N (일대다) 관계를 가집니다.- 한 명의 사용자(
User)는 여러 개의 메모(MemoList)를 작성할 수 있습니다. MemoList테이블의user_id컬럼이User테이블의id를 외래 키(Foreign Key)로 참조하여 관계를 맺습니다.
사용자는 총 4개의 주요 화면을 통해 서비스의 모든 기능을 이용할 수 있습니다.
- 역할: 서비스의 진입점으로, 사용자를 식별하고 인증하는 역할을 담당합니다.
- 주요 기능: 아이디와 비밀번호를 입력하여 새 계정을 생성하거나, 기존 계정으로 로그인합니다.
- 기술적 흐름: Spring Security의 폼 로그인(
formLogin) 및 로그아웃 처리를 기반으로 동작합니다.
회원가입 시/api/auth/signupAPI를 호출하여 사용자 정보를 DB에 저장합니다.
- 역할: 서비스의 핵심 기능으로, 새로운 비공개 메모를 생성하는 페이지입니다.
- 주요 기능:
- 제목, 본문 텍스트 입력
- 파일(이미지 등) 첨부
- 고급 설정:
- 시간 제한: 5분, 1시간, 1일 등 프리셋 또는 직접 입력을 통해 만료 시간(TTL)을 설정합니다.
- 횟수 제한: 1회, 5회 등 열람 가능 횟수를 설정합니다.
- 기술적 흐름:
- '저장 및 링크 생성' 버튼 클릭 시, 브라우저 내에서 AES-256 비밀키를 생성합니다.
- 생성된 키를 이용해 입력된 모든 데이터(제목, 본문, 파일)를 클라이언트 단에서 암호화합니다.
- 암호화된 데이터와 고급 설정 값을
FormData로 구성하여/api/notes에POST요청을 보냅니다. - 서버로부터 응답받은 메모 ID와 생성했던 비밀키를 조합하여, 조회 페이지 URL(
.../view?id=...#key)을 생성하고 사용자를 리다이렉트합니다.
- 역할: 로그인한 사용자가 자신이 작성한 메모들을 확인하고 관리하는 개인 대시보드입니다.
- 주요 기능:
- 내가 작성한 메모의 목록(제목, 생성일)을 시간순으로 조회합니다.
- 만료된 메모는 '만료됨'으로 비활성화 처리되어 표시됩니다.
- 각 메모를 클릭하여 조회 페이지로 이동하거나, '삭제' 버튼을 통해 영구적으로 삭제(하드 삭제)할 수 있습니다.
- 기술적 흐름: 페이지 로드 시
/api/notes/listAPI를 호출하여, 현재 로그인된 사용자의user_id와 일치하는MemoList를 DB에서 조회하여 화면에 렌더링합니다.
- 역할: 생성된 메모의 내용을 확인하는 최종 목적지 페이지입니다.
- 주요 기능:
- 암호화된 메모의 내용을 복호화하여 보여줍니다.
- 만료 시간 또는 남은 조회 횟수와 같은 제한 사항을 사용자에게 안내합니다.
- 메모 내용을 텍스트 파일로 다운로드하거나, 첨부된 파일을 다운로드할 수 있습니다.
- 현재 보고 있는 메모의 공유 링크를 복사할 수 있습니다.
- 기술적 흐름:
- 페이지에 진입하면, URL의 프래그먼트(
#) 또는 IndexedDB에서 복호화 키를 획득합니다. - URL의 쿼리 파라미터에서
id를 추출하여/api/notes/{id}에GET요청을 보냅니다. - 서버로부터 암호화된 메모 데이터를 응답받습니다.
- 획득한 키를 사용해 응답받은 데이터를 클라이언트 단에서 복호화하여 화면에 표시합니다.
- 페이지에 진입하면, URL의 프래그먼트(
- Backend: Spring Boot, Spring Security, Spring Data JPA, Spring Data Redis
- Database: RDS DB, Redis
- File Storage: AWS S3
- Frontend: HTML, Bootstrap, JavaScript
- Cryptography: CryptoJS (AES-256-CBC)
- Build: Maven