AMP

使用用戶端加密保護您的訂閱內容

重要事項:此文件不適用於您目前選取的格式 廣告

如果您是線上出版商,您可能仰賴訂閱者提供收入。您可能會使用 CSS 混淆 (display: none) 在用戶端將優質內容阻擋在付費牆後方。

遺憾的是,更精通技術的人可以繞過這個方法。

或者,您可能會向使用者顯示完全缺乏優質內容的文件!一旦您的後端驗證使用者,就會提供全新的頁面。雖然這種方法更安全,但會耗費時間、資源和使用者滿意度。

透過在用戶端實作優質訂閱者驗證和內容解密,即可解決這兩個問題。使用此解決方案,具有優質存取權的使用者將能夠解密內容,而無需載入新頁面或等待後端回應!

設定總覽

若要實作用戶端解密,您將以下列方式結合對稱金鑰和公開金鑰加密

  1. 為每個文件建立隨機對稱金鑰,授予每個文件獨一無二的金鑰。
  2. 使用文件的對稱金鑰加密優質內容。
    金鑰是對稱的,以便相同的金鑰可以加密和解密內容。
  3. 使用公鑰加密文件金鑰,使用 混合式加密協定來加密對稱金鑰。
  4. 使用 <amp-subscriptions> 和/或 <amp-subscriptions-google> 元件,將加密的文件金鑰儲存在 AMP 文件內,與加密的優質內容放在一起。

AMP 文件本身儲存加密金鑰。這可防止加密文件與解碼金鑰分離。

運作方式

  1. AMP 會從使用者登陸的文件上的加密內容中剖析金鑰。
  2. 在提供優質內容時,AMP 會將文件中的加密對稱金鑰,作為使用者權利擷取的一部分,傳送給授權者。
  3. 授權者會決定使用者是否具有正確的權限。如果有的話,授權者會使用其公鑰/私鑰配對中的授權者私鑰解密文件對稱金鑰。然後,授權者會將文件金鑰傳回 amp-subscriptions 元件邏輯
  4. AMP 會使用文件金鑰解密優質內容,並將其顯示給使用者!

實作步驟

請按照以下步驟將 AMP 加密處理與您的內部權利伺服器整合。

步驟 1:建立公鑰/私鑰配對

若要加密文件對稱金鑰,您需要擁有自己的公鑰/私鑰配對。公鑰加密是一種 混合式加密協定,特別是 P-256 橢圓曲線 ECIES 非對稱加密方法,搭配 AES-GCM (128 位元) 對稱加密方法。

我們要求使用 Tink此非對稱金鑰類型 完成公鑰處理。若要建立您的私鑰-公鑰配對,請使用下列其中一種方法

兩者都支援金鑰輪換。實作金鑰輪換可降低私鑰外洩的風險。

為了協助您開始建立非對稱金鑰,我們建立了此指令碼。其功能如下

  1. 建立新的 ECIES 與 AEAD 金鑰。
  2. 以純文字格式將公鑰輸出到輸出檔案。
  3. 將私鑰輸出到另一個輸出檔案。
  4. 在使用託管於 Google Cloud (GCP) 的金鑰加密產生的私鑰後,再寫入輸出檔案 (通常稱為 Envelope Encryption (信封加密))。

我們要求以 Tink KeysetJSON 格式 儲存/發布您的公開 Tink Keyset。這可讓其他 AMP 提供的工具無縫運作。我們的指令碼已使用此格式輸出公鑰。

步驟 2:加密文章

決定您要手動加密優質內容,還是自動加密優質內容。

手動加密

我們要求使用 Tink 的 AES-GCM 128 對稱方法來加密優質內容。用於加密優質內容的對稱文件金鑰應為每個文件專屬。將文件金鑰新增至 JSON 物件,該物件包含 base64 編碼純文字格式的金鑰,以及存取文件加密內容所需的 SKU。

下方的 JSON 物件包含 base64 編碼純文字格式的金鑰和 SKU 範例。

{
  AccessRequirements: ['thenewsynews.com:premium'],
  Key: 'aBcDef781-2-4/sjfdi',
}

使用在「建立公鑰/私鑰配對」中產生的公鑰,加密上述 JSON 物件。

將加密結果新增為金鑰 "local" 的值。將金鑰-值配對放在包裝在 <script type="application/json" cryptokeys=""> 標記內的 JSON 物件中。將標記放在文件的 head 中。

<head>
...
<script type="application/json" cryptokeys="">
{
  "local": ['y0^r$t^ff'], // This is for your environment
  "google.com": ['g00g|e$t^ff'], // This is for Google's environment
}
</script></head>

您必須使用本機環境和 Google 的公鑰 加密文件金鑰。包含 Google 的公鑰可讓 Google AMP 快取提供您的文件。您必須例項化 Tink Keyset,以從其網址接受 Google 公鑰

https://news.google.com/swg/encryption/keys/prod/tink/public\_key

Google 的公鑰是 Tink Keyset,採用 JSON 格式。如需使用此金鑰組的範例,請參閱 此處

延伸閱讀:請參閱運作中的加密 AMP 文件範例。

自動加密

使用我們的 指令碼 加密文件。此指令碼接受 HTML 文件,並加密 <section subscriptions-section="content" encrypted> 標記內的所有內容。使用傳遞給它的網址中的公鑰,此指令碼會加密由指令碼建立的文件金鑰。使用此指令碼可確保所有內容都經過編碼和格式化,以進行提供。如需使用此指令碼的進一步指示,請參閱此處

步驟 3:整合授權者

當使用者擁有正確的權利時,您需要更新授權者以解密文件金鑰。amp-subscriptions 元件會透過 “crypt=” URL 參數,自動將加密的文件金鑰傳送給 "local" 授權者。它會執行

  1. "local" JSON 金鑰欄位剖析文件金鑰。
  2. 文件解密。

您必須在授權者中使用 Tink 來解密文件金鑰。若要使用 Tink 進行解密,請使用在「建立公鑰/私鑰配對」章節中產生的私鑰,例項化 HybridDecrypt 用戶端。在伺服器啟動時執行此操作,以獲得最佳效能。

您的 HybridDecrypt/授權者部署應大致符合您的金鑰輪換排程。這會建立所有產生的金鑰對 HybridDecrypt 用戶端的可用性。

Tink 具有廣泛的 文件 和 C++、Java、Go 和 Python 的 範例,可協助您開始伺服器端實作。

請求管理

當請求傳送至您的授權者時

  1. 剖析權利回呼 URL 中的 “crypt=” 參數。
  2. 使用 base64 解碼 “crypt=” 參數值。儲存在 URL 參數中的值是 base64 編碼的加密 JSON 物件。
  3. 一旦加密金鑰採用原始位元組格式,請使用 HybridDecrypt 的解密函式,使用您的私鑰解密金鑰。
  4. 如果解密成功,請將結果剖析為 JSON 物件。
  5. 驗證使用者是否具有存取 AccessRequirements JSON 欄位中所列權利之一的權限。
  6. 從解密的 JSON 物件的 “Key” 欄位傳回文件金鑰做為權利回應。在權利回應中新增名為 “decryptedDocumentKey” 的新欄位,加入已解密的文件金鑰。這會授予 AMP 框架存取權。

以下範例是概述上述描述步驟的虛擬程式碼片段

string decryptDocumentKey(string encryptedKey, List < string > usersEntitlements,
    HybridDecrypt hybridDecrypter) {
    // 1. Base64 decode the input encrypted key.
    bytes encryptedKeyBytes = base64.decode(encryptedKey);
    // 2. Try to decrypt the encrypted key.
    bytes decryptedKeyBytes;
    try {
        decryptedKeyBytes = hybridDecrypter.decrypt(
            encryptedKeyBytes, null /* contextInfo */ );
    } catch (error e) {
        // Decryption error occurred. Handle it how you want.
        LOG("Error occurred decrypting: ", e);
        return "";
    }
    // 3. Parse the decrypted text into a JSON object.
    string decryptedKey = new string(decryptedKeyBytes, UTF_8);
    json::object decryptedParsedJson = JsonParser.parse(decryptedKey);
    // 4. Check to see if the requesting user has the entitlements specified in               
    //    the AccessRequirements section of the JSON object.
    for (entitlement in usersEntitlements) {
        if (decryptedParsedJson["AccessRequirements"]
            .contains(entitlement)) {
            // 5. Return the document key if the user has entitlements.
            return decryptedParsedJson["Key"];
        }
    }
    // User doesn't have correct requirements, return empty string.
    return "";
}

JsonResponse getEntitlements(string requestUri) {
    // Do normal handling of entitlements here…
    List < string > usersEntitlements = getUsersEntitlementInfo();

    // Check if request URI has "crypt" parameter.
    String documentCrypt = requestUri.getQueryParameters().getFirst("crypt");

    // If URI has "crypt" param, try to decrypt it.
    string documentKey;
    if (documentCrypt != null) {
        documentKey = decryptDocumentKey(
            documentCrypt,
            usersEntitlements,
            this.hybridDecrypter_);
    }

    // Construct JSON response.
    JsonResponse response = JsonResponse {
        signedEntitlements: getSignedEntitlements(),
        isReadyToPay: getIsReadyToPay(),
    };
    if (!documentKey.empty()) {
        response.decryptedDocumentKey = documentKey;
    }
    return response;
}

相關資源

請查看 Tink Github 頁面上的文件和範例。

所有輔助指令碼都在 subscriptions-project/encryption Github 儲存庫中。

進一步支援

如有任何問題、意見或疑慮,請提交 Github Issue