使用用戶端加密保護您的訂閱內容
重要事項:此文件不適用於您目前選取的格式 廣告!
如果您是線上出版商,您可能仰賴訂閱者提供收入。您可能會使用 CSS 混淆 (display: none
) 在用戶端將優質內容阻擋在付費牆後方。
遺憾的是,更精通技術的人可以繞過這個方法。
或者,您可能會向使用者顯示完全缺乏優質內容的文件!一旦您的後端驗證使用者,就會提供全新的頁面。雖然這種方法更安全,但會耗費時間、資源和使用者滿意度。
透過在用戶端實作優質訂閱者驗證和內容解密,即可解決這兩個問題。使用此解決方案,具有優質存取權的使用者將能夠解密內容,而無需載入新頁面或等待後端回應!
設定總覽
若要實作用戶端解密,您將以下列方式結合對稱金鑰和公開金鑰加密
- 為每個文件建立隨機對稱金鑰,授予每個文件獨一無二的金鑰。
- 使用文件的對稱金鑰加密優質內容。
- 使用公鑰加密文件金鑰,使用 混合式加密協定來加密對稱金鑰。
- 使用
<amp-subscriptions>
和/或<amp-subscriptions-google>
元件,將加密的文件金鑰儲存在 AMP 文件內,與加密的優質內容放在一起。
AMP 文件本身儲存加密金鑰。這可防止加密文件與解碼金鑰分離。
運作方式
- AMP 會從使用者登陸的文件上的加密內容中剖析金鑰。
- 在提供優質內容時,AMP 會將文件中的加密對稱金鑰,作為使用者權利擷取的一部分,傳送給授權者。
- 授權者會決定使用者是否具有正確的權限。如果有的話,授權者會使用其公鑰/私鑰配對中的授權者私鑰解密文件對稱金鑰。然後,授權者會將文件金鑰傳回 amp-subscriptions 元件邏輯。
- AMP 會使用文件金鑰解密優質內容,並將其顯示給使用者!
實作步驟
請按照以下步驟將 AMP 加密處理與您的內部權利伺服器整合。
步驟 1:建立公鑰/私鑰配對
若要加密文件對稱金鑰,您需要擁有自己的公鑰/私鑰配對。公鑰加密是一種 混合式加密協定,特別是 P-256 橢圓曲線 ECIES 非對稱加密方法,搭配 AES-GCM (128 位元) 對稱加密方法。
我們要求使用 Tink 和 此非對稱金鑰類型 完成公鑰處理。若要建立您的私鑰-公鑰配對,請使用下列其中一種方法
- Tink 的 KeysetManager 類別
- Tinkey (Tink 的金鑰公用程式工具)
兩者都支援金鑰輪換。實作金鑰輪換可降低私鑰外洩的風險。
為了協助您開始建立非對稱金鑰,我們建立了此指令碼。其功能如下
- 建立新的 ECIES 與 AEAD 金鑰。
- 以純文字格式將公鑰輸出到輸出檔案。
- 將私鑰輸出到另一個輸出檔案。
- 在使用託管於 Google Cloud (GCP) 的金鑰加密產生的私鑰後,再寫入輸出檔案 (通常稱為 Envelope Encryption (信封加密))。
我們要求以 Tink Keyset 的 JSON 格式 儲存/發布您的公開 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"
授權者。它會執行
- 從
"local"
JSON 金鑰欄位剖析文件金鑰。 - 文件解密。
您必須在授權者中使用 Tink 來解密文件金鑰。若要使用 Tink 進行解密,請使用在「建立公鑰/私鑰配對」章節中產生的私鑰,例項化 HybridDecrypt 用戶端。在伺服器啟動時執行此操作,以獲得最佳效能。
您的 HybridDecrypt/授權者部署應大致符合您的金鑰輪換排程。這會建立所有產生的金鑰對 HybridDecrypt 用戶端的可用性。
Tink 具有廣泛的 文件 和 C++、Java、Go 和 Python 的 範例,可協助您開始伺服器端實作。
請求管理
當請求傳送至您的授權者時
- 剖析權利回呼 URL 中的 “crypt=” 參數。
- 使用 base64 解碼 “crypt=” 參數值。儲存在 URL 參數中的值是 base64 編碼的加密 JSON 物件。
- 一旦加密金鑰採用原始位元組格式,請使用 HybridDecrypt 的解密函式,使用您的私鑰解密金鑰。
- 如果解密成功,請將結果剖析為 JSON 物件。
- 驗證使用者是否具有存取 AccessRequirements JSON 欄位中所列權利之一的權限。
- 從解密的 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。