아키텍처
expo-crypto-dpop 내부 동작 원리 및 설계 구조
아키텍처
레이어 구조
EC P-256 키 쌍 생성 흐름
EC P-256(secp256r1)은 NIST가 표준화한 타원 곡선으로, DPoP에서 ES256 알고리즘(ECDSA with P-256 and SHA-256)의 기반입니다.
iOS: Security.framework의 SecKeyCreateRandomKey 또는 CryptoKit의 P256.Signing.PrivateKey를 사용합니다.
Android: KeyPairGenerator를 EC 알고리즘으로 초기화하여 AndroidKeyStore 제공자로 키를 생성합니다.
JWT 서명 프로세스 (ES256)
DPoP 증명 JWT는 세 부분으로 구성됩니다: header.payload.signature
헤더 구조 (RFC 9449 Section 4.2)
{"typ": "dpop+jwt","alg": "ES256","jwk": {"kty": "EC","crv": "P-256","x": "...","y": "..."}}
typ: 반드시dpop+jwt여야 합니다alg: ES256 (ECDSA with P-256 and SHA-256)jwk: 서명에 사용된 공개 키 (인라인 포함)
페이로드 구조 (RFC 9449 Section 4.2)
{"jti": "unique-jwt-id","htm": "POST","htu": "https://api.example.com/resource","iat": 1700000000,"ath": "sha256-of-access-token","nonce": "server-nonce"}
| 클레임 | 필수 | 설명 |
|---|---|---|
jti | 필수 | JWT 고유 식별자 (UUID v4) |
htm | 필수 | HTTP 메서드 |
htu | 필수 | HTTP URI (쿼리/프래그먼트 제외) |
iat | 필수 | 발급 시각 (Unix 타임스탬프) |
ath | 선택 | 액세스 토큰의 SHA-256 해시 (Base64URL) |
nonce | 선택 | 서버 제공 nonce |
서명 과정
1. headerBase64Url = base64url(JSON.stringify(header))2. payloadBase64Url = base64url(JSON.stringify(payload))3. signingInput = headerBase64Url + "." + payloadBase64Url4. signature = ECDSA_P256_SHA256(signingInput, privateKey)5. result = signingInput + "." + base64url(signature)
JS 레이어에서 헤더와 페이로드를 구성하고, 실제 ECDSA 서명은 네이티브 모듈(signJwt)에서 수행합니다.
보안 저장소 메커니즘
저장 키 이름:
- 개인 키:
expo-crypto-dpop.privateKey - 공개 키:
expo-crypto-dpop.publicKey
접근 제어: SecureStore.WHEN_UNLOCKED — 기기가 잠금 해제된 상태에서만 접근 가능
앱 재설치 감지
iOS Keychain은 앱 삭제 후에도 데이터가 유지됩니다. 이로 인해 재설치 후 기존 Keychain 키가 남아 있어 서버와 키 불일치가 발생할 수 있습니다.
ensureKeyPair()는 네이티브 모듈의 checkFirstLaunch()를 통해 이 문제를 자동으로 처리합니다.
동작 원리:
- iOS:
UserDefaults에 설치 플래그를 저장. 앱 삭제 시 UserDefaults는 제거되지만 Keychain은 유지됨. 재설치 후 플래그가 없으면 최초 실행으로 판단. - Android:
SharedPreferences에 플래그를 저장. Android는 앱 삭제 시 모든 데이터가 삭제되므로 실질적으로 항상 최초 실행.
DPoP 전체 요청 흐름
서버가 use_dpop_nonce 오류를 반환하면 WWW-Authenticate 헤더의 nonce 값을
추출하여 다음 createProof() 호출 시 nonce 옵션으로 전달합니다. 자세한
내용은 통합 가이드를 참고하세요.