Sanctum: 암호학적으로 부인 가능한 볼트 시스템과 IPFS 스토리지

발행: (2026년 1월 9일 오전 08:03 GMT+9)
9 분 소요
원문: Dev.to

Source: Dev.to

Sanctum 일러스트

🎭 The Problem: Encryption Isn’t Enough

Traditional encrypted storage has a fatal flaw:

Attacker: "Give me the password or else."
You:      "I don't have one."
Attacker: *checks encrypted file* "This is clearly encrypted. Try again."

You can’t prove the absence of data—until now.

✨ 해결책: 암호학적 부인 가능성

Sanctum은 세 개의 구분할 수 없는 레이어를 생성합니다:

레이어설명
디코이 레이어무해한 내용 (가족 사진, $200이 들어 있는 작은 지갑)
숨겨진 레이어실제 비밀 (고발자 문서, 주요 암호화 지갑)
패닉 레이어압박 상황에서 “볼트가 삭제됨”을 표시

마법? 세 레이어 모두 암호학적으로 동일합니다. 공격자는 어느 레이어가 실제인지—또는 숨겨진 레이어가 존재하는지조차 증명할 수 없습니다.

🏗️ 아키텍처: 설계 단계에서 제로‑트러스트

클라이언트‑사이드 암호화 흐름

// 1. User creates vault with decoy + hidden content
const decoyBlob  = encrypt(decoyContent, '');               // Empty passphrase
const hiddenBlob = encrypt(hiddenContent, deriveKey(passphrase));

// 2. XOR both layers (makes them indistinguishable)
const combined = xor(decoyBlob, hiddenBlob);

// 3. Upload to IPFS
const decoyCID  = await ipfs.upload(decoyBlob);
const hiddenCID = await ipfs.upload(hiddenBlob);

// 4. Split‑key architecture
const keyA = randomBytes(32); // Stays in URL
const keyB = randomBytes(32); // Encrypted in database
const vaultURL = `https://sanctumvault.online/unlock/${vaultId}#${encode(keyA)}`;

기술 스택

  • 프론트엔드: Next.js 15 + React + Web Crypto API
  • 암호학: XChaCha20‑Poly1305 + Argon2id (256 MB 메모리, 3회 반복)
  • 스토리지: IPFS via Pinata / Filebase (무료 티어)
  • 데이터베이스: Cloudflare D1 (키 분할 저장 전용)
  • 호스팅: Cloudflare Pages (정적 사이트)

보안 기능

// RAM‑only storage (no disk persistence)
class SecureStorage {
  private keys = new Map();

  store(key: string, value: Uint8Array): void {
    this.keys.set(key, value);
    // Auto‑clear after 5 minutes
    setTimeout(() => this.wipe(key), 300_000);
  }

  wipe(key: string): void {
    const data = this.keys.get(key);
    if (data) {
      data.fill(0);               // Overwrite memory
      this.keys.delete(key);
    }
  }
}

// Panic key: Double‑press Escape
let escapeCount = 0;
document.addEventListener('keydown', (e) => {
  if (e.key === 'Escape') {
    escapeCount++;
    if (escapeCount === 2) {
      wipeAllKeys();
      window.location.href = '/';
    }
    setTimeout(() => (escapeCount = 0), 500);
  }
});

🎯 실제 사용 사례

  1. 기자, 출처 보호

    Decoy:  Published articles, public research notes
    Hidden: Confidential source documents, whistleblower communications
    Scenario: Device seized at border → reveal decoy, sources stay protected
  2. 압박을 받는 암호화폐 보유자

    Decoy: Small wallet with $200 ("this is all I have")
    Hidden: Main wallet with life savings
    Scenario: $5 wrench attack → hand over decoy wallet, real funds stay safe
  3. 권위주의 정권 내 활동가

    Decoy: Personal photos, innocuous social media content
    Hidden: Protest coordination plans, evidence of government abuse
    Scenario: Police raid → show decoy layer, cannot prove hidden content exists

🛡️ 공격 저항

공격 벡터방어
Physical Duress위장 구문을 공개; 숨겨진 레이어는 구별할 수 없음.
Disk ForensicsRAM‑only 저장; 키가 디스크에 기록되지 않음; 탭 닫을 때 자동 삭제.
Timing Analysis모든 작업에 500‑2000 ms 무작위 지연 적용.
Blob Size Analysis표준 크기(1 KB, 10 KB, 100 KB, 1 MB, 10 MB, 25 MB)로 패딩.
Brute Force256 MB 메모리를 사용하는 Argon2id로 무차별 공격이 실질적으로 불가능.

🚀 Quick Start

For Users

Visit sanctumvault.online

  • Pinata 또는 Filebase (무료 IPFS 제공자) 설정
  • 선택적 미끼 콘텐츠와 함께 금고 생성
  • 숨겨진 레이어를 위한 암호문 설정
  • 링크 공유 – 암호문은 본인만 알 수 있음

For Developers

# Clone repository
git clone https://github.com/Teycir/Sanctum.git
cd Sanctum

# Install dependencies
npm install

# Run development server
npm run dev

🔬 기술 심층 분석

왜 XChaCha20‑Poly1305인가?

// AES‑GCM: 96‑bit nonce (collision risk after 2^48 encryptions)
// XChaCha20: 192‑bit nonce (collision risk after 2^96 encryptions)

import { xchacha20poly1305 } from '@noble/ciphers/chacha';

export function encrypt(
  plaintext: Uint8Array,
  key: Uint8Array
): Uint8Array {
  const nonce = randomBytes(24); // 192‑bit nonce
  const cipher = xchacha20poly1305(key, nonce);
  return cipher.encrypt(plaintext);
}

XChaCha20‑Poly1305는 훨씬 더 큰 논스 공간을 제공하여 논스 재사용 문제를 없애고 브라우저에서 높은 성능을 갖춘 인증된 암호화를 제공합니다.

// Encryption function
function encryptData(
  plaintext: Uint8Array,
  key: Uint8Array
): EncryptionResult {
  const nonce = randomBytes(24); // 192‑bit nonce
  const cipher = xchacha20poly1305(key, nonce);
  const ciphertext = cipher.encrypt(plaintext);

  return {
    ciphertext,
    nonce,
    // Authenticated encryption tag (last 16 bytes)
    authTag: ciphertext.slice(-16),
  };
}

분할‑키 아키텍처

// KeyA: Stays in URL fragment (never sent to server)
// KeyB: Encrypted in database with vault‑specific key

const vaultKey = deriveKey(vaultId + salt);
const encryptedKeyB = encrypt(keyB, vaultKey);

// To decrypt IPFS CIDs:
const masterKey = xor(keyA, keyB);
const decoyCID = decrypt(encryptedDecoyCID, masterKey);
const hiddenCID = decrypt(encryptedHiddenCID, masterKey);

30‑일 유예 기간

-- Stage 1: Soft delete (mark inactive)
UPDATE vaults
SET is_active = 0
WHERE expires_at < NOW();
  • ✅ 받지 않음:

    • 국가 안보 서한 (NSLs)
    • FISA 법원 명령
    • 검열 명령
    • 백도어 구현 요청
  • ✅ 아키텍처 보장:

    • 제로‑지식: 사용자 금고를 복호화할 수 없음
    • 사용자 로그 없음: IP 주소나 메타데이터가 없음
    • 백도어 없음: 모든 코드가 오픈‑소스
    • RAM‑전용: 키를 영구 저장하지 않음

🌐 왜 IPFS인가?

Traditional cloud storage has single points of failure:

IssueCloud StorageIPFS
중앙 집중식제공자가 데이터를 넘겨주도록 강제될 수 있음데이터가 다수의 노드에 복제됨
검열 가능정부가 접근을 차단할 수 있음콘텐츠 주소 지정(CID), 위치 기반이 아님
삭제 가능제공자가 귀하의 데이터를 삭제할 수 있음불변 – 업로드 후 수정 불가
비용지속적인 요금무료 티어: Pinata (1 GB) + Filebase (5 GB)

🚫 Sanctum 이 아닌 것

  • 비밀번호 관리자 – 이를 위해 KeePassXC / Bitwarden를 사용하세요
  • 백업 솔루션 – IPFS 데이터는 고정 해제될 수 있습니다
  • 파일‑공유 서비스 – 링크는 영구적이며 삭제할 수 없습니다
  • VPN – 익명을 위해 Tor Browser를 사용하세요

💡 배운 교훈

1. RAM‑Only Storage Is Hard

// ❌ WRONG: localStorage persists to disk
localStorage.setItem('key', encode(key));

// ✅ CORRECT: In‑memory only
const keyStore = new Map();

2. Timing Attacks Are Real

// ❌ WRONG: Instant response reveals wrong passphrase
if (passphrase !== correctPassphrase) {
  return { error: 'Invalid passphrase' };
}

// ✅ CORRECT: Constant‑time comparison + random delay
const isValid = timingSafeEqual(hash(passphrase), hash(correctPassphrase));
await sleep(randomInt(500, 2000));
return isValid ? { data } : { error: 'Invalid passphrase' };

3. Browser History Is a Leak

// Vault URLs contain KeyA in fragment.
// Must clear from browser history.
if (window.history.replaceState) {
  window.history.replaceState(null, '', '/unlock');
}

🔮 미래 로드맵

  • Shamir Secret Sharing – 여러 사람에게 금고 접근을 분산
  • Dead Man’s Switch – 비활성 상태 후 자동 해제
  • Steganography – 무해해 보이는 이미지에 금고 숨기기
  • Hardware Key Support – YubiKey / Ledger 통합
  • Mobile Apps – iOS / Android 바이오메트릭 잠금 해제

🙏 감사의 글

  • VeraCrypt – 그럴듯한 부인 가능성에 대한 영감
  • Cloudflare Pages – 무료 정적 사이트 호스팅
  • Pinata / Filebase – 무료 IPFS 고정 서비스
  • @noble/ciphers – 검증된 암호학 라이브러리

📜 License

Business Source License 1.1 – 비생산 용도에 대해서는 무료입니다. 생산 용도로 사용하려면 4년 후에 상업 라이선스가 필요합니다.

🔗 링크

  • 실시간 데모:
  • GitHub:
  • 비디오 데모:
  • 연락처:

💬 토론

어떻게 생각하시나요? 민감한 데이터에 암호학적 부인성을 사용하시겠습니까? 또 어떤 사용 사례를 상상할 수 있나요?

아래에 댓글을 남기거나 GitHub에서 이슈를 열어 주세요!

❤️와 🔒으로 만든 개발자: Teycir Ben Soltane

면책 조항: Sanctum은 정당한 프라이버시 요구를 위한 도구입니다. 사용자는 현지 법규를 준수할 책임이 있습니다. 개발자는 불법 활동을 용인하지 않습니다.

Back to Blog

관련 글

더 보기 »

안녕, 뉴비 여기요.

안녕! 나는 다시 S.T.E.M. 분야로 돌아가고 있어. 에너지 시스템, 과학, 기술, 공학, 그리고 수학을 배우는 것을 즐겨. 내가 진행하고 있는 프로젝트 중 하나는...