이 문서에서는 proto 형식으로 type.googleapis.com/google.crypto.tink.AesGcmHkdfStreamingKey
로 인코딩된 AES-GCM-HKDF 스트리밍 키로 표현되는 수학적 함수를 공식적으로 정의합니다.
이 암호화는 HRRV151을 대략적으로 기반으로 합니다. 보안 분석의 경우 HS202를 참고하세요.
키 및 매개변수
키는 다음 부분으로 설명됩니다 (이 문서의 모든 크기는 바이트 단위임).
- \(\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{DerivedKeySize}\) 인 균일한 무작위 문자열 \(\mathrm{Salt}\) 과 길이가 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을 사용합니다. 결과를 \(\mathrm{DerivedKey}\)라고 합니다.
메시지 분할
그런 다음 메시지 \(\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바이트이고 바이트 $b$ 는 $i < n-1$ 인 경우 0x00
이고 그렇지 않은 경우에는 0x01
입니다.
그런 다음 AES-GCM4를 사용하여 \(M_i\) 를 암호화합니다. 여기서 키는\(\mathrm{DerivedKey}\), 초기화 벡터는 \(\mathrm{IV}_i\), 연결된 데이터는 빈 문자열입니다. \(C_i\) 는 이 암호화의 결과입니다(즉, 링크된 AES-GCM 참조의 5.2.1.2 섹션에 있는 \(C\) 와 \(T\) 의 연결).
암호화된 세그먼트 연결
마지막으로 모든 세그먼트가 연결되어 최종 암호화 텍스트인 \(\mathrm{Header} \| C_0 \| \cdots \| C_{n-1}\)가 됩니다.
복호화
복호화는 암호화를 반전시킵니다. 헤더를 사용하여\(\mathrm{NoncePrefix}\)를 가져오고 암호 텍스트의 각 세그먼트를 개별적으로 복호화합니다.
API는 무작위 액세스를 허용하거나 파일의 끝을 검사하지 않고 파일의 시작 부분에 액세스할 수 있습니다 (일반적으로 허용됨). 이는 이전의 모든 암호문 블록과 나머지 암호문 블록을 복호화하지 않고도 \(C_i\)에서 \(M_i\) 를 복호화할 수 있기 때문에 의도적으로 설계된 것입니다.
그러나 API는 사용자가 파일 끝과 복호화 오류를 혼동하지 않도록 주의해야 합니다. 두 경우 모두 API는 오류를 반환해야 할 수 있으며 차이를 무시하면 공격자가 파일을 효과적으로 자르는 결과를 초래할 수 있습니다.
키의 직렬화 및 파싱
'Tink Proto' 형식으로 키를 직렬화하려면 먼저 매개변수를 명시적인 방식으로 aes_gcm_hkdf_streaming.proto에 제공된 proto에 매핑합니다. version
필드는 0으로 설정해야 합니다. 그런 다음 일반 프로토 직렬화를 사용하여 이를 직렬화하고 결과 문자열을 KeyData 프로토의 필드 값에 삽입합니다. type_url
필드를 type.googleapis.com/google.crypto.tink.AesGcmHkdfStreamingKey
로 설정합니다. 그런 다음 key_material_type
를 SYMMETRIC
로 설정하고 이를 키 세트에 삽입합니다. 일반적으로 output_prefix_type
를 RAW
로 설정합니다. 단, 키가 output_prefix_type
에 다른 값이 설정된 상태로 파싱된 경우 Tink는 RAW
또는 이전 값을 쓸 수 있습니다.
키를 파싱하려면 위의 프로세스를 반대로 진행합니다 (proto를 파싱할 때 일반적인 방식). key_material_type
필드는 무시됩니다. output_prefix_type
값은 무시되거나 output_prefix_type
이 RAW
과 다른 키는 거부될 수 있습니다. version
가 0과 다른 키는 거부되어야 합니다.
알려진 문제
위의 암호화 함수 구현은 포크 안전하지 않을 것으로 예상됩니다. 포크 안전성을 참고하세요.
참조
-
Hoang, Reyhanitabar, Rogaway, Vizar, 2015. 온라인 인증 암호화 및 nonce 재사용 악용 방지 CRYPTO 2015. https://eprint.iacr.org/2015/189 ↩
-
Hoang, Shen, 2020. Google Tink 라이브러리의 스트리밍 암호화 보안. https://eprint.iacr.org/2020/1019 ↩
-
RFC 5869 HMAC 기반 추출-확장 키 파생 함수 (HKDF)입니다. https://www.rfc-editor.org/rfc/rfc5869 ↩
-
NIST SP 800-38D 블록 암호 모드 권장사항: Galois/Counter Mode (GCM) 및 GMAC. https://csrc.nist.gov/pubs/sp/800/38/d/final ↩