Trong thời đại 4.0 hiện nay, sự phát triển của công nghệ đã giúp con người rất nhiều trong cuộc sống. Tuy nhiên, đi cùng với sự phát triển là nỗi lo về mặt trái của công nghệ, đặc biệt là việc xâm phạm quyền riêng tư và lỗ hổng trong bảo mật thông tin. Ta thấy rằng, công nghệ càng phát triển, an toàn và bảo mật thông tin càng được chú trọng nhiều hơn. Chính vì vậy, trong bài viết hôm nay, ta sẽ cùng thảo luận về những quy tắc để tăng cường sự bảo mật và giảm thiểu rủi ro mà một lập trình viên Flutter có thể áp dụng khi phát triển ứng dụng của mình.
Trước khi bắt đầu, ta sẽ cùng nhìn lại một số triết lý về bảo mật mà team Flutter đã đưa ra và áp dụng xuyên suốt từ những ngày đầu tiên, bao gồm 5 “key point” sau:
Trên đây là 5 tiêu chí hàng đầu để xây dựng một ứng dụng Flutter có tính bảo mật cao. Để đáp ứng được 5 tiêu chí này, ta sẽ cùng tìm hiểu các bước cần làm để đạt được điều đó.
Cách đơn giản nhất để tăng cường sự bảo mật và an toàn ứng dụng đó là cập nhật thường xuyên, từ Flutter SDK, plugin cho tới các thư viện được sử dụng, … Các bản cập nhật mới nhất sẽ vá các lỗ hổng tồn đọng và các lỗi có thể ảnh hưởng đến bảo mật của app.
Thông thường, mã nguồn sau khi được biên dịch thành dạng file binary hoàn toàn có thể sử dụng kỹ thuật dịch ngược (reversed engineering) để chuyển về mã nguồn gốc, qua đó rất dễ bị kẻ gian lợi dụng để đánh cắp các thông tin quan trọng. Chính vì vậy, obfuscate code là một bước quan trọng giúp thay đổi cấu trúc code (thay đổi tên hàm, tên biến, mã hóa chuỗi, thêm các file dummy, …) mà không làm ảnh hưởng đến output của chương trình, từ đó gây ra nhiều khó khăn khi muốn đánh cắp mã nguồn hay tìm kiếm những thông tin có giá trị trong đó.
Hiểu một cách đơn giản, obfuscate code sẽ sắp xếp, thay đổi để code trở nên rối tung và không thể đọc được.
Tìm hiểu thêm về obfuscation tại đây.
Đối với Flutter, ta sẽ thêm biến --obfuscate
, kết hợp với --split-debug-info
vào lệnh build như sau:
flutter build apk --obfuscate --split-debug-info=/<project-name>/<directory>
Ở đây, --split-debug-info
có nhiệm vụ cung cấp địa chỉ của thư mục để Flutter lưu trữ file output debug. Ngoài ra, ta có thể thay thế apk
thành appbundle
, ipa
, hay ios
để build các file tương ứng cho iOS và Android.
Lưu ý: khi sử dụng --obfuscate
trong lệnh build, ta mới chỉ obfuscate code của Dart. Nếu muốn obfuscate code native Android hay iOS, ta sẽ cần cấu hình hai phần riêng biệt trong từng thư mục /ios và /android.
Ta có thể sử dụng ProGuard để obfuscate cho Android và iXGuard cho iOS.
API key là một trong những thông tin quan trọng nhất mà ta cần bảo mật và tuyệt đối không để lọt vào tay kẻ gian. Một khi API key bị lộ, hacker có thể dùng để khai thác các thông tin của người dùng và hệ thống. Thông thường, để đảm bảo an toàn và củng cố các lớp bảo mật, ta có thể thực hiện bảo mật API key từ cả hai phía là Server side và Client side.
Đối với server, ta sẽ hạn chế quyền truy cập của một API key bằng cách cung cấp các dịch vụ cần thiết tối thiểu cho API key đó, thay vì cho phép truy cập vào tất cả các dịch vụ của hệ thống. Ngoài ra, ta còn có thể encrypt/decrypt API key trực tiếp lúc runtime (ví dụ như xác thực người dùng). Bằng cách này, ta có thể thêm một lớp bảo mật để bảo vệ API key nhằm tránh việc hacker có thể dễ dàng lấy và đọc được.
Thứ nhất, không bao giờ commit key trực tiếp lên git, đặc biệt là những dự án public hay open-source. Với những dự án này, để sử dụng API key, ta sẽ dùng các biến môi trường.
Trong Flutter, ta có thể sử dụng thư viện flutter_dotenv để tạo file
.env
và lưu các key vào trong đó.
Thứ hai, việc tạo biến môi trường có thể giảm thiểu rủi ro bằng cách không public key lên git, tuy nhiên không thể tránh việc key vẫn được sử dụng để compile code, vì vậy hacker vẫn có thể dịch ngược để lấy được key. Khi này, ta sẽ quay lại mục 2 bên trên và thực hiện obfuscate code nhằm mã hóa các key.
Thứ ba, không chia sẻ, trao đổi key, token, dữ liệu qua các kênh chat như Slack, Messenger hay Discord. Thay vào đó, ta có thể sử dụng các công cụ quản lý mật khẩu như 1Password.
Khi ứng dụng gọi API request, các thông tin được trao đổi giữa app và server sẽ xảy ra ở tầng giao vận Transport Layer Security (TLS), trong đó mục tiêu chính của giao thức TLS là cung cấp sự riêng tư và toàn vẹn dữ liệu giữa hai ứng dụng trong môi trường mạng.
Một cách để hạn chế truy cập và bảo đảm việc truy cập đến địa chỉ được xác thực đó là liệt kê các tên miền được sử dụng trong ứng dụng.
<?xml version="1.0" encoding="utf-8"?><network-security-config><domain-config><domain includeSubdomains="true">example.com</domain><trust-anchors><certificates src="@raw/my_ca"/></trust-anchors></domain-config></network-security-config>
Tham kh ảo thêm về config network security Android tại đây
<key>NSAppTransportSecurity</key><dict><key>NSAllowsArbitraryLoads</key><false/><key>NSExceptionDomains</key><dict><key>cocoacasts.com</key><dict><key>NSIncludesSubdomains</key><true/><key>NSExceptionAllowsInsecureHTTPLoads</key><true/></dict></dict></dict>
Tham khảo thêm về app transport security iOS tại đây
Một ứng dụng Flutter khi muốn truy cập vào phần cứng của thiết bị hay các API native sẽ phải yêu cầu các quyền tương ứng. Vì vậy, khi sử dụng thư viện bất kỳ của một bên thứ ba, ta cần kiểm tra cẩn thận để xem các quyền mà thư viện cần có đúng với chức năng của nó hay không. Ví dụ, một thư viện sử dụng camera để quay phim, chụp ảnh sẽ cần các quyền liên quan tới camera và bộ nhớ để lưu ảnh hoặc video vào thiết bị, thay vì các quyền không liên quan như network hay location.
Việc kiểm tra và cung cấp các quyền tối thiểu sẽ giúp ta hạn chế được việc bị kẻ gian khai thác dữ liệu và rò rỉ thông tin, từ đó tránh những sự cố đáng tiếc về sau.
Trong bài viết này, ta đã tìm hiểu 5 bước giúp cải thiện và nâng cao độ bảo mật cho một ứng dụng Flutter. Ở bài viết tiếp theo, ta sẽ cùng tìm hiểu những cách khác có thể giúp ứng dụng an toàn hơn trước những nguy cơ tiềm ẩn của việc bị đánh cắp thông tin, qua đó cải thiện hiệu quả bảo mật cho app.