Lược đồ chữ ký APK phiên bản 2

Lược đồ chữ ký APK phiên bản 2 là một lược đồ chữ ký toàn bộ tệp giúp tăng tốc độ xác minh và tăng cường đảm bảo tính toàn vẹn bằng cách phát hiện mọi thay đổi đối với các phần được bảo vệ của tệp APK.

Khi ký bằng lược đồ chữ ký APK phiên bản 2, một khối ký APK sẽ được chèn vào tệp APK ngay trước mục Thư mục trung tâm ZIP. Bên trong khối ký APK, chữ ký v2 và thông tin danh tính của chữ ký được lưu trữ trong khối lược đồ chữ ký APK v2.

APK trước và sau khi ký

Hình 1. APK trước và sau khi ký

Lược đồ chữ ký APK phiên bản 2 được giới thiệu trong Android 7.0 (Nougat). Để có thể cài đặt APK trên Android 6.0 (Marshmallow) và các thiết bị cũ hơn, APK phải được ký bằng tính năng ký JAR trước khi được ký bằng lược đồ v2.

Khối ký APK

Để duy trì khả năng tương thích ngược với định dạng APK v1, chữ ký APK v2 trở lên được lưu trữ bên trong một khối ký APK, một vùng chứa mới được giới thiệu để hỗ trợ lược đồ chữ ký APK v2. Trong tệp APK, khối ký APK nằm ngay trước Thư mục trung tâm ZIP, nằm ở cuối tệp.

Khối này chứa các cặp giá trị-mã nhận dạng được gói theo cách giúp bạn dễ dàng xác định vị trí của khối trong tệp APK. Chữ ký v2 của APK được lưu trữ dưới dạng một cặp giá trị-mã nhận dạng có mã nhận dạng 0x7109871a.

Định dạng

Định dạng của khối ký APK như sau (tất cả các trường số đều là little-endian):

  • size of block đơn vị byte (không bao gồm trường này) (uint64)
  • Trình tự các cặp giá trị-mã nhận dạng có tiền tố độ dài uint64:
    • ID (uint32)
    • value (độ dài biến: độ dài của cặp – 4 byte)
  • size of block tính bằng byte – giống như trường đầu tiên (uint64)
  • magic "Khối chữ ký APK 42" (16 byte)

Trước tiên, tệp APK được phân tích cú pháp bằng cách tìm điểm bắt đầu của Thư mục trung tâm ZIP (bằng cách tìm bản ghi Điểm cuối của Thư mục trung tâm ở cuối tệp, sau đó đọc giá trị chênh lệch đầu của Thư mục trung tâm trong bản ghi). Giá trị magic cung cấp một cách nhanh chóng để xác định rằng nội dung đứng trước Thư mục trung tâm có thể là khối ký APK. Sau đó, giá trị size of block sẽ trỏ hiệu quả đến đầu khối trong tệp.

Bạn nên bỏ qua các cặp giá trị-mã nhận dạng có mã nhận dạng không xác định khi diễn giải khối.

Khối lược đồ chữ ký APK phiên bản 2

APK được ký bởi một hoặc nhiều chữ ký/danh tính, mỗi chữ ký/danh tính được biểu thị bằng một khoá ký. Thông tin này được lưu trữ dưới dạng khối lược đồ chữ ký APK phiên bản 2. Đối với mỗi chữ ký, hệ thống sẽ lưu trữ những thông tin sau:

  • Các bộ dữ liệu (thuật toán chữ ký, chuỗi đại diện, chữ ký). Checksum được lưu trữ để tách biệt quá trình xác minh chữ ký khỏi quá trình kiểm tra tính toàn vẹn của nội dung APK.
  • Chuỗi chứng chỉ X.509 thể hiện danh tính của người ký.
  • Các thuộc tính bổ sung dưới dạng cặp khoá-giá trị.

Đối với mỗi người ký, APK được xác minh bằng cách sử dụng chữ ký được hỗ trợ trong danh sách được cung cấp. Chữ ký có thuật toán chữ ký không xác định sẽ bị bỏ qua. Việc chọn chữ ký để dùng khi gặp nhiều chữ ký được hỗ trợ sẽ phụ thuộc vào mỗi cách triển khai. Điều này cho phép giới thiệu các phương thức ký mạnh hơn trong tương lai theo cách tương thích ngược. Phương pháp đề xuất là xác minh chữ ký mạnh nhất.

Định dạng

Khối lược đồ chữ ký APK phiên bản 2 được lưu trữ bên trong khối ký APK theo mã nhận dạng 0x7109871a.

Định dạng của khối lược đồ chữ ký APK phiên bản 2 như sau (tất cả giá trị số đều là little-endian, tất cả trường có tiền tố độ dài đều sử dụng uint32 cho độ dài):

  • chuỗi có tiền tố độ dài có tiền tố signer:
    • signed data có tiền tố độ dài:
      • trình tự có tiền tố độ dài của digests có tiền tố độ dài:
      • Trình tự có tiền tố độ dài của X.509 certificates:
        • certificate X.509 có tiền tố độ dài (dạng ASN.1 DER)
      • chuỗi có tiền tố độ dài có tiền tố additional attributes:
        • ID (uint32)
        • value (độ dài biến đổi: độ dài của thuộc tính bổ sung – 4 byte)
    • trình tự có tiền tố độ dài của signatures có tiền tố độ dài:
      • signature algorithm ID (uint32)
      • signature có tiền tố độ dài trên signed data
    • có tiền tố độ dài public key (SubjectPublicKeyInfo, biểu mẫu ASN.1 DER)

Mã nhận dạng thuật toán chữ ký

  • 0x0101 – RSASSA-PSS với chuỗi đại diện SHA2-256, SHA2-256 MGF1, 32 byte muối, đoạn giới thiệu: 0xbc
  • 0x0102 – RSASSA-PSS với chuỗi đại diện SHA2-512, SHA2-512 MGF1, 64 byte muối, phần phụ lục: 0xbc
  • 0x0103 – RSASSA-PKCS1-v1_5 với chuỗi đại diện SHA2-256. Điều này dành cho các hệ thống xây dựng yêu cầu chữ ký xác định.
  • 0x0104 — RSASSA-PKCS1-v1_5 với chuỗi đại diện SHA2-512. Điều này dành cho các hệ thống xây dựng yêu cầu chữ ký xác định.
  • 0x0201 – ECDSA với chuỗi đại diện SHA2-256
  • 0x0202 – ECDSA với chuỗi đại diện SHA2-512
  • 0x0301 – DSA với chuỗi đại diện SHA2-256

Tất cả các thuật toán chữ ký nêu trên đều được nền tảng Android hỗ trợ. Các công cụ ký có thể hỗ trợ một tập hợp con của các thuật toán.

Kích thước khoá và đường cong EC được hỗ trợ:

  • RSA: 1024, 2048, 4096, 8192, 16384
  • EC: NIST P-256, P-384, P-521
  • DSA: 1024, 2048, 3072

Nội dung được bảo vệ tính toàn vẹn

Để bảo vệ nội dung APK, tệp APK bao gồm 4 phần:

  1. Nội dung của các mục nhập ZIP (từ độ dời 0 cho đến khi bắt đầu khối ký APK)
  2. Khối ký APK
  3. Thư mục trung tâm ZIP
  4. Phần cuối thư mục trung tâm ZIP

Các phần của tệp APK sau khi ký

Hình 2. Các phần của tệp APK sau khi ký

Lược đồ chữ ký APK v2 bảo vệ tính toàn vẹn của các phần 1, 3, 4 và các khối signed data của khối lược đồ chữ ký APK v2 có trong phần 2.

Tính toàn vẹn của các phần 1, 3 và 4 được bảo vệ bằng một hoặc nhiều chuỗi đại diện của nội dung được lưu trữ trong các khối signed data. Các khối này, đến lượt mình, được bảo vệ bằng một hoặc nhiều chữ ký.

Giá trị tổng quan trên các phần 1, 3 và 4 được tính như sau, tương tự như một cây Merkle hai cấp. Mỗi phần được chia thành các phần 1 MB (220 byte) liên tiếp. Phần cuối cùng trong mỗi phần có thể ngắn hơn. Chuỗi đại diện của từng đoạn được tính toán dựa trên việc nối byte 0xa5, độ dài của đoạn tính bằng byte (little-endian uint32) và nội dung của đoạn. Giá trị tổng quan cấp cao nhất được tính toán dựa trên việc nối byte 0x5a, số lượng đoạn (little-endian uint32) và nối các giá trị tổng quan của các đoạn theo thứ tự các đoạn xuất hiện trong APK. Chuỗi đại diện này được tính toán theo kiểu phân đoạn để tăng tốc độ tính toán bằng cách tải song song chuỗi đại diện này.

Checksum APK

Hình 3. Thông báo APK

Việc bảo vệ phần 4 (ZIP End of Central Directory) (Kết thúc thư mục trung tâm ZIP) trở nên phức tạp do phần này chứa độ dời của Thư mục trung tâm ZIP. Độ lệch sẽ thay đổi khi kích thước của khối ký APK thay đổi, chẳng hạn như khi thêm chữ ký mới. Do đó, khi tính toán chuỗi đại diện qua phần Kết thúc thư mục trung tâm ZIP, trường chứa độ dời của Thư mục trung tâm ZIP phải được coi là chứa độ dời của khối ký APK.

Tính năng bảo vệ chống khôi phục

Kẻ tấn công có thể cố gắng xác minh APK đã ký bằng phiên bản 2 là APK đã ký bằng phiên bản 1 trên các nền tảng Android hỗ trợ xác minh APK đã ký bằng phiên bản 2. Để giảm thiểu cuộc tấn công này, tệp APK phiên bản 2 cũng được ký bằng v1 phải chứa thuộc tính X-Android-APK-Signed trong phần chính của tệp META-INF/*.SF. Giá trị của thuộc tính này là một tập hợp các mã giao thức chữ ký APK được phân tách bằng dấu phẩy (mã của giao thức này là 2). Khi xác minh chữ ký v1, trình xác minh APK bắt buộc phải từ chối các tệp APK không có chữ ký cho lược đồ chữ ký APK mà trình xác minh ưu tiên trong tập hợp này (ví dụ: lược đồ v2). Phương thức bảo vệ này dựa trên thực tế là nội dung của tệp META-INF/*.SF được bảo vệ bằng chữ ký phiên bản 1. Xem phần Xác minh APK đã ký JAR.

Kẻ tấn công có thể cố gắng xoá các chữ ký mạnh hơn khỏi khối Lược đồ chữ ký APK phiên bản 2. Để giảm thiểu cuộc tấn công này, danh sách mã nhận dạng thuật toán chữ ký mà APK đang được ký sẽ được lưu trữ trong khối signed data được bảo vệ bằng từng chữ ký.

Xác minh

Trong Android 7.0 trở lên, bạn có thể xác minh tệp APK theo giao thức chữ ký APK phiên bản 2 trở lên hoặc ký JAR (giao thức phiên bản 1). Các nền tảng cũ bỏ qua chữ ký v2 và chỉ xác minh chữ ký v1.

Quy trình xác minh chữ ký APK

Hình 4. Quy trình xác minh chữ ký APK (các bước mới có màu đỏ)

Xác minh lược đồ chữ ký APK phiên bản 2

  1. Tìm khối ký APK và xác minh rằng:
    1. Hai trường kích thước của khối ký APK chứa cùng một giá trị.
    2. Ngay sau đó là bản ghi ZIP End của Thư mục trung tâm.
    3. Không có dữ liệu nào theo sau phần Kết thúc thư mục trung tâm của tệp ZIP.
  2. Xác định vị trí khối chữ ký APK v2 đầu tiên trong khối ký APK. Nếu có khối v2, hãy chuyển sang bước 3. Nếu không, hãy quay lại xác minh APK bằng lược đồ v1.
  3. Đối với mỗi signer trong khối lược đồ chữ ký APK phiên bản 2:
    1. Chọn signature algorithm ID được hỗ trợ mạnh nhất trong signatures. Thứ tự độ mạnh tuỳ thuộc vào từng phiên bản triển khai/nền tảng.
    2. Xác minh signature tương ứng từ signatures với signed data bằng public key. (Giờ đây, bạn có thể phân tích cú pháp signed data một cách an toàn.)
    3. Xác minh rằng danh sách mã thuật toán chữ ký theo thứ tự trong digestssignatures là giống hệt nhau. (Việc này nhằm ngăn chặn việc xoá/thêm chữ ký.)
    4. Tính toán chuỗi đại diện của nội dung APK bằng cách sử dụng cùng một thuật toán chuỗi đại diện với thuật toán chuỗi đại diện mà thuật toán chữ ký sử dụng.
    5. Xác minh rằng chuỗi đại diện đã tính toán giống với digest tương ứng trong digests.
    6. Xác minh rằng SubjectPublicKeyInfo của certificate đầu tiên của certificates giống với public key.
  4. Quá trình xác minh sẽ thành công nếu tìm thấy ít nhất một signer và bước 3 thành công đối với mỗi signer được tìm thấy.

Lưu ý: Không được xác minh APK bằng lược đồ v1 nếu xảy ra lỗi ở bước 3 hoặc 4.

Xác minh APK được ký bằng JAR (sơ đồ phiên bản 1)

APK được ký JAR là một JAR được ký tiêu chuẩn, phải chứa chính xác các mục được liệt kê trong META-INF/MANIFEST.MF và tất cả các mục phải được ký bằng cùng một bộ chữ ký. Tính toàn vẹn của tệp này được xác minh như sau:

  1. Mỗi chữ ký được biểu thị bằng mục nhập JAR META-INF/<signer>.SF và META-INF/<signer>.(RSA|DSA|EC).
  2. <signer>.(RSA|DSA|EC) là một PKCS #7 CMS ContentInfo có cấu trúc SignedData, chữ ký được xác minh qua tệp <signer>.SF.
  3. Tệp <signer>.SF chứa một chuỗi đại diện toàn bộ tệp META-INF/MANIFEST.MF và chuỗi đại diện của từng phần trong META-INF/MANIFEST.MF. Xác minh chuỗi đại diện toàn bộ tệp của MANIFEST.MF. Nếu không thành công, hệ thống sẽ xác minh chuỗi đại diện của từng phần MANIFEST.MF.
  4. META-INF/MANIFEST.MF chứa mỗi mục JAR được bảo vệ tính toàn vẹn, chứa một phần được đặt tên tương ứng chứa chuỗi đại diện nội dung không nén của mục nhập. Tất cả các chuỗi đại diện này đều đã được xác minh.
  5. Quá trình xác minh APK sẽ không thành công nếu APK chứa các mục JAR không được liệt kê trong MANIFEST.MF và không phải là một phần của chữ ký JAR.

Do đó, chuỗi bảo vệ là <signer>.(RSA|DSA|EC) -> <signer>.SF -> MANIFEST.MF -> nội dung của mỗi mục JAR được bảo vệ tính toàn vẹn.