Giao thức ký APK v2 là một giao thức ký toàn bộ tệp giúp tăng tốc độ xác minh và củng cố tính toàn vẹn của tệp 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.
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
tính bằng 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
(kích thước biến đổi: chiều 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, 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 ZIP End of Central Directory (Kết thúc thư mục trung tâm ZIP) ở cuối tệp, sau đó đọc độ dời bắt đầu của Thư mục trung tâm từ 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 v2. Đố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 trình ký, APK được xác minh bằ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. Mỗi phương thức triển khai sẽ tuỳ ý chọn chữ ký để sử dụng khi gặp nhiều chữ ký được hỗ trợ. Đ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):
- trình tự có tiền tố độ dài của
signer
có tiền tố độ dài: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:signature algorithm ID
(uint32)- (có tiền tố độ dài)
digest
– xem phần Nội dung được bảo vệ tính toàn vẹn
- 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)
- trình tự có tiền tố độ dài của
additional attributes
có tiền tố độ dài: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
- 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ênsigned data
public key
có tiền tố độ dài (SubjectPublicKeyInfo, dạng ASN.1 DER)
Mã nhận dạng thuật toán chữ ký
- 0x0101 – RSASSA-PSS với thông báo SHA2-256, SHA2-256 MGF1, 32 byte muối, phần phụ lục: 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 thông báo SHA2-256
- 0x0202 – ECDSA với chuỗi đại diện SHA2-512
- 0x0301 – DSA với thông báo 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:
- 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)
- Khối ký APK
- Thư mục trung tâm ZIP
- Phần cuối thư mục trung tâm ZIP
Hình 2. Các phần của tệp APK sau khi ký
Lược đồ chữ ký APK phiên bản 2 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 phiên bản 2 nằm bên 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 đoạ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. Giá trị tổng quan của mỗi phần được tính toán dựa trên việc nối byte 0xa5
, độ dài của phần tính bằng byte (little-endian uint32) và nội dung của phầ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 tệp APK. Checksum được tính toán theo kiểu phân đoạn để
có thể tăng tốc độ tính toán bằng cách chạy song song.
Hình 3. Checksum 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 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 Cuối 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, các tệp APK được ký bằng phiên bản 2 và cũng được ký bằng phiên bản 1 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ý dùng để ký APK đượ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.
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
- Tìm khối ký APK và xác minh rằng:
- Hai trường kích thước của khối ký APK chứa cùng một giá trị.
- Ngay sau thư mục trung tâm ZIP là bản ghi ZIP End of Central Directory (Kết thúc thư mục trung tâm ZIP).
- 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.
- Tìm khối lược đồ chữ ký APK phiên bản 2 đầu tiên bê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.
- Đối với mỗi
signer
trong khối lược đồ chữ ký APK phiên bản 2:- Chọn
signature algorithm ID
được hỗ trợ mạnh nhất trongsignatures
. Thứ tự độ mạnh tuỳ thuộc vào từng phiên bản triển khai/nền tảng. - Xác minh
signature
tương ứng từsignatures
vớisigned data
bằngpublic key
. (Giờ đây, bạn có thể phân tích cú phápsigned data
một cách an toàn.) - Xác minh rằng danh sách thứ tự mã nhận dạng thuật toán chữ ký trong
digests
vàsignatures
giống hệt nhau. (Điều này là để ngăn chặn việc xoá/thêm chữ ký.) - 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.
- Xác minh rằng chuỗi đại diện được tính toán giống với
digest
tương ứng từdigests
. - Xác minh rằng SubjectPublicKeyInfo của
certificate
đầu tiên củacertificates
giống vớipublic key
.
- Chọn
- 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ỗisigner
đượ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ý JAR (lược đồ v1)
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:
- 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).
- <signer>.(RSA|DSA|EC) là một PKCS #7 CMS ContentInfo có cấu trúc SignedData, chữ ký của tệp này được xác minh qua tệp <signer>.SF.
- 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.
- META-INF/MANIFEST.MF chứa, đối với mỗi mục JAR được bảo vệ tính toàn vẹn, một phần được đặt tên tương ứng chứa chuỗi đại diện của nội dung chưa nén của mục đó. Tất cả các bản tóm tắt này đều được xác minh.
- 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.