我們建議在大多數資料加密用途中使用 AEAD 基本類型,並搭配 AES128_GCM 金鑰類型。
支援關聯資料的驗證式加密 (AEAD) 是最簡單且最適合大多數用途的原語。AEAD 可提供保密性和真實性,並確保訊息一律具有不同的密文 (加密輸出),即使明文 (加密輸入) 相同也一樣。這是一種對稱式演算法,使用單一金鑰進行加密和解密。
下列範例說明如何開始使用 AEAD 元件:
C++
// A command-line utility for testing Tink AEAD. #include <iostream> #include <memory> #include <string> #include "absl/flags/flag.h" #include "absl/flags/parse.h" #include "absl/log/absl_check.h" #include "absl/status/status.h" #include "absl/status/statusor.h" #include "absl/strings/string_view.h" #include "tink/aead.h" #include "tink/aead/config_v0.h" #include "util/util.h" #include "tink/keyset_handle.h" ABSL_FLAG(std::string, keyset_filename, "", "Keyset file in JSON format"); ABSL_FLAG(std::string, mode, "", "Mode of operation {encrypt|decrypt}"); ABSL_FLAG(std::string, input_filename, "", "Filename to operate on"); ABSL_FLAG(std::string, output_filename, "", "Output file name"); ABSL_FLAG(std::string, associated_data, "", "Associated data for AEAD (default: empty"); namespace { using ::crypto::tink::Aead; using ::crypto::tink::KeysetHandle; constexpr absl::string_view kEncrypt = "encrypt"; constexpr absl::string_view kDecrypt = "decrypt"; void ValidateParams() { // ... } } // namespace namespace tink_cc_examples { // AEAD example CLI implementation. absl::Status AeadCli(absl::string_view mode, const std::string& keyset_filename, const std::string& input_filename, const std::string& output_filename, absl::string_view associated_data) { // Read the keyset from file. absl::StatusOr<std::unique_ptr<KeysetHandle>> keyset_handle = ReadJsonCleartextKeyset(keyset_filename); if (!keyset_handle.ok()) return keyset_handle.status(); // Get the primitive. absl::StatusOr<std::unique_ptr<Aead>> aead = (*keyset_handle) ->GetPrimitive<crypto::tink::Aead>(crypto::tink::ConfigAead2026()); if (!aead.ok()) return aead.status(); // Read the input. absl::StatusOr<std::string> input_file_content = ReadFile(input_filename); if (!input_file_content.ok()) return input_file_content.status(); // Compute the output. std::string output; if (mode == kEncrypt) { absl::StatusOr<std::string> encrypt_result = (*aead)->Encrypt(*input_file_content, associated_data); if (!encrypt_result.ok()) return encrypt_result.status(); output = encrypt_result.value(); } else { // operation == kDecrypt. absl::StatusOr<std::string> decrypt_result = (*aead)->Decrypt(*input_file_content, associated_data); if (!decrypt_result.ok()) return decrypt_result.status(); output = decrypt_result.value(); } // Write the output to the output file. return WriteToFile(output, output_filename); } } // namespace tink_cc_examples int main(int argc, char** argv) { absl::ParseCommandLine(argc, argv); ValidateParams(); std::string mode = absl::GetFlag(FLAGS_mode); std::string keyset_filename = absl::GetFlag(FLAGS_keyset_filename); std::string input_filename = absl::GetFlag(FLAGS_input_filename); std::string output_filename = absl::GetFlag(FLAGS_output_filename); std::string associated_data = absl::GetFlag(FLAGS_associated_data); std::clog << "Using keyset from file " << keyset_filename << " to AEAD-" << mode << " file " << input_filename << " with associated data '" << associated_data << "'." << '\n'; std::clog << "The resulting output will be written to " << output_filename << '\n'; ABSL_CHECK_OK(tink_cc_examples::AeadCli(mode, keyset_filename, input_filename, output_filename, associated_data)); return 0; }
Go
import ( "bytes" "fmt" "log" "github.com/tink-crypto/tink-go/v2/aead" "github.com/tink-crypto/tink-go/v2/insecurecleartextkeyset" "github.com/tink-crypto/tink-go/v2/keyset" ) func Example() { // A keyset created with "tinkey create-keyset --key-template=AES256_GCM". Note // that this keyset has the secret key information in cleartext. jsonKeyset := `{ "key": [{ "keyData": { "keyMaterialType": "SYMMETRIC", "typeUrl": "type.googleapis.com/google.crypto.tink.AesGcmKey", "value": "GiBWyUfGgYk3RTRhj/LIUzSudIWlyjCftCOypTr0jCNSLg==" }, "keyId": 294406504, "outputPrefixType": "TINK", "status": "ENABLED" }], "primaryKeyId": 294406504 }` // Create a keyset handle from the cleartext keyset in the previous // step. The keyset handle provides abstract access to the underlying keyset to // limit the exposure of accessing the raw key material. // // WARNING: In practice, it is unlikely you will want to use a insecurecleartextkeyset, // as it implies that your key material is passed in cleartext, which is a security risk. // Consider encrypting it with a remote key in Cloud KMS, AWS KMS or HashiCorp Vault. // See https://developers.google.com/tink/key-management-overview. keysetHandle, err := insecurecleartextkeyset.Read( keyset.NewJSONReader(bytes.NewBufferString(jsonKeyset))) if err != nil { log.Fatal(err) } // Retrieve the AEAD primitive we want to use from the keyset handle. primitive, err := aead.New(keysetHandle) if err != nil { log.Fatal(err) } // Use the primitive to encrypt a message. In this case the primary key of the // keyset will be used (which is also the only key in this example). plaintext := []byte("message") associatedData := []byte("associated data") ciphertext, err := primitive.Encrypt(plaintext, associatedData) if err != nil { log.Fatal(err) } // Use the primitive to decrypt the message. Decrypt finds the correct key in // the keyset and decrypts the ciphertext. If no key is found or decryption // fails, it returns an error. decrypted, err := primitive.Decrypt(ciphertext, associatedData) if err != nil { log.Fatal(err) } fmt.Println(string(decrypted)) // Output: message }
Java
package aead; import static java.nio.charset.StandardCharsets.UTF_8; import com.google.crypto.tink.Aead; import com.google.crypto.tink.InsecureSecretKeyAccess; import com.google.crypto.tink.KeysetHandle; import com.google.crypto.tink.RegistryConfiguration; import com.google.crypto.tink.TinkJsonProtoKeysetFormat; import com.google.crypto.tink.aead.AeadConfig; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; /** * A command-line utility for encrypting small files with AEAD. * * <p>It loads cleartext keys from disk - this is not recommended! * * <p>It requires the following arguments: * * <ul> * <li>mode: Can be "encrypt" or "decrypt" to encrypt/decrypt the input to the output. * <li>key-file: Read the key material from this file. * <li>input-file: Read the input from this file. * <li>output-file: Write the result to this file. * <li>[optional] associated-data: Associated data used for the encryption or decryption. */ public final class AeadExample { private static final String MODE_ENCRYPT = "encrypt"; private static final String MODE_DECRYPT = "decrypt"; public static void main(String[] args) throws Exception { if (args.length != 4 && args.length != 5) { System.err.printf("Expected 4 or 5 parameters, got %d\n", args.length); System.err.println( "Usage: java AeadExample encrypt/decrypt key-file input-file output-file" + " [associated-data]"); System.exit(1); } String mode = args[0]; Path keyFile = Paths.get(args[1]); Path inputFile = Paths.get(args[2]); Path outputFile = Paths.get(args[3]); byte[] associatedData = new byte[0]; if (args.length == 5) { associatedData = args[4].getBytes(UTF_8); } // Register all AEAD key types with the Tink runtime. AeadConfig.register(); // Read the keyset into a KeysetHandle. KeysetHandle handle = TinkJsonProtoKeysetFormat.parseKeyset( new String(Files.readAllBytes(keyFile), UTF_8), InsecureSecretKeyAccess.get()); // Get the primitive. Aead aead = handle.getPrimitive(RegistryConfiguration.get(), Aead.class); // Use the primitive to encrypt/decrypt files. if (MODE_ENCRYPT.equals(mode)) { byte[] plaintext = Files.readAllBytes(inputFile); byte[] ciphertext = aead.encrypt(plaintext, associatedData); Files.write(outputFile, ciphertext); } else if (MODE_DECRYPT.equals(mode)) { byte[] ciphertext = Files.readAllBytes(inputFile); byte[] plaintext = aead.decrypt(ciphertext, associatedData); Files.write(outputFile, plaintext); } else { System.err.println("The first argument must be either encrypt or decrypt, got: " + mode); System.exit(1); } } private AeadExample() {} }
Obj-C
Python
import tink from tink import aead from tink import secret_key_access def example(): """Encrypt and decrypt using AEAD.""" # Register the AEAD key managers. This is needed to create an Aead primitive # later. aead.register() # A keyset created with "tinkey create-keyset --key-template=AES256_GCM". Note # that this keyset has the secret key information in cleartext. keyset = r"""{ "key": [{ "keyData": { "keyMaterialType": "SYMMETRIC", "typeUrl": "type.googleapis.com/google.crypto.tink.AesGcmKey", "value": "GiBWyUfGgYk3RTRhj/LIUzSudIWlyjCftCOypTr0jCNSLg==" }, "keyId": 294406504, "outputPrefixType": "TINK", "status": "ENABLED" }], "primaryKeyId": 294406504 }""" # Create a keyset handle from the cleartext keyset in the previous # step. The keyset handle provides abstract access to the underlying keyset to # limit access of the raw key material. WARNING: In practice, it is unlikely # you will want to use a cleartext_keyset_handle, as it implies that your key # material is passed in cleartext, which is a security risk. keyset_handle = tink.json_proto_keyset_format.parse( keyset, secret_key_access.TOKEN ) # Retrieve the Aead primitive we want to use from the keyset handle. primitive = keyset_handle.primitive(aead.Aead) # Use the primitive to encrypt a message. In this case the primary key of the # keyset will be used (which is also the only key in this example). ciphertext = primitive.encrypt(b'msg', b'associated_data') # Use the primitive to decrypt the message. Decrypt finds the correct key in # the keyset and decrypts the ciphertext. If no key is found or decryption # fails, it raises an error. output = primitive.decrypt(ciphertext, b'associated_data')
AEAD
AEAD (Authenticated Encryption with Associated Data) 基本體是最常見的資料加密基本體,適用於大多數需求。
AEAD 具有下列屬性:
- Secrecy:除了長度之外,沒有人知道任何有關明文的資訊。
- 真實性:無法在不被偵測到的情況下,變更密文底層的加密明文。
- 對稱:使用相同金鑰加密明文及解密密文。
- 隨機化:加密方式為隨機。兩個具有相同明文的訊息會產生不同的密文。攻擊者無法得知哪個密文對應到特定明文。如要避免這種情況,請改用「Deterministic AEAD」。
相關資料
AEAD 可用於將密文繫結至特定相關聯資料。假設資料庫包含 user-id 和 encrypted-medical-history 欄位。在這種情況下,加密 encrypted-medical-history 時,可以使用 user-id 做為關聯資料。這樣一來,攻擊者就無法將醫療記錄從一位使用者轉移到另一位使用者。
關聯資料為選填項目。如果指定了相關聯的資料,只有在加密和解密呼叫中傳遞相同的相關聯資料時,解密才會成功。
選擇金鑰類型
雖然我們建議在大多數情況下使用 AES128_GCM,但您也可以根據不同需求選擇各種金鑰類型。AES128 提供 128 位元安全性,AES256 則提供 256 位元安全性。
選擇模式時,有兩項重要的安全性限制:
- QPS:使用相同金鑰加密的訊息數量。
- 郵件大小:郵件有多大?
支援的金鑰類型:
- AES-CTR-HMAC (AES128_CTR_HMAC_SHA256、AES256_CTR_HMAC_SHA256) 搭配 16 位元組的初始向量 (IV) 是最保守的模式,具有良好的界限。
- 金鑰承諾。
- AES-EAX (AES128_EAX、AES256_EAX) 稍微不保守,速度也比 AES128_CTR_HMAC_SHA256 稍快。
- 未提交金鑰的 MLGR。
- AES-GCM (AES128_GCM、AES256_GCM) 通常是最快的模式,對訊息數量 (232) 和訊息大小 (每則訊息約 64 GB) 有最嚴格的限制。超過這些限制時,AES-GCM 會發生嚴重錯誤,洩漏明文和 AES-GCM 內部金鑰的驗證部分。
- AES-GCM-SIV (AES128_GCM_SIV、AES256_GCM_SIV) [RFC8452] 可防止意外誤用 Nonce,效能幾乎與 AES-GCM 相同。對於採用隨機 IV 的 AES-GCM-SIV,訊息數量和大小的限制遠高於 AES-GCM。如果超過這些限制,AES-GCM-SIV 的失敗情況較不嚴重:只會洩漏兩則訊息相同。AES-GCM-SIV 較安全,但實際使用率較低。
- 隨機 Nonce 的界限:
- 232 則訊息,每則訊息的純文字大小上限為 8 GiB
- 248 則訊息,每則明文最多 32 MiB
- 264 則訊息,每則明文最多 128 KiB
- 如要在 Java 中使用,請安裝 Conscrypt。
- 未提交金鑰 ADGKLS。
- 隨機 Nonce 的界限:
- 與 AES-GCM 相比,XChaCha20-Poly1305 (XCHACHA20_POLY1305) 的郵件數量和郵件大小限制高出許多,但如果失敗 (機率極低),也會洩漏金鑰資料。由於未採用硬體加速功能,因此在可使用硬體加速功能的情況下,速度可能會比 AES 模式慢。
- 非金鑰繫結 LGR。
進一步瞭解 AEAD 密文的傳輸格式。
安全保障
AEAD 實作提供:
- CCA2 安全性。
- 驗證強度至少 80 位元。
- 至少可加密 232 則訊息,總共 250 個位元組。如果攻擊者選擇最多 232 個明文或密文,攻擊成功的機率不會超過 2-32。