Share via


Azure Cosmos DB의 Always Encrypted에서 클라이언트 쪽 암호화 사용

적용 대상: NoSQL

Important

암호화 패키지의 1.0 릴리스와 함께 호환성이 손상되는 변경이 도입되었습니다. 이전 버전에서 데이터 암호화 키 및 암호화 사용하도록 설정 컨테이너를 만든 경우 클라이언트 코드를 1.0 패키지로 마이그레이션한 후 데이터베이스와 컨테이너를 다시 만들어야 합니다.

Always Encrypted는 신용 카드 번호나 주민 등록 번호(예: 미국 사회 보장 번호)와 같이 Azure Cosmos DB에 저장된 중요한 데이터를 보호하기 위한 기능입니다. Always Encrypted를 사용하면 클라이언트가 클라이언트 애플리케이션의 중요한 데이터를 암호화하고 암호화 키를 데이터베이스에 표시하지 않을 수 있습니다.

Always Encrypted는 Azure Cosmos DB에 클라이언트 쪽 암호화 기능을 제공합니다. 다음 시나리오에서는 클라이언트 쪽 데이터 암호화가 필요할 수 있습니다.

  • 특정 기밀 특성이 있는 중요한 데이터 보호: Always Encrypted를 사용하면 클라이언트가 애플리케이션 내부의 중요한 데이터를 암호화할 수 있으며 일반 텍스트 데이터 또는 암호화 키를 Azure Cosmos DB 서비스에 공개하지 않습니다.
  • 속성별 액세스 제어 구현: 암호화는 Azure Key Vault에서 소유하고 관리하는 키로 제어되므로 액세스 정책을 적용하여 각 클라이언트가 액세스할 수 있는 민감한 속성을 제어할 수 있습니다.

개념

Azure Cosmos DB의 Always Encrypted는 클라이언트 쪽 암호화 구성과 관련된 몇 가지 새로운 개념을 도입합니다.

암호화 키

데이터 암호화 키

Always Encrypted를 사용하는 경우 데이터는 미리 만들어야 하는 DEK(데이터 암호화 키)로 암호화됩니다. 이러한 DEK는 Azure Cosmos DB 서비스에 저장되고 데이터베이스 수준에서 정의되므로 여러 컨테이너에서 DEK를 공유할 수 있습니다. DEK 생성은 Azure Cosmos DB SDK를 사용하여 클라이언트 쪽에서 수행됩니다.

마케팅 목록의 구성원을 관리할 수 있습니다.

  • 속성당 하나의 DEK를 만들어 암호화하거나
  • 동일한 DEK를 사용하여 여러 속성을 암호화합니다.

고객 관리형 키

DEK는 Azure Cosmos DB에 저장되기 전에 CMK(고객 관리 키)로 래핑됩니다. CMK는 DEK 래핑 및 래핑 해제를 제어하여 해당 DEK로 암호화된 데이터에 대한 액세스를 효과적으로 제어합니다. CMK 스토리지는 Azure Key Vault에 저장될 것으로 예상하는 기본 구현을 통해 확장 가능하도록 설계되었습니다.

암호화 키

암호화 정책

인덱싱 정책과 마찬가지로 암호화 정책은 JSON 속성을 암호화해야 하는 방법을 설명하는 컨테이너 수준 사양입니다. 이 정책은 컨테이너가 만들어지고 변경할 수 없을 때 제공되어야 합니다. 현재 릴리스에서는 암호화 정책을 업데이트할 수 없습니다.

암호화하려는 각 속성에 대해 암호화 정책은 다음을 정의합니다.

  • /property 양식에 있는 속성 경로입니다. 현재 최상위 수준 경로만 지원되며 /path/to/property와 같은 중첩 경로는 지원되지 않습니다.
  • 속성을 암호화하고 암호 해독할 때 사용할 DEK의 ID입니다.
  • 암호화 유형입니다. 임의적 또는 결정적일 수 있습니다.
  • 속성을 암호화할 때 사용할 암호화 알고리즘입니다. 지정된 알고리즘은 호환되는 경우 키를 만들 때 정의된 알고리즘을 재정의할 수 있습니다.

임의 암호화와 결정적 암호화

Azure Cosmos DB 서비스에서는 Always Encrypted로 암호화된 속성의 일반 텍스트를 볼 수 없습니다. 그러나 속성에 사용되는 암호화 유형에 따라 암호화된 데이터에 대한 일부 쿼리 기능은 계속 지원됩니다. Always Encrypted는 다음 두 가지 유형의 암호화를 지원합니다.

  • 결정적 암호화: 지정된 일반 텍스트 값 및 암호화 구성에 대해 항상 동일한 암호화된 값을 생성합니다. 결정적 암호화를 사용하면 쿼리가 암호화된 속성에 대해 동일한 필터를 수행할 수 있습니다. 그러나 공격자는 암호화된 속성의 패턴을 검사하여 암호화된 값에 대한 정보를 추측할 수 있습니다. True/False 또는 North/South/East/West 지역과 같이 있을 수 있는 암호화된 값의 작은 집합이 있는 경우 특히 그렇습니다.

  • 임의적 암호화: 예측하기 어려운 방식으로 데이터를 암호화하는 방법을 사용합니다. 임의 암호화는 더 안전하지만 암호화된 속성에 대한 쿼리가 필터링되지 못하도록 방지합니다.

Always Encrypted의 결정적 및 임의 암호화에 관해 자세히 알아보려면 IV(초기화 벡터) 생성을 참조하세요.

Azure Key Vault 설정

Always Encrypted를 시작하는 첫 번째 단계는 Azure Key Vault에서 CMK를 만드는 것입니다.

  1. 새 Azure Key Vault 인스턴스를 만들거나 기존 인스턴스를 찾습니다.
  2. 섹션에서 새 키를 만듭니다.
  3. 키를 만들었으면 현재 버전으로 이동하여 전체 키 식별자를 복사합니다.
    https://<my-key-vault>.vault.azure.net/keys/<key>/<version>. 키 식별자의 끝에서 키 버전을 생략하면 키의 최신 버전이 사용됩니다.

다음으로 Azure Cosmos DB SDK가 Azure Key Vault 인스턴스에 액세스하는 방법을 구성해야 합니다. 이 인증은 Microsoft Entra ID를 통해 수행됩니다. 어떤 종류의 ID라도 사용될 수 있지만 클라이언트 코드와 Azure Key Vault 인스턴스 간의 프록시로 Microsoft Entra 애플리케이션의 ID 또는 관리 ID를 사용할 가능성이 높습니다. Microsoft Entra ID를 프록시로 사용하려면 다음 단계를 따릅니다.

  1. Azure Key Vault 인스턴스에서 액세스 정책 섹션으로 이동하여 새 정책을 추가합니다.

    1. 키 권한에서 가져오기, 나열, 키 래핑 해제, 키 래핑, 확인서명을 선택합니다.
    2. 주체 선택에서 Microsoft Entra ID를 검색합니다.

CMK가 실수로 삭제되지 않도록 보호

CMK를 실수로 삭제한 후 암호화된 데이터에 대한 액세스 권한을 잃지 않도록 Azure Key Vault 인스턴스의 두 가지 속성, 일시 삭제보호 제거 속성을 설정하는 것이 좋습니다.

새 Azure Key Vault 인스턴스를 만드는 경우 생성 중에 이러한 속성을 사용하도록 설정합니다.

새 Azure Key Vault 인스턴스에 대한 일시 삭제 및 제거 방지 속성의 스크린샷.

기존 Azure Key Vault 인스턴스를 사용하는 경우 Azure Portal에서 속성 섹션을 보면 이러한 속성을 사용하도록 설정되어 있는지 확인할 수 있습니다. 이러한 속성 중 하나라도 사용하도록 설정되어 있지 않으면 다음 문서 중 하나에서 "일시 삭제를 사용하도록 설정" 및 "제거 보호 활성화" 섹션을 참조하세요.

SDK 초기화

참고 항목

현재 Azure Cosmos DB의 Always Encrypted가 다음에서 지원됩니다.

Always Encrypted를 사용하려면 KeyResolver의 인스턴스를 Azure Cosmos DB SDK 인스턴스에 연결해야 합니다. Azure.Security.KeyVault.Keys.Cryptography 네임스페이스에 정의된 이 클래스는 CMK를 호스팅하는 키 저장소와 상호 작용하는 데 사용됩니다.

다음 코드 조각은 DefaultAzureCredential 클래스를 사용하여 Azure Key Vault 인스턴스에 액세스할 때 사용할 Microsoft Entra ID를 검색합니다. 여기에서 다양한 종류의 TokenCredential 클래스를 만드는 예를 찾을 수 있습니다.

참고 항목

TokenCredential 클래스에 액세스하려면 추가 Azure.Identity 패키지가 필요합니다.

var tokenCredential = new DefaultAzureCredential();
var keyResolver = new KeyResolver(tokenCredential);
var client = new CosmosClient("<connection-string>")
    .WithEncryption(keyResolver, KeyEncryptionKeyResolverName.AzureKeyVault);

데이터 암호화 키 만들기

컨테이너에서 데이터를 암호화하려면 먼저 상위 데이터베이스에 데이터 암호화 키를 만들어야 합니다.

새 데이터 암호화 키 만들기는 CreateClientEncryptionKeyAsync 메서드를 호출하고 다음을 전달하여 수행됩니다.

  • 데이터베이스에서 키를 고유하게 식별하는 문자열 식별자입니다.
  • 키와 함께 사용할 암호화 알고리즘입니다. 현재 하나의 알고리즘만 지원됩니다.
  • Azure Key Vault에 저장된 CMK의 키 식별자입니다. 이 매개 변수는 다음과 같은 일반 EncryptionKeyWrapMetadata 개체에 전달됩니다.
    • type은 키 확인자의 형식을 정의합니다(예: Azure Key Vault).
    • name은 원하는 모든 식별 이름이 될 수 있습니다.
    • value는 키 식별자여야 합니다.

    Important

    키를 만들었으면 현재 버전으로 이동하여 전체 키 식별자(https://<my-key-vault>.vault.azure.net/keys/<key>/<version>)를 복사합니다. 키 식별자의 끝에서 키 버전을 생략하면 키의 최신 버전이 사용됩니다.

    • algorithm은 키 암호화 키를 고객 관리형 키로 래핑하는 데 사용할 알고리즘을 정의합니다.
var database = client.GetDatabase("my-database");
await database.CreateClientEncryptionKeyAsync(
    "my-key",
    DataEncryptionAlgorithm.AeadAes256CbcHmacSha256,
    new EncryptionKeyWrapMetadata(
        KeyEncryptionKeyResolverName.AzureKeyVault,
        "akvKey",
        "https://<my-key-vault>.vault.azure.net/keys/<key>/<version>",
        EncryptionAlgorithm.RsaOaep.ToString()));

암호화 정책을 사용하여 컨테이너 만들기

컨테이너를 만들 때 컨테이너 수준 암호화 정책을 지정합니다.

var path1 = new ClientEncryptionIncludedPath
{
    Path = "/property1",
    ClientEncryptionKeyId = "my-key",
    EncryptionType = EncryptionType.Deterministic.ToString(),
    EncryptionAlgorithm = DataEncryptionAlgorithm.AeadAes256CbcHmacSha256
};
var path2 = new ClientEncryptionIncludedPath
{
    Path = "/property2",
    ClientEncryptionKeyId = "my-key",
    EncryptionType = EncryptionType.Randomized.ToString(),
    EncryptionAlgorithm = DataEncryptionAlgorithm.AeadAes256CbcHmacSha256
};
await database.DefineContainer("my-container", "/partition-key")
    .WithClientEncryptionPolicy()
    .WithIncludedPath(path1)
    .WithIncludedPath(path2)
    .Attach()
    .CreateAsync();

암호화된 데이터 읽기 및 쓰기

데이터가 암호화되는 방법

문서가 Azure Cosmos DB에 기록될 때마다 SDK는 암호화 정책을 검색하여 암호화해야 하는 속성과 방법을 파악합니다. 암호화 결과는 base 64 문자열입니다.

복잡한 유형의 암호화:

  • 암호화할 속성이 JSON 배열인 경우 해당 배열의 모든 항목이 암호화됩니다.

  • 암호화할 속성이 JSON 개체인 경우 해당 개체의 리프 값만 암호화됩니다. 중간 하위 속성 이름은 일반 텍스트 형식으로 유지됩니다.

암호화된 항목 읽기

지점 읽기(ID 및 파티션 키로 단일 항목 가져오기) 또는 쿼리를 실행하거나 변경 피드를 읽을 때 암호화된 속성의 암호를 해독하는 데 명시적인 작업이 필요하지 않습니다. 그 이유는 다음과 같습니다.

  • SDK는 암호 해독이 필요한 속성을 파악하기 위해 암호화 정책을 조회합니다.
  • 암호화 결과는 값의 원래 JSON 형식을 포함합니다.

암호화된 속성의 확인과 후속 암호 해독은 요청에서 반환된 결과만을 기준으로 합니다. 예를 들어 property1이 암호화되었지만 property2(SELECT property1 AS property2 FROM c)에 프로젝션되는 경우 SDK에서 수신할 때 암호화된 속성으로 식별되지 않습니다.

암호화된 속성에 대한 쿼리 필터링

암호화된 속성을 필터링하는 쿼리를 작성할 때 쿼리 매개 변수의 값을 전달하기 위해 특정 방법을 사용해야 합니다. 이 메서드에서 사용하는 인수는 다음과 같습니다.

  • 쿼리 매개 변수의 이름입니다.
  • 쿼리에서 사용할 값입니다.
  • 암호화된 속성의 경로(암호화 정책에 정의됨).

Important

암호화된 속성은 같음 필터(WHERE c.property = @Value)에서만 사용할 수 있습니다. 이와 다르게 사용하면 예측할 수 없고 잘못된 쿼리 결과를 반환합니다. 이 제약 조건은 SDK의 다음 버전에서 더 효과적으로 적용됩니다.

var queryDefinition = container.CreateQueryDefinition(
    "SELECT * FROM c where c.property1 = @Property1");
await queryDefinition.AddParameterAsync(
    "@Property1",
    1234,
    "/property1");

속성의 하위 집합만 해독할 수 있는 경우 문서 읽기

클라이언트에 속성을 암호화하는 데 사용되는 모든 CMK에 대한 액세스 권한이 없는 경우에는 데이터를 다시 읽을 때 속성의 하위 집합만 암호 해독할 수 있습니다. 예를 들어, property1이 key1로 암호화되고 property2가 key2로 암호화된 경우 key1에 대한 액세스 권한만 있는 클라이언트 애플리케이션은 여전히 데이터를 읽을 수 있지만 property2는 읽을 수 없습니다. 이 경우 SQL 쿼리를 통해 데이터를 읽고 클라이언트가 암호 해독할 수 없는 속성을 제거해야 합니다. SELECT c.property1, c.property3 FROM c

CMK 회전

현재 CMK가 손상되었다고 의심되는 경우 CMK를 "회전"할 수 있습니다(즉, 현재 CMK 대신 새 CMK 사용). CMK를 정기적으로 회전하는 일반적인 보안 방법이기도 합니다. 이 회전을 수행하려면 특정 DEK를 래핑하는 데 사용해야 하는 새 CMK의 키 식별자만 제공하면 됩니다. 이 작업은 데이터 암호화에 영향을 미치지 않고 DEK의 보호에 영향을 미칩니다. 회전이 완료될 때까지 이전 CMK에 대한 액세스 권한을 취소하면 안 됩니다.

await database.RewrapClientEncryptionKeyAsync(
    "my-key",
    new EncryptionKeyWrapMetadata(
        KeyEncryptionKeyResolverName.AzureKeyVault,
        "akvKey",
        "https://<my-key-vault>.vault.azure.net/keys/<new-key>/<version>",
        EncryptionAlgorithm.RsaOaep.ToString()));

DEK 회전

데이터 암호화 키 순환 수행은 턴키 기능으로 제공되지 않습니다. DEK를 업데이트하려면 이 키가 사용되는 모든 컨테이너를 검사하고 이 키로 암호화된 모든 속성을 다시 암호화해야 하기 때문입니다. 이 작업은 Azure Cosmos DB 서비스가 DEK의 일반 텍스트 값을 저장하거나 액세스하지 않기 때문에 클라이언트 쪽에서만 발생할 수 있습니다.

실제로 DEK 순환을 수행하려면 영향을 받는 컨테이너에서 새 컨테이너로 데이터 마이그레이션을 수행하면 됩니다. 새 컨테이너는 원래 컨테이너와 똑같은 방식으로 만들 수 있습니다. 이러한 데이터 마이그레이션에 도움이 되도록 GitHub에서 독립형 마이그레이션 도구를 찾을 수 있습니다.

암호화된 속성 추가

기존 암호화 정책에 암호화된 속성을 추가하는 것은 바로 위 섹션에서 설명한 것과 같은 이유로 지원되지 않습니다. 이 작업은 속성의 모든 인스턴스가 제대로 암호화되었는지 확인하기 위해 컨테이너 전체를 검사해야 하며 이는 클라이언트 쪽에서만 발생할 수 있는 작업입니다. DEK 순환과 마찬가지로 적절한 암호화 정책을 사용하여 새 컨테이너로 데이터 마이그레이션을 수행하여 암호화된 속성을 추가할 수 있습니다.

스키마 관점에서 새로운 암호화된 속성을 추가할 수 있는 방식에 유연성이 있는 경우 Azure Cosmos DB의 스키마에 구애받지 않는 특성을 활용할 수도 있습니다. 암호화 정책에 정의된 속성을 "속성 모음"으로 사용하는 경우 아래에 제약 조건 없이 속성을 추가할 수 있습니다. 예를 들어 property1이 암호화 정책에 정의되어 있고 처음에 문서에 property1.property2를 작성한다고 가정해 보겠습니다. 나중 단계에서 property3을 암호화된 속성으로 추가해야 하는 경우 문서에 property1.property3 작성을 시작할 수 있으며 새 속성도 자동으로 암호화됩니다. 이 방법에는 데이터 마이그레이션이 필요하지 않습니다.

다음 단계