비밀을 읽기 위해 키를 보유하지 않고 어떻게 서버가 비밀을 저장하고 동기화할 수 있습니까? Confidex이 키를 생성하고 잠금 장치로 보호하며 이를 사용하여 데이터를 암호화하고 변경 사항을 확인하는 방법을 따라가겠습니다.
잠금
선택받은 자만이 문을 열 수 있다
매트릭스 리로디드
비밀 데이터를 저장하기 위해 가상 vault을 구축한다고 상상해 보세요. 애플리케이션이 닫힌 후에도 해당 데이터를 노트북에 보관하고 다른 장치에서 열 수 있도록 해야 합니다. 저장소에는 자체 키가 필요합니다. 파일을 복사해도 외부인이 해당 내용을 읽거나 변경할 수 없도록 파일을 저장해야 합니다.
lock은 키를 보호하는 작은 암호화 파일입니다. 열려면 마스터 비밀번호와 비밀 코드인 secret_key가 필요하며, 둘 다 파일 자체에는 들어 있지 않습니다. 잠금 파일 전체를 조간신문에 실을 수도 있습니다. 독자들은 사본을 얻겠지만, 공개했다는 사실만으로 개인 키가 드러나지는 않습니다.
외부인이 이 파일을 입수하고 심지어 마스터 비밀번호까지 알아냈다고 가정해 보겠습니다. 이제 비밀번호만 필요한 모든 계산을 반복할 수 있지만 이러한 계산에서는 자물쇠를 여는 키가 생성되지 않습니다. secret_key도 입력입니다. 파일에는 비밀번호만 별도로 확인하는 내용이 포함되어 있지 않습니다. 그것을 열려면 여전히 알 수 없는 비밀 코드가 필요합니다.
해당 코드는 암호화된 무작위 데이터에서 나옵니다. 사람이 그것을 발명하지 않습니다. secret_key을 알 수 없는 한 올바르게 추측된 마스터 비밀번호라도 잠금을 열 수 없습니다. 공격자에게는 여전히 임의의 비밀이 부족합니다. 따라서 취약한 비밀번호만으로는 서버 사본이 실제 사전 공격으로 전환되지 않습니다.
공격자가 secret_key도 획득하면 오프라인으로 마스터 비밀번호 추측을 테스트할 수 있습니다. 비용이 많이 들지만, 최종적인 어려움은 비밀번호의 강도에 따라 달라집니다.
키
여러분, 열쇠는 어떤 역할을 하나요? 열쇠… 잠금을 해제하시겠습니까?
캐리비안의 해적 – 망자의 함
애플리케이션은 운영 체제의 암호화 생성기에서 32개의 임의 바이트를 가져와 키로 사용할 준비를 합니다. 이렇게 준비된 바이트는 개인 마스터 키 master_key입니다.
애플리케이션은 항상 master_key에서 동일한 키를 파생합니다. 동일한 바이트에 대해 동일한 계산을 반복하면 동일한 결과가 제공됩니다. 따라서 노트북과 휴대폰은 동일한 키를 복구할 수 있습니다. 새 장치에서 master_key을 얻으려면 애플리케이션이 먼저 마스터 비밀번호와 secret_key을 사용하여 자물쇠를 열어야 합니다.
첫 번째 쌍: 암호화
저는 키마스터입니다.
고스트버스터즈
암호화된 데이터를 저장하기 위해 애플리케이션은 master_key: secret_key에서 공개 키를 계산합니다. 볼트의 데이터를 암호화할 수 있지만 암호 해독에는 master_key이 필요합니다.
"공개"는 모든 사람에게 키를 제공해야 하는 의무가 아니라 알고리즘에서 이 키의 역할을 설명합니다. Confidex에서는 사용자와 함께 유지됩니다. 이는 자물쇠를 여는 데 필요한 두 번째 비밀이기도 하므로 로컬에 유지되며 네트워크를 통해 전송되지 않습니다. secret_key 자체적으로 누군가가 저장소의 데이터를 암호화할 수 있지만 이를 해독하거나 대신 변경 사항에 서명할 수는 없습니다.
X25519 는 master_key를 입력받아 secret_key를 계산하는 알고리즘입니다. master_key를 secret_key로부터 복원하는 것은 현실적으로 불가능합니다. 초당 10^18번의 곡선 연산을 수행하는 가상의 슈퍼컴퓨터에서도 고전적인 공격에는 평균 약 2조 3천억 년이 걸립니다 (SafeCurves 추정치).
두 번째 쌍: 서명
독창적인 것은 서명뿐입니다.
잡을 수 있으면 날 잡아라
첫 번째 쌍은 내용을 숨기는 데 도움이 됩니다. 이제 볼트 소유자가 변경을 승인했는지 확인하는 방법이 필요합니다. 서버도 서명을 확인할 수 있어야 하므로 공개 확인 키를 보내야 합니다. 첫 번째 쌍의 공개 부분은 secret_key이며, 서버는 이를 수신해서는 안 됩니다. 따라서 우리는 서명을 위한 두 번째 영구 키 쌍을 만듭니다.
또 다른 독립적인 비밀을 저장하는 대신 master_key에서 개인 서명 키를 파생하여 signing_key이라고 부릅니다. 애플리케이션은 이를 사용하여 데이터 및 잠금에 서명합니다. 다른 장치와 서버는 공개 부분을 사용하여 내용을 읽지 않고 서명을 확인합니다.
동일한 master_key에서 이 쌍을 복구할 수 있습니다. 애플리케이션은 서명 키를 고유하게 결정하는 32바이트의 시작 값을 파생합니다.
다양한 작업에 대해 다양한 키를 파생시키기 위해 계산에 고정 라벨(여기서는 confidex/vault/ed25519/v1)을 포함합니다. 동일한 master_key 및 동일한 레이블은 항상 동일한 키를 생성합니다.
마스터 비밀번호는 이러한 계산에 입력되지 않았습니다. 두 쌍 모두 사람이 만든 문자열이 아닌 master_key의 임의 바이트에 의존합니다. 이 구별은 나중에 중요합니다. 비밀번호는 이미 존재하는 키에 대한 액세스를 보호하므로 키 자체를 변경하지 않고도 비밀번호를 변경할 수 있습니다.
파생에서는 해시 기반 키 파생 함수(HKDF, HMAC-based Extract-and-Expand Key Derivation Function)를 사용합니다. Confidex는 이를 SHA-256와 함께 사용하므로 이름이 HKDF-SHA256입니다. 결과 32바이트는 Ed25519 쌍의 seed입니다. signing_key의 공개 부분은 secret_key을 공개하지 않으므로 서명 확인을 위해 서버로 보낼 수 있습니다.
잠금 파일이란 무엇이며 어떤 역할을 합니까?
포드 베이 문을 열어주세요, HAL.
스페이스 오디세이
미안해요, Dave. 유감스럽게도 저는 그렇게 할 수 없습니다.
잠금 장치는 암호화된 master_key을 저장합니다. 열려면 마스터 비밀번호와 secret_key이 필요합니다. 공개 헤더에는 형식 버전, 볼트 식별자, signing_key의 공개 부분, 솔트 및 비밀번호 처리 매개변수가 포함되어 있습니다. 내부 형식 버전은 master_key과 함께 암호화됩니다.
잠금을 생성할 때 애플리케이션에는 이미 master_key이 있으므로 암호화하기 전에 secret_key을 계산합니다. 열 때 사용자는 secret_key를 별도로 제공합니다. 이는 잠금의 공개 복사본에는 없습니다.
암호 해독 후 애플리케이션은 X25519을 사용하여 master_key의 공개 부분을 계산하고 secret_key과 일치하도록 요구합니다. 그런 다음 signing_key을(를) 복구하고 공개 부분을 헤더의 키와 비교합니다. 이러한 방식으로 잠금 장치가 자체적으로 확인됩니다.
마스터 비밀번호 추측 속도를 늦추려면 각 시도에 시간과 메모리가 필요합니다. Confidex은 64MiB의 메모리와 3개의 패스를 갖춘 Argon2id을 사용합니다. 비밀번호 처리에는 무작위 솔트도 사용됩니다. 솔트는 비밀이 아닙니다. 동일한 비밀번호가 다른 잠금에서 다른 결과를 생성하도록 만듭니다.
secret_key이 이미 알려진 경우 비밀번호 길이에 따라 공격 규모가 크게 달라집니다. 5개의 임의의 라틴 소문자 조합은 약 1,200만 개, 7개는 약 80억 개, 12개는 약 9.5 × 10¹⁶ 개입니다. 7개에서 12개 문자로 이동하면 검색 공간이 거의 1,200만 배 증가합니다. 특정 기계에서 7개의 문자를 검색하는 데 평균 하루가 걸렸다면 5개의 문자는 약 2분, 12개의 문자는 약 33,000년이 걸립니다.
Argon2id 결과와 secret_key에서 하나의 키를 얻기 위해 앞서 소개한 HKDF-SHA256 함수를 confidex/lock/wrap/v1 라벨과 함께 사용합니다. 결과는 잠금을 암호화하는 키인 wrapping_key입니다.
우리는 master_key을 숨기고 변조되지 않도록 보호해야 합니다. XChaCha20-Poly1305은 이를 wrapping_key로 암호화하고 인증 태그를 추가합니다. 태그는 암호문을 공개 헤더에 바인딩합니다. 잘못된 비밀번호나 보호된 바이트가 변경되면 열 수 없게 됩니다. 태그와 임의의 일회용 값인 nonce은 암호문과 함께 저장됩니다.
잠금이 다시 생성되거나 암호화될 때마다 애플리케이션은 새로운 솔트와 nonce을 얻습니다. 암호화된 내용에는 버전 바이트 1개와 master_key의 32바이트가 포함됩니다. 24바이트 nonce 및 16바이트 태그가 함께 저장됩니다. 알고리즘 문서에 구성이 설명되어 있습니다.
복구된 키 간의 불일치는 의도적으로 잘못된 잠금 형식이나 잠금을 생성한 프로그램의 버그로 인해 발생할 수 있습니다. 무작위 바이트 손상은 일반적으로 태그를 확인할 때 더 일찍 감지됩니다.
서버에 이 태그를 확인하는 데 필요한 비밀이 없습니다. 따라서 잠금을 전송하기 위해 애플리케이션은 signing_key로 작성된 서명을 추가합니다. 공개 헤더와 암호화된 내용을 포함하여 완성된 파일 전체를 다룹니다. 서버는 서명을 확인하고 처음 업로드할 때 signing_key의 전체 공개 부분과 내부 저장소 식별자를 고정합니다. 이 키에 대한 후속 변경 사항을 확인합니다. 잠금 및 암호화된 데이터는 동일한 signing_key로 서명되어 master_key에서 복구됩니다.
금고
이제 잠금을 게시할 수 있습니다. secret_key 및 master password을 아는 사람만 master_key을 읽을 수 있는 반면, 누구나 잠금 파일에서 공개 서명 키를 가져오고 잠금 내용이나 데이터를 보지 않고도 서명된 데이터를 확인할 수 있습니다.
비밀번호 SuperPa55worD을 생각해 보세요. 지금은 저장만 하고 싶습니다. 애플리케이션을 닫은 후에는 누구도 SuperPa55worD을 보지 않고도 잠금 파일과 암호화된 비밀번호를 어디로든 보낼 수 있어야 하며, 동시에 우리의 소유권을 확인할 수 있어야 합니다. secret_key 및 master password이 있으면 암호화를 되돌릴 수 있어야 합니다. 대칭암호는 동일한 키로 암호화하고 복호화할 수 있습니다. 해당 키를 저장하지 않으므로 잠금을 사용하여 키를 복구할 수 있어야 합니다. 우리는 그것을 message_key이라고 부르겠습니다.
새로운 암호화마다 별도의 message_key을 얻습니다. 우리는 여전히 복구 작업을 준비해야 합니다. 암호문 옆에 키를 열어두면 사본을 가진 사람은 누구나 모든 것을 읽을 수 있습니다.
애플리케이션은 새로운 임의 바이트에서 이 암호화에 대해서만 임시 X25519 쌍을 생성합니다: record_key. 그런 다음 record_key의 비공개 부분과 저장소의 영구 공개 키 secret_key를 가져와 공유 비밀을 계산합니다. 별도의 함수가 해당 비밀에서 message_key를 파생합니다. 이 키는 비밀번호를 암호화합니다.
읽을 때 애플리케이션은 다른 경로를 사용합니다. master_key 및 record_key의 공개 부분을 사용합니다. 이 두 키는 동일한 공유 비밀을 생성합니다. 수학적으로 두 경로 모두 곡선의 동일한 초기 지점에 두 개의 개인 숫자를 다른 순서로 곱합니다. 순서는 결과를 바꾸지 않습니다. 따라서 우리는 record_key의 비공개 부분을 유지하지 않고도 복호화 키를 복구할 수 있습니다.
두 공개 키에서 이 비밀을 얻는 것은 사실상 불가능합니다. 두 경로 모두 해당 개인 키가 필요합니다. 공개 포인트를 사용하면 이를 생성한 개인 번호를 효율적으로 복구할 수 없습니다. 이 수학적 트릭은 금고의 보안을 뒷받침합니다. 하지만 누군가가 우리의 자물쇠가 달린 다른 금고를 우리에게 주는 것을 막을 수는 없습니다.
이제 record_key의 공개 부분과 함께 암호문을 저장할 수 있습니다. record_key의 비공개 부분과 사용된 message_key는 향후 읽기에 더 이상 필요하지 않습니다. 나중에 애플리케이션은 master_key 및 record_key의 저장된 공개 부분에서 암호 해독 키를 복구합니다. 따라서 하나의 영구 저장소 비밀은 개별적으로 암호화된 여러 값을 해독할 수 있으며, 각 값은 필요할 때 복구되는 고유한 message_key을 갖습니다.
텍스트를 숨기는 것만으로는 충분하지 않습니다. 텍스트를 읽을 때 변조도 감지해야 합니다. 따라서 암호화는 암호화된 데이터와 함께 확인되는 짧은 값인 인증 태그를 추가합니다. 검사에 실패하면 애플리케이션은 결과를 거부합니다. 성공적으로 해독된 비밀번호로 무작위 또는 변경된 텍스트를 표시해서는 안 됩니다.
우리는 비대칭 쌍이 공유 비밀을 설정하는 데 도움이 되는 동시에 message_key이 콘텐츠를 암호화하는 구성을 설명했습니다. 이는 Hybrid Public Key Encryption 또는 HPKE라고 하며 RFC 9180에 지정됩니다. Confidex은 X25519을 사용하여 비밀에 동의하고, HKDF-SHA256을 사용하여 키를 파생하며, ChaCha20-Poly1305을 사용하여 무결성 검사를 통한 암호화를 수행합니다. 여기서 HKDF은 record_key 및 master_key 쌍을 사용하여 합의된 공유 비밀을 가져옵니다. 이는 Ed25519 seed 파생과는 별개의 작업입니다. RFC 5869은 HKDF을(를) 설명합니다.
다음 암호화를 위해 애플리케이션은 새 record_key 및 새 HPKE 컨텍스트를 생성합니다. message_key 및 nonce의 조합을 재사용하면 암호 보호가 중단됩니다. Confidex에서는 키와 nonce가 HPKE 내부에서 파생되어 하나의 메시지에 사용된 다음 컨텍스트가 삭제됩니다.
ChaCha20 자체는 256비트 message_key을 사용합니다. 직접적인 공격을 이해하려면 초당 10¹⁸개의 후보 키를 시험하는 가상의 슈퍼컴퓨터를 상상해 보십시오. 2²⁵⁶ 가능한 키를 검색하는 데 약 1.8 × 10⁵¹년이 걸립니다. RFC 8439은 알고리즘과 분석을 설명합니다.
기록
행복해요?
펄프픽션
비밀번호를 저장했지만 웹사이트에 로그인하려면 일반적으로 사용자 이름과 주소도 필요합니다. 때로는 옆에 메모가 필요할 때도 있습니다. 이러한 값을 별도로 저장하는 경우 애플리케이션은 해당 값이 함께 속한다는 것을 어떻게든 기억해야 합니다. 이 "종이"를 봉투에 넣어 보겠습니다. 필드를 하나의 컬렉션으로 결합하고 이를 record이라고 부릅니다.
레코드에는 필드 조합이 포함될 수 있습니다. 애플리케이션은 이를 하나의 패킷으로 모으고 압축한 후 방금 설명한 구성을 사용하여 전체를 암호화합니다. 필드 값, 이름, 유형 및 태그는 모두 보호됩니다. 동일한 비밀번호를 유지하더라도 웹사이트 주소를 변경하면 새로운 record_key로 암호화된 전체 기록의 새 버전이 생성됩니다.
해시체인
프로젝트에서 블록체인을 사용해야 하나요? → 아니요
XKCD 2267
secret_key을 아는 사람은 우리 금고에 대한 암호화된 기록을 준비할 수 있습니다. 이를 우리의 것으로 받아들이지 않기 위해 애플리케이션은 모든 레코드 버전에 signing_key을 사용하여 서명합니다. 이 키가 없으면 외부인은 서버 및 기타 장치에서 허용하는 서명이 있는 버전을 만들 수 없습니다.
암호화된 버전에는 해당 버전이 속한 저장소 및 기록, 변경 시기, 이전 버전 등 몇 가지 정보가 함께 필요합니다. 이는 공개 헤더를 형성합니다. 비밀번호는 포함되어 있지 않지만 여전히 보호가 필요합니다. 그렇지 않으면 누군가가 암호문을 건드리지 않고도 객체의 소유권과 관계를 변경할 수 있습니다. 애플리케이션은 암호문과 함께 정확한 헤더 바이트에 서명합니다. 서명된 데이터를 변경하면 서명이 무효화됩니다.
서버는 봉인된 소포를 보관하는 창고와 유사합니다. 헤더, 암호화된 내용, 서명은 볼 수 있지만 내부의 비밀번호는 볼 수 없으며 수신자가 도시에 존재한다는 것을 알 수 있습니다. signing_key의 고정된 공개 부분과 비교하여 서명을 확인하고 새 버전을 수락할지 여부를 결정합니다. 마스터 비밀번호나 secret_key 또는 개인 키가 필요하지 않습니다. 읽을 때 클라이언트는 동일한 서명을 확인한 다음 HPKE 인증 태그를 사용하여 암호 해독 중에 내용과 헤더 간의 바인딩을 다시 확인합니다.
서명은 버전 간의 링크도 보호합니다. 정확한 헤더 및 암호문 바이트의 SHA-256 해시가 버전의 식별자가 됩니다. 다음 버전의 헤더에는 이전 버전의 식별자가 포함됩니다. 웹사이트 비밀번호를 변경하면 애플리케이션은 이 링크를 사용하여 새 개체를 생성하고 서명합니다. 이전 개체와 해당 서명은 변경되지 않은 상태로 유지됩니다. 따라서 새 버전의 서명을 유지하면서 이전 버전을 교체하면 실패합니다. 두 장치가 독립적으로 변경하는 경우 두 분기 모두 유지됩니다. 병합된 버전은 둘 다를 참조할 수 있습니다.
블록체인을 기억하시나요? 거기 있습니다 :-).
공개 헤더를 사용하면 암호를 해독하지 않고도 링크를 확인할 수 있지만 일부 정보는 계속 표시됩니다. 서버는 식별자, 버전 링크, 클라이언트가 제공한 변경 시간, 필드 수 및 각 필드 값의 인코딩된 크기를 알고 있습니다. 암호화는 구조적 정보가 아닌 기록의 내용을 숨깁니다.
동기화
“데이터 유출이 아니라 무료 분산 백업입니다.”
인터넷 밈.
여러 장치에서 작업하려면 장치 간에 잠금 및 기록 기록을 전송해야 합니다. 객체 형식은 변경되지 않습니다. 로컬에 저장된 것과 동일한 암호화된 바이트 및 서명을 전송합니다.
수동 전송의 경우 애플리케이션은 잠금, 로컬에 저장된 모든 암호화된 레코드 버전 및 서명된 파일 목록을 포함하는 ZIP 아카이브를 생성합니다. 비밀번호와 secret_key은 모두 포함되지 않습니다. 다른 장치에서 애플리케이션은 먼저 잠금 장치를 연 다음 서명을 확인하고 기록을 해독하며 로컬 저장소 복사본을 복원합니다. 가져오기는 이 볼트가 아직 존재하지 않는 장치를 위한 것입니다. 기록을 기존 복사본과 병합하지 않습니다.
동기화 서버는 작업 복사본 간에 변경 사항을 교환합니다. 첫 번째 업로드 시 서명된 잠금을 수신하고 서명을 확인한 후 signing_key의 공개 부분에서 공개 저장소 주소를 계산합니다. 그런 다음 이 키와 헤더의 저장소 식별자를 기억합니다. 이렇게 하면 저장소가 서버에 등록됩니다. 후속 변경 사항은 이 식별자와 일치해야 하며 동일한 키의 서명을 전달해야 합니다.
새 장치에는 저장소 주소와 secret_key이 필요하므로 애플리케이션은 이를 체크섬이 있는 전송 코드로 결합합니다. 사용자는 이 코드를 장치에 전송하고 별도로 마스터 비밀번호를 입력합니다. 공개 주소만 서버로 전송됩니다.
다운로드한 잠금을 연 후 애플리케이션은 복구된 키의 공개 부분을 secret_key 및 헤더와 비교하고 계산된 주소를 전송 코드와 비교합니다. 그런 다음 기록을 확인하기 위해 signing_key의 확인된 공개 부분을 저장합니다.
동기화 중에 애플리케이션은 누락된 버전을 가져와서 변경 사항을 업로드합니다. 서버는 이전 버전에 대한 서명과 링크를 확인하고 허용된 개체를 저장한 다음 이를 다른 장치에 전달합니다. 수신된 데이터를 확인하고, 이를 해독하고, 충돌을 해결하는 작업은 모두 사용자의 장치에서 이루어집니다.
등록을 위해서는 이름, 이메일 주소 또는 별도의 비밀번호가 필요하지 않습니다. 서버는 공개 주소, 서명 확인 키, 공개 헤더, 개체 크기 및 버전 링크를 확인합니다. 또한 IP 주소와 요청 시간도 볼 수 있습니다. 마스터 비밀번호인 secret_key, 개인 키 및 일반 텍스트 기록 내용은 사용자의 장치에 유지됩니다. 공개 주소를 아는 사람은 누구나 암호화된 객체를 다운로드할 수 있지만 이를 읽으려면 사용자의 비밀이 필요합니다.
이러한 검사는 위조된 개체를 탐지하지만 서버가 전체 기록과 최신 상태를 표시했음을 증명하지는 않습니다. 클라이언트가 본 적이 없는 분기를 숨기거나 일관된 이전 데이터 세트를 반환할 수 있습니다. 서명은 서버 응답의 완전성이 아니라 개별 개체의 신뢰성을 확인합니다. 사용자는 다른 기기에서 누락된 변경사항을 발견할 수 있지만 일관된 이전 기록은 hash-chain 확인을 통과합니다.
마스터 비밀번호 변경
그렇다면 성하께서는 비밀번호를 변경하시겠습니까?
해커스 (1995)
마스터 비밀번호를 변경하면 잠금만 다시 암호화됩니다. 내부의 master_key은 동일하게 유지됩니다. 따라서 기록, 서명, secret_key 및 금고 주소는 변경되지 않습니다. 새 잠금은 동일한 signing_key로 서명됩니다.
이전 잠금 복사본은 여전히 이전 비밀번호와 동일한 secret_key으로 열립니다. 비밀번호를 변경해도 이미 노출된 키는 취소되지 않습니다.
전체 그림
잠금 장치는 master_key을 보호하고, message_key은 기록을 암호화하고, signing_key은 서명을 생성합니다. ZIP 아카이브와 서버는 서명이 포함된 암호화된 개체를 받습니다.