AMP

Bảo vệ nội dung trả phí của bạn với mã hóa phía máy khách

Important: this documentation is not applicable to your currently selected format email!

Nếu bạn là một tờ báo trực tuyến, doanh thu của bạn có thể phụ thuộc vào các thuê bao. Bạn có thể chặn nội dung cao cấp đằng sau một bức tường thanh toán trên máy khách sử dụng Che CSS (display: none).

Tiếc là, những người thạo công nghệ có thể vượt qua lớp che này.

Thay vào đó, bạn sẽ hiển thị cho người dùng một tài liệu hoàn toàn không có nội dung cao cấp! Phục vụ một trang hoàn toàn mới sau khi backend của bạn đã xác thực cho người dùng. Tuy phương thức này bảo mật hơn, nhưng nó cũng tốn nhiều thời gian, tài nguyên và khiến người dùng không hài lòng.

Giải quyết cả hai vấn đề này bằng cách triển khai xác thực thuê bao cao cấp và giải mã nội dung phía máy khách. Với giải pháp này, người dùng có quyền truy cập cao cấp sẽ có thể giải mã nội dung mà không cần tải một trang mới hoặc chờ backend hồi đáp!

Tổng quan về thiết lập

Để triển khai việc giải mã phía máy khách, bạn sẽ kết hợp cả mã hóa symmetric-key (khóa đối xứng) và public-key (khóa công khai) như sau:

  1. Tạo một khóa đối xứng cho mỗi tài liệu, mỗi khóa này sẽ cấp cho tài liệu một khóa độc đáo.
  2. Mã hóa nội dung cao cấp với khóa đối xứng của tài liệu đó.
    Khóa này có tính đối xứng để cho phép nó vừa mã hóa vừa giải mã nội dung.
  3. Mã hóa khóa của tài liệu bằng khóa công khai, sử dụng giao thức mã hóa lai để mã hóa các khóa đối xứng.
  4. Sử dụng các thành phần <amp-subscriptions> và/hoặc <amp-subscriptions-google>, lưu trữ khóa mã hóa của tài liệu vào trong tài liệu AMP, cùng với nội dung cao cấp đã được mã hóa.

Tài liệu AMP sẽ lưu trữ khóa mã hóa trong nội dung của nó. Việc này ngăn chặn khả năng tách rời tài liệu được mã hóa với khóa giải mã cho nó.

Nguyên lý hoạt động là gì?

  1. AMP đọc khóa này từ nội dung được mã hóa trên tài liệu mà người dùng truy cập.
  2. Khi phục vụ nội dung cao cấp, AMP sẽ gửi khóa đối xứng được mã hóa từ tài liệu đến bộ xác thực như một phần trong quy trình truy xuất quyền lợi của người dùng.
  3. Bộ xác thực quyết định là liệu người dùng có quyền phù hợp hay chưa. Nếu có, bộ xác thực sẽ giải mã khóa đối xứng của tài liệu bằng khóa riêng tư của bộ xác thực từ cặp khóa công khai/riêng tư của nó. Sau đó, bộ xác thực sẽ trả khóa tài liệu về cho lôgic thành phần amp-subscriptions.
  4. AMP giải mã nội dung cao cấp bằng khóa tài liệu này và hiển thị nó cho người dùng!

Các bước triển khai

Làm theo các bước dưới đây để tích hợp việc xử lý mã hóa AMP vào máy chủ quyền lợi nội bộ của bạn.

Bước 1: Tạo một cặp khóa công khai/riêng tư

Để mã hóa khóa đối xứng của tài liệu, bạn cần có cặp khóa công khai/riêng tư của mình. Mã hóa cho khóa công khai là một giao thức mã hóa lai, cụ thể là phương pháp mã hóa bất đối xứng P-256 Elliptic Curve ECIES với một phương pháp mã hóa đối xứng AES-GCM (128-bit).

Chúng ta cần việc xử lý khóa công khai được thực hiện bằng Tink sử dụng loại khóa bất đối xứng này. Để tạo cặp khóa riêng tư-công khai, hãy sử dụng:

Cả hai đều hỗ trợ chuyển khóa. Việc triển khai chuyển khóa sẽ giới hạn lỗ hổng bảo mật khi có một khóa riêng tư bị xâm phạm.

Để giúp bạn bắt đầu tạo các khóa bất đối xứng, chúng tôi đã tạo kịch bản này. Kịch bản này:

  1. Tạo một ECIES mới với khóa AEAD.
  2. Xuất khóa công khai dưới dạng văn bản thường ra một tập tin bên ngoài.
  3. Xuất khóa riêng tư ra một tập tin bên ngoài khác.
  4. Mã hóa khóa riêng tư đã tạo sử dụng một khóa được lưu trữ trên Google Cloud (GCP) trước khi ghi nó ra tập tin bên ngoài (thường được gọi là Mã hóa Phong bì).

Chúng tôi cần lưu trữ/phát hành Tink Keyset công khai của bạn trong định dạng JSON. Điều này cho phép các công cụ AMP khác có thể hoạt động liền mạch. Kịch bản của chúng tôi đã xuất khóa công khai trong định dạng này.

Bước 2: Mã hóa các bài viết

Hãy quyết định là bạn muốn mã hóa thủ công cho các nội dung cao cấp, hay tự động mã hóa nội dung cao cấp.

Mã hóa Thủ công

Chúng tôi cần phương pháp đối xứng AES-GCM 128 với Tink để mã hóa nội dung cao cấp. Khóa tài liệu đối xứng được sử dụng để mã hóa nội dung cao cấp cần là khóa duy nhất cho mỗi tài liệu. Thêm khóa tài liệu vào một đối tượng JSON có chứa khóa trong văn bản thường mã hóa base64, cũng như các SKU cần để truy cập nội dung được mã hóa trong tài liệu.

Đối tượng JSON dưới đây chứa một ví dụ về khóa trong văn bản thường được mã hóa base64 và SKU.

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

Mã hóa đối tượng JSON ở trên sử dụng khóa công khai được tạo trong phần Tạo một Cặp Khóa Công khai/Riêng tư.

Thêm kết quả mã hóa làm giá trị cho khóa "local" (cục bộ). Đặt cặp giá trị khóa vào trong một đối tượng JSON được bọc trong một thẻ <script type="application/json" cryptokeys="">. Đặt thẻ này vào đầu tài liệu.

<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>

Bạn cần mã hóa khóa tài liệu với môi trường cục bộ và khóa công khai của Google. Việc bao gồm khóa công khai của Google cho phép bộ nhớ đệm Google AMP có thể phục vụ tài liệu của bạn. Bạn phải tạo một Tink Keyset để chấp nhận khóa công khai Google từ URL của nó:

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

Khóa công khai của Google là một Tink Keyset trong định dạng JSON. Xem ở đây để biết ví dụ về làm việc với keyset này.

Đọc tiếp: Xem một ví dụ về một tài liệu AMP mã hóa hoạt động tốt.

Tự động Mã hóa

Mã hóa tài liệu của bạn sử dụng kịch bản của chúng tôi. Kịch bản này chấp nhận một tài liệu HTML và mã hóa toàn bộ nội dung bên trong thẻ <section subscriptions-section="content" encrypted>. Sử dụng các khóa công khai trong URL được truyền đến nó, kịch bản này sẽ mã hóa khóa tài liệu được tạo bởi kịch bản. Việc sử dụng kịch bản này đảm bảo mọi nội dung đều được mã hóa và định dạng phù hợp để phục vụ. Đọc ở đây để biết hướng dẫn bổ sung về việc sử dụng kịch bản này.

Bước 3: Tích hợp bộ xác thực

Bạn cần cập nhật bộ xác thực để giải mã các khóa tài liệu khi người dùng có quyền lợi phù hợp. Thành phần amp-subscriptions sẽ tự động gửi khóa tài liệu được mã hóa cho bộ xác thực "local" (cục bộ) thông qua một tham số URL “crypt=”. Hoạt động của nó:

  1. Đọc khóa tài liệu từ trường khóa JSON "local" (cục bộ).
  2. Giải mã tài liệu.

Bạn phải sử dụng Tink để giải mã các khóa tài liệu trong bộ xác thực của mình. Để giải mã với Tink, hãy tạo một máy khách HybridDecrypt sử dụng các khóa riêng tư được tạo trong phần Tạo một Cặp Khóa Công khai/Riêng tư. Làm việc này khi khởi động máy chủ để có hiệu năng tối ưu.

Việc triển khai HybridDecrypt/Bộ Xác thực phải gần khớp với lịch chuyển khóa của bạn. Việc này sẽ đảm bảo tình trạng sẵn có của tất cả các khóa được tạo cho máy khách HybridDecrypt.

Tink có tài liệuví dụ đầy đủ cho C++, Java, Go, và Javascript để giúp bạn bắt đầu triển khai cho phía máy chủ của mình.

Quản lý yêu cầu

Khi một yêu cầu được gửi đến bộ xác thực của bạn:

  1. Đọc URL pingbank quyền lợi để tìm tham số “crypt=”.
  2. Giải mã giá trị tham số "crypt=” với base64. Giá trị được lưu trữ trong tham số URL là đối tượng JSON được mã hóa base64.
  3. Sau khi khóa mã hóa đã ở trong dạng byte thô, sử dụng chức năng giải mã của HybridDecrypt để giải mã khóa này bằng khóa riêng tư của bạn.
  4. Nếu việc giải mã thành công, gửi kết quả vào một đối tượng JSON.
  5. Xác minh quyền truy cập của người dùng vào một quyền lợi được liệt kê trong trường JSON AccessRequirements.
  6. Trả về khóa tài liệu từ trường “Key” (Khóa) của đối tượng JSON được giải mã trong hồi đáp quyền lợi. Thêm khóa tài liệu được giải mã vào một trường mới tên là “decryptedDocumentKey” trong hồi đáp quyền lợi. Việc này sẽ cho phép truy cập khung AMP.

Mẫu dưới đây là một đoạn code giả để liệt kê các bước ở trên:

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;
}

Các tài nguyên liên quan

Kiểm tra tài liệu và các ví dụ trên Trang Github của Tink.

Tất cả các kịch bản trợ giúp đều có trong kho Github subscriptions-project/encryption.

Hỗ trợ thêm

Nếu bạn có bất kỳ câu hỏi, bình luận hay thắc mắc nào, hãy gửi một Vấn đề Github.