AES-GCM-HKDF 스트리밍 AEAD

이 문서는 AES-GCM-HKDF 스트리밍 키 (type.googleapis.com/google.crypto.tink.AesGcmHkdfStreamingKey로 proto 형식으로 인코딩된 AES-GCM-HKDF 스트리밍 키)로 표시되는 수학 함수를 공식적으로 정의합니다.

이 암호화는 대략 [HRRV15]1를 기반으로 합니다. 보안 분석은 [HS20]2를 참고하세요.

키 및 매개변수

키는 다음과 같은 부분으로 설명됩니다 (이 문서의 모든 크기는 바이트 단위).

  • \(\mathrm{KeyValue}\): 바이트 문자열
  • \(\mathrm{CiphertextSegmentSize} \in \{1, 2, \ldots, 2^{31}-1\}\).
  • \(\mathrm{DerivedKeySize} \in \{16, 32\}\).
  • \(\mathrm{HkdfHashType} \in \{\mathrm{SHA1}, \mathrm{SHA256}, \mathrm{SHA512}\}\).

유효한 키는 추가로 다음 속성을 충족합니다.

  • \(\mathrm{len}(\mathrm{KeyValue}) \geq \mathrm{DerivedKeySize}\).
  • \(\mathrm{CiphertextSegmentSize} > \mathrm{DerivedKeySize} + 24\) (이는 나중에 설명하는\(\mathrm{len}(\mathrm{Header}) + 16\) 와 같습니다.)

이러한 속성을 충족하지 않는 키는 키가 파싱되거나 해당 프리미티브가 생성될 때 Tink에서 거부됩니다.

암호화 함수

관련 데이터로 \(\mathrm{Msg}\) 메시지를 암호화\(\mathrm{AssociatedData}\)하기 위해 헤더를 만들고 메시지를 세그먼트로 분할한 후 각 세그먼트를 암호화한 후 세그먼트를 연결합니다. 이러한 단계는 다음과 같습니다.

헤더 만들기

헤더를 만들기 위해 먼저 \(\mathrm{Salt}\)길이 \(\mathrm{DerivedKeySize}\)의 균일한 임의의 문자열을 선택합니다. 다음으로 길이 7인 임의의 균일한\(\mathrm{NoncePrefix}\) 문자열을 선택합니다.

그런 다음\(\mathrm{Header} := \mathrm{len}(\mathrm{Header}) \| \mathrm{Salt} \| \mathrm{NoncePrefix}\)를 설정합니다. 여기서 헤더 길이가 단일 바이트로 인코딩됩니다.\(\mathrm{len}(\mathrm{Header}) \in \{24, 40\}\)을 참고하세요.

그런 다음 \(\mathrm{HkdfHashType}\), 입력 \(\mathrm{ikm} := \mathrm{KeyValue}\),\(\mathrm{salt} := \mathrm{Salt}\),\(\mathrm{info} := \mathrm{AssociatedData}\)에 의해 주어진 해시 함수와 출력 길이 \(\mathrm{DerivedKeySize}\)로 HKDF3을 사용합니다. 그 결과를 \(k\)라고 합니다.

메일 분할

그런 다음 \(\mathrm{Msg}\) 메시지가\(\mathrm{Msg} = M_0 \| M_1 \| \cdots \| M_{n-1}\)(으)로 분할됩니다.

길이는 다음을 충족하도록 선택됩니다.

  • \(\mathrm{len}(M_0) \in \{0,\ldots, \mathrm{CiphertextSegmentSize} - \mathrm{len}(\mathrm{Header}) - \mathrm{16}\}\).
  • \(n>1\)인 경우 \(\mathrm{len}(M_1), \ldots, \mathrm{len}(M_{n-1}) \in \{1,\ldots, \mathrm{CiphertextSegmentSize} - \mathrm{16}\}\)입니다.
  • \(n>1\)인 경우 \(\mathrm{len}(M_{0}), \ldots, \mathrm{len}(M_{n-2})\) 은 제약 조건에 따라 위의 최대 길이를 가져야 합니다.

이 분할에서 \(n\) 최대 \(2^{32}\)일 수 있습니다. 그렇지 않으면 암호화가 실패합니다.

블록 암호화

세그먼트 \(M_i\)를 암호화하기 위해 먼저\(\mathrm{IV}_i := \mathrm{NoncePrefix} \| \mathrm{i} \| b\)을 계산하고, 여기서\(\mathrm{i}\) big-endian 인코딩을 사용하여 4바이트로 인코딩하고, $i < n-1$ 인 경우 바이트 $b$ 를 0x00로 설정하고 그렇지 않은 경우 0x01을 설정합니다.

그런 다음 AES GCM4를 사용하여 \(M_i\) 를 암호화합니다. 여기서 키는\(\mathrm{DerivedKey}\), 초기화 벡터는 \(\mathrm{IV}_i\), 연결된 데이터 \(A\) 는 빈 문자열입니다. \(C_i\) 를 이 암호화의 결과로 설정합니다(즉, 위 참조에서 \(C\) 와 \(T\) 를 연결).

세그먼트 연결

마지막으로 모든 세그먼트는 최종 암호문인\(\mathrm{Header} \| C_0 \| \cdots \| C_{n-1}\)로 연결됩니다.

복호화

복호화는 단순히 암호화를 반전시킵니다. 헤더를 사용하여 nonce를 가져오고 암호문의 각 세그먼트를 개별적으로 복호화합니다.

API는 무작위 액세스 또는 파일 끝을 검사하지 않고 파일의 시작 부분에 대한 액세스를 허용할 수 있습니다 (일반적으로 허용함). 이는 이전의 모든 암호문 블록과 나머지 암호문 블록을 복호화하지 않고 \(C_i\)에서 \(M_i\) 를 복호화하는 것이 가능하므로 의도적인 것입니다.

하지만 API에서는 사용자가 파일 끝 오류와 복호화 오류를 혼동하지 않도록 주의해야 합니다. 두 경우 모두 API가 오류를 반환해야 하며, 차이를 무시하면 공격자가 파일을 효과적으로 자를 수 있습니다.

키 직렬화 및 파싱

'Tink Proto' 형식으로 키를 직렬화하려면 먼저 aes_gcm_hkdf_streaming.proto에 제공된 proto에 명확한 방식으로 매개변수를 매핑합니다. version 필드는 0으로 설정해야 합니다. 그런 다음 일반 proto 직렬화를 사용하여 직렬화하고 결과 문자열을 KeyData proto 필드 값에 삽입합니다. type_url 필드를 type.googleapis.com/google.crypto.tink.AesGcmHkdfStreamingKey로 설정합니다. 그런 다음 key_material_typeSYMMETRIC로 설정하고 이를 키 세트에 삽입합니다. 일반적으로 output_prefix_typeRAW로 설정합니다. 예외적으로 키가 output_prefix_type에 설정된 다른 값으로 파싱된 경우 Tink는 RAW 또는 이전 값을 쓸 수 있습니다.

키를 파싱하려면 proto를 파싱할 때 일반적으로 사용하는 방식으로 위의 프로세스를 반대로 진행합니다. key_material_type 필드는 무시됩니다. output_prefix_type 값은 무시되거나 output_prefix_typeRAW와 다른 키가 거부될 수 있습니다. version가 0이 아닌 키는 거부해야 합니다.

알려진 문제

위의 암호화 함수를 구현해도 포크되지 않을 수 있습니다. 포크 안전을 참고하세요.

참조


  1. [HRRV15] 호앙, 레이하니타바, 로가웨이, 비자르. 온라인 인증 암호화 및 nonce-reuse 오용 방지 CRYPTO 2015. https://eprint.iacr.org/2015/189 

  2. [HS20] Google Tink 라이브러리의 스트리밍 암호화 보안 Hoang, Shen, 2020년. https://eprint.iacr.org/2020/1019 

  3. [HKDF] HMAC 기반 추출 및 확장 키 파생 함수 (HKDF), RFC 5869. https://www.rfc-editor.org/rfc/rfc5869 

  4. NIST SP 800-38D, 블록 암호화 작동 모드 권장사항: GCM (Galois/Counter Mode) 및 GMAC