Cách mã hóa cơ sở dữ liệu Firebird hoạt động
(c) Alexey Kovyazin, IBSurgeon, Alex Peshkoff, Firebird Project, 2021
Bài viết dựa trên tài liệu của hội thảo “Mã hóa cơ sở dữ liệu” tại Hội nghị Firebird 2019 ở Berlin, Đức. Bài viết mô tả cách hoạt động của mã hóa cơ sở dữ liệu Firebird, ở cấp máy chủ, ở phía máy khách, cách cấu hình mã hóa cơ sở dữ liệu, cách sử dụng nó từ các loại ứng dụng khác nhau (Delphi, Java, .NET). Các ví dụ trong bài viết dựa trên IBSurgeon Firebird Encryption Framework (FEPF) nhưng có thể được điều chỉnh cho phần lớn các triển khai plugin mã hóa hiện có.
Nội dung:
- Tại sao chúng ta cần mã hóa cơ sở dữ liệu (và khi nào thì không)?
- Cách mã hóa cơ sở dữ liệu Firebird hoạt động ở phía máy chủ
- Phần nào của cơ sở dữ liệu được mã hóa?
- Khi nào các trang dữ liệu được mã hóa?
- Làm thế nào để bảo vệ việc truyền khóa?
- Cách mã hóa Firebird hoạt động ở phía máy khách
- Ứng dụng gốc
- Ứng dụng Java
- Ứng dụng .NET
- Cài đặt và Cấu hình
- Cách theo dõi tiến trình mã hóa
- Tóm tắt
1. Tại sao chúng ta cần mã hóa cơ sở dữ liệu (và khi nào thì không)?
Mã hóa cơ sở dữ liệu Firebird được giới thiệu trong bản phát hành Firebird 3.0 (cùng với mã hóa giao thức truyền tải, thường bị nhầm lẫn với chủ đề đang thảo luận), và đã làm tăng đáng kể khả năng bảo vệ dữ liệu khỏi truy cập trái phép. Tuy nhiên, nó không phải là giải pháp vạn năng, và cần phải hiểu những điểm mạnh và điểm yếu của nó để sử dụng đúng cách.
Trong bài viết này, chúng tôi xem xét các chi tiết nội bộ của mã hóa cơ sở dữ liệu ở mức cơ bản, để giúp các nhà phát triển ứng dụng Firebird hiểu rõ hơn về cách hoạt động của mã hóa cơ sở dữ liệu.
Vậy, tại sao chúng ta cần mã hóa cơ sở dữ liệu?
- Để bảo vệ các cơ sở dữ liệu có dữ liệu nhạy cảm/có giá trị khỏi việc “đánh cắp” vật lý. Nếu kẻ xâm nhập đánh cắp ổ đĩa chứa bản sao của cơ sở dữ liệu được mã hóa hoặc bằng cách nào đó có được bản sao của tệp cơ sở dữ liệu, sẽ không thể đọc dữ liệu từ đó nếu không có khóa thích hợp, cũng như không thể sử dụng phần mềm khôi phục như FirstAID để trích xuất dữ liệu. Tất nhiên, điều này phụ thuộc vào thuật toán mã hóa và sức mạnh tính toán, nhưng việc bẻ khóa AES256 sẽ đòi hỏi quá nhiều thời gian hoặc tài nguyên tính toán quá đắt đỏ.
- Để bảo vệ cơ sở dữ liệu khỏi truy cập từ các ứng dụng không được ủy quyền mà không có khóa mã hóa. Ví dụ bao gồm:
- truy cập trực tiếp bằng công cụ nhà phát triển bởi người không được ủy quyền, để thay đổi thông tin nhạy cảm (ví dụ: giao dịch tiền),
- thay đổi hoặc đánh cắp logic kinh doanh (văn bản của stored procedure và trigger).
- Bảo vệ các cơ sở dữ liệu có dữ liệu được điền sẵn khỏi việc xuất ra hoặc truy cập bởi các ứng dụng không được ủy quyền.
- Các chính phủ gần đây đã ban hành luật bảo vệ dữ liệu (GDPR/DSVGO ở Châu Âu, LGPD ở Brazil, v.v.) yêu cầu, trong số những điều khác, mức độ bảo vệ cao hơn cho dữ liệu cá nhân và dữ liệu nhạy cảm khác, và mã hóa được đề cập như một trong những biện pháp bảo vệ đầy đủ.
Khi nào mã hóa cơ sở dữ liệu không hữu ích?
Trong một số trường hợp, tốt hơn là sử dụng các khả năng bảo mật và cấu hình của Firebird thay vì mã hóa cơ sở dữ liệu:
- Để bảo vệ cơ sở dữ liệu khỏi truy cập vật lý qua mạng, cần phải cấu hình truy cập mạng: tức là, đóng các thư mục chia sẻ mạng, vì Firebird không yêu cầu truy cập chia sẻ mạng vào các tệp cơ sở dữ liệu, và thắt chặt các quyền bảo mật (ví dụ, đối với Linux, các tệp cơ sở dữ liệu phải có quyền đọc-ghi chỉ cho người dùng “firebird”).
- Để hạn chế quyền truy cập vào cơ sở dữ liệu cụ thể cho một tập hợp người dùng cụ thể, giải pháp dễ dàng hơn sẽ là cấu hình một cơ sở dữ liệu bảo mật riêng biệt.
- Để hạn chế quyền truy cập vào các đối tượng cơ sở dữ liệu (bảng, stored procedure), cần phải sử dụng các cơ chế bảo mật của Firebird: người dùng, vai trò, v.v.
Tất nhiên, cả hai danh sách trên đều không đầy đủ, nhưng chúng cho bạn một số ấn tượng về khi nào bạn cần hoặc không cần mã hóa cơ sở dữ liệu.
2. Cách mã hóa cơ sở dữ liệu Firebird hoạt động ở phía máy chủ
Hãy đi qua các chi tiết nội bộ của mã hóa cơ sở dữ liệu Firebird, và bắt đầu với phần máy chủ.
2.1. Phần nào của cơ sở dữ liệu được mã hóa?
Điều đầu tiên chúng ta cần xem xét là phần nào của cơ sở dữ liệu được mã hóa? Như bạn có thể biết, cơ sở dữ liệu Firebird bao gồm các phần có kích thước bằng nhau, được gọi là “trang cơ sở dữ liệu”. Có một số loại trang như vậy, mỗi loại phục vụ một mục đích cụ thể.
Dưới đây bạn có thể thấy hình với các loại dữ liệu chính:

Hình 1. Các loại trang cơ sở dữ liệu
Một số trang được thiết kế để lưu trữ dữ liệu của người dùng, và một số trang khác cần thiết để lưu trữ thông tin hệ thống, như các trang giao dịch và trang kiểm kê trang (chi tiết hơn về các trang cơ sở dữ liệu có sẵn tại đây).
Khi cơ sở dữ liệu Firebird được mã hóa, chỉ các trang có dữ liệu người dùng được mã hóa: trang dữ liệu, chỉ mục, bộ sinh (generator) và BLOB:

Hình 2. Chỉ các trang cơ sở dữ liệu có dữ liệu người dùng được mã hóa
Xin lưu ý rằng siêu dữ liệu cơ sở dữ liệu (stored procedure, bảng, view, trigger, tên bộ sinh, v.v.) không khác biệt với “dữ liệu người dùng” trong phần của engine chịu trách nhiệm mã hóa, và chúng được mã hóa.
Tại sao các trang hệ thống không được mã hóa? Chủ yếu là vì lý do hiệu suất, và do thực tế là chúng không chứa dữ liệu nhạy cảm cần được bảo vệ.
Trang tiêu đề cơ sở dữ liệu không được mã hóa, vì nó chứa thông tin cần thiết cho việc mã hóa (ví dụ: tên khóa).
2.2. Khi nào các trang dữ liệu được mã hóa?
Khi người dùng thực hiện SELECT từ cơ sở dữ liệu được mã hóa, dữ liệu được đọc từ tệp được mã hóa nhưng đến lưới kết quả của ứng dụng ở dạng không mã hóa.
Hãy xem xét chi tiết của quá trình này:

Hình 3. Khi nào các trang cơ sở dữ liệu được mã hóa?
Thông thường, quá trình bắt đầu với một loạt các lần đọc các trang cơ sở dữ liệu từ tệp cơ sở dữ liệu, và chúng được lưu vào bộ nhớ đệm tệp của Hệ điều hành.
Firebird cũng có thể được cấu hình để bỏ qua bộ nhớ đệm tệp và chỉ sử dụng bộ nhớ đệm riêng của nó, nhưng theo mặc định, bộ nhớ đệm tệp được sử dụng.
Sau đó, Firebird đọc các trang và đưa chúng vào bộ nhớ đệm trang Firebird (được xác định bởi tham số DefaultDBCachePages trong firebird.conf và/hoặc databases.conf hoặc trong trang tiêu đề cơ sở dữ liệu).
Sau đó, các trang từ bộ nhớ đệm được chọn vào tập kết quả của câu lệnh SQL cụ thể (SELECT trong ví dụ của chúng ta).
Hình dưới đây cho thấy chi tiết:

Hình 4. Các trang được mã hóa giữa Bộ nhớ đệm Firebird và bộ nhớ đệm tệp HĐH
Vì vậy, các trang cơ sở dữ liệu được mã hóa trong bộ nhớ đệm tệp HĐH nhưng đến bộ nhớ đệm trang Firebird ở dạng không mã hóa, và ngược lại.
Phần của phần mềm Firebird chịu trách nhiệm mã hóa/giải mã được gọi là “plugin mã hóa”. Vì phần lớn các triển khai plugin (mà các tác giả biết đến) được gọi là DbCrypt, chúng tôi sẽ gọi nó là DbCrypt.
Trong hình dưới đây, bạn có thể thấy các biến thể cho Windows (DbCrypt.dll) và Linux (libDbCrypt.so):


Hình 5. Plugin mã hóa (DbCrypt) thực hiện mã hóa/giải mã
Nếu bạn nhìn vào bức tranh này đủ lâu, câu hỏi tiếp theo sẽ xuất hiện khá nhanh: làm thế nào DbCrypt có được khóa thích hợp để mã hóa/giải mã các trang cơ sở dữ liệu?
Câu trả lời - có một plugin khác để quản lý khóa.
Tên điển hình cho việc quản lý khóa là KeyHolder, đóng vai trò là cơ sở lưu trữ/quản lý khóa cho plugin mã hóa (DbCrypt). KeyHolder triển khai giao diện quản lý khóa được DbCrypt sử dụng.

Hình 6. DbCrypt và KeyHolder
“Quản lý khóa” nghĩa là gì?
Trong trường hợp đơn giản nhất, DbCrypt có thể đọc các khóa từ tệp trên máy chủ. Tệp có thể là một tệp văn bản thuần túy đơn giản, có thể được giấu ở một nơi “bí mật” hoặc trên ổ USB, hoặc có thể là một tệp được mã hóa (ví dụ: sử dụng Windows Crypto API, hoặc với một khóa nội bộ tích hợp).
Tệp khóa có thể chứa nhiều khóa, được lưu trữ, để thuận tiện, dưới dạng danh sách có tên, và có thể trông như thế này (ví dụ dưới đây được lấy từ Encryption Framework IBSurgeon):
Key=Red 0xec,0xa1,0x52,0xf6,0x4d,0x27,0xda,0x93,0x53,0xe5,0x48,0x86,0xb9,0x7d,0xe2,0x8f,0x3b,0xfa,0xb7,0x91,0x22,0x5b,0x59,0x15,0x82,0x35,0xf5,0x30,0x1f,0x04,0xdc,0x75,
Key=Green 0xab,0xd7,0x34,0x63,0xae,0x19,0x52,0x00,0xb8,0x84,0xa3,0x44,0xbd,0x11,0x9f,0x72,0xe0,0x04,0x68,0x4f,0xc4,0x89,0x3b,0x20,0x8d,0x2a,0xa7,0x07,0x32,0x3b,0x5e,0x74,
Khi cơ sở dữ liệu được mã hóa, các trang tiêu đề của nó vẫn không được mã hóa để lưu trữ thông tin về plugin mã hóa và tên của khóa, và bạn có thể xem thông tin này bằng lệnh “gstat -h databasename”:
Database header page information:
....
Creation date Jan 11, 2017 15:12:20
Attributes force write, encrypted, plugin DBCRYPT
Variable header data:
Crypt checksum: MUB2NTJqchh9RshmP6xFAiIc2iI=
Key hash: ask88tfWbinvC6b1JvS9Mfuh47c=
Encryption key name: RED
Sweep interval: 0
*END*
DbCrypt có thể xử lý các trang cho nhiều cơ sở dữ liệu và nhiều khóa:

Hình 7. Nhiều khóa cho nhiều cơ sở dữ liệu trên cùng một máy chủ (tức là, một phiên bản Firebird)
Chọn khóa chính xác
Các nhà phát triển thường đặt câu hỏi “Làm thế nào plugin nhận biết khóa nào dành cho cơ sở dữ liệu nào?” Câu trả lời khá đơn giản: bằng tên khóa, được lưu trữ trên trang tiêu đề của cơ sở dữ liệu.
Ít thường xuyên hơn, nhưng vẫn là câu hỏi quan trọng - điều gì sẽ xảy ra nếu tên khóa giống như được ghi trong tiêu đề, nhưng giá trị khóa khác nhau? Để ngăn ngừa lỗi đọc trang do sai khóa, plugin DbCrypt lưu trữ chuỗi kiểm tra được mã hóa (các chữ số 0…F) trên tiêu đề, và sau đó khi khóa được kích hoạt, plugin cố gắng mã hóa dữ liệu mẫu bằng một khóa và so sánh hàm băm của nó với kết quả được lưu trữ, để đảm bảo rằng giá trị khóa được truyền vào thực sự chính xác cho cơ sở dữ liệu cụ thể này.
Cách tiếp cận khi plugin mã hóa đọc trực tiếp các khóa, đơn giản và có lợi cho việc gỡ lỗi, kiểm tra hiệu suất, v.v., vì nó triển khai mã hóa một cách minh bạch: tức là, các ứng dụng khách và công cụ nhà phát triển không biết rằng cơ sở dữ liệu được mã hóa.
Tuy nhiên, trong thực tế, chúng ta cần hạn chế quyền truy cập của các ứng dụng vào cơ sở dữ liệu: chỉ ứng dụng khách có khóa mới có thể kết nối với cơ sở dữ liệu được mã hóa.
Để làm điều này, chúng ta cần plugin KeyHolder, để lấy khóa từ ứng dụng khách (thường nằm trên một máy tính khác) thông qua giao thức mạng Firebird.
2.3. Làm thế nào để bảo vệ việc truyền khóa?
Có thể chúng ta muốn bảo vệ cơ sở dữ liệu trong tình huống khách hàng quyết định truy cập trực tiếp vào cơ sở dữ liệu được mã hóa, bỏ qua các ứng dụng được ủy quyền (nhiều nhà cung cấp muốn hạn chế dữ liệu khỏi truy cập trực tiếp, dù chỉ đọc hay đọc-ghi).
Điều này tương đương với tình huống kẻ xâm nhập có quyền truy cập vào máy chủ nhưng không có khóa.
Hãy xem xét các kịch bản tấn công sau để đánh cắp khóa ở phía máy chủ:
- Khi kẻ xâm nhập tạo plugin mã hóa giả (DBCrypt.dll) và đặt nó trên máy chủ, và khi KeyHolder truyền khóa, DbCrypt giả sẽ tạo bản dump của khóa:

Hình 8. Tấn công bằng DbCrypt.dll giả
- Khi kẻ xâm nhập tạo file firebird.exe giả và tạo bản dump bằng nó:

Hình 9. Tấn công bằng firebird.exe giả
Để bảo vệ khỏi các cuộc tấn công như vậy, việc triển khai tốt các plugin mã hóa và quản lý khóa cần phải bảo vệ quá trình trao đổi khóa.
Trao đổi khóa có thể được bảo vệ bằng mã hóa bất đối xứng với cặp khóa công khai/riêng tư.
Các khóa này được tạo trong quá trình build và được tích hợp sẵn cho cặp plugin mã hóa và quản lý khóa cụ thể. Để bảo vệ tốt nhất, cần sử dụng các cặp DbCrypt/KeyHolder được xây dựng đặc biệt.

Khi DbCrypt và KeyHolder trao đổi khóa, chúng sử dụng giao thức sau (được đơn giản hóa, nhưng ý tưởng rõ ràng, tôi nghĩ vậy):
DbCrypt → KeyHolder:
Đưa Cho Tôi Khóa Cơ Sở Dữ Liệu Với Salt Này
KeyHolder:
Mã Hóa DbKey Bằng Khóa Công Khai Sử Dụng salt từ DbCrypt
Chuyển DbKey Đã Mã Hóa Cho DbCrypt
DbCrypt:
Giải Mã DbKey Bằng Khóa Riêng Tư
Xác Thực Tính Đúng Đắn Của Salt
Sẵn Sàng Làm Việc
Giao thức tương tự cũng được sử dụng để trao đổi khóa giữa các phiên bản KeyHolder, và để trao đổi khóa giữa ứng dụng khách và KeyHolder.
Xin lưu ý rằng việc mã hóa và tính đúng đắn của các khóa được chuyển phụ thuộc vào việc triển khai plugin, công cụ Firebird chỉ cung cấp dịch vụ truyền cấp thấp cơ bản “gửi N byte từ phiên bản plugin này đến phiên bản plugin kia”.
Tóm tắt cho phần phía máy chủ của mã hóa
- Mã hóa/giải mã được thực hiện bởi plugin mã hóa cơ sở dữ liệu (DbCrypt), từng trang một, trong quá trình trao đổi dữ liệu giữa bộ nhớ cache file của Hệ điều hành và bộ nhớ cache trang của Firebird
- Quản lý khóa có thể được triển khai theo cách đơn giản khi DbCrypt đọc khóa trực tiếp, nhưng thông thường, nó được thực hiện với plugin quản lý khóa (KeyHolder)
Bây giờ hãy khám phá cách các ứng dụng khách làm việc với cơ sở dữ liệu được mã hóa.
3. Cách mã hóa Firebird hoạt động ở phía máy khách
3.1. Ứng dụng gốc
Để hiểu điều gì xảy ra khi ứng dụng khách kết nối đến cơ sở dữ liệu được mã hóa, hãy xem xét quy trình kết nối thông thường đến cơ sở dữ liệu không mã hóa cho các ứng dụng gốc.
Xin lưu ý: từ đây trở đi, “gốc” có nghĩa là ứng dụng như vậy thiết lập kết nối mạng với máy chủ bằng fbclient.dll, thông thường, ứng dụng như vậy được xây dựng bằng Delphi, C++, PHP. Không giống như các ứng dụng gốc, Java và .NET triển khai phiên bản giao thức của riêng họ, chúng sẽ được xem xét bên dưới.
Quy trình kết nối:
- Ứng dụng khách tải thư viện máy khách
- fbclient.dll - ứng dụng Windows gốc
- libfbclient.so - ứng dụng Linux gốc
- Ứng dụng khách khởi tạo kết nối, gửi
- Tên người dùng, ví dụ: SYSDBA
- Mật khẩu, ví dụ: masterkey
- Đường dẫn/bí danh của cơ sở dữ liệu
Trong trường hợp cơ sở dữ liệu được mã hóa, cần thêm một bước: cần truyền tên của khóa mã hóa và giá trị của nó.
Điều quan trọng cần nói là việc truyền khóa nên được thực hiện trước khi kết nối thông thường, do thực tế là các trang dữ liệu chứa siêu dữ liệu, bao gồm tên chủ sở hữu cơ sở dữ liệu, bộ ký tự, v.v., đều được mã hóa.
Vì vậy, điều này dẫn chúng ta đến những điều sau:
- Cần có thêm một vòng lặp mạng để truyền khóa trước khi kết nối thông thường
- Việc chuyển khóa từ ứng dụng khách đến Firebird yêu cầu lập trình với việc sử dụng mã hóa bất đối xứng và triển khai giao diện callback, điều này có thể khá phức tạp. Để đơn giản hóa nhiệm vụ này, các nhà cung cấp plugin cung cấp ví dụ về mã kết nối, hoặc, như trong khung plugin IBSurgeon, tạo thư viện bổ sung fbcrypt.dll/libfbcrypt.so, thư viện này triển khai giao diện dễ sử dụng phù hợp để chuyển khóa từ ứng dụng khách.
Để kết nối một ứng dụng gốc (sử dụng fbclient.dll) đến cơ sở dữ liệu được mã hóa, cần thực hiện 3 lệnh gọi. Dưới đây là ví dụ Delphi (được đơn giản hóa, không xử lý lỗi):
Trong trình xử lý sự kiện BeforeConnect:
fbcrypt_init(PAnsiChar(‘C:\Firebird30.bclient.dll’));
fbcrypt_key(‘RED’, ‘0xec,0xa1,0x52,0xf6,...’));
fbcrypt_callback(nil);
// Sau đó kết nối như bình thường
Database1.Active:=True;
Trong hình dưới đây, bạn có thể thấy tổng quan về quy trình kết nối với cơ sở dữ liệu được mã hóa trong ứng dụng gốc:

Hình 10. Quy trình kết nối đến cơ sở dữ liệu được mã hóa cho các ứng dụng gốc
Còn về an toàn luồng trong trường hợp ứng dụng khách đa luồng thì sao?
Hãy để tôi nhắc bạn về một số điểm chung trong việc triển khai các ứng dụng khách đa luồng.
Kể từ Firebird 2.5, nhiều luồng bên trong ứng dụng có thể sử dụng an toàn một attachment duy nhất đến cơ sở dữ liệu, vì tất cả đồng bộ hóa cần thiết được thực hiện bên trong fbclient.dll.
Tuy nhiên, trong trường hợp này, các luồng sẽ chỉ có thể làm việc với attachment lần lượt từng cái một.
Điều này ổn đối với các ứng dụng không yêu cầu trao đổi dữ liệu hiệu suất cao với cơ sở dữ liệu - nếu việc chờ đợi để thực hiện truy vấn SQL từ một luồng khi một luồng khác đang thực thi SQL khác không phải là vấn đề, thì việc sử dụng mô hình đơn giản khi 1 attachment được chia sẻ giữa nhiều luồng sẽ dễ dàng hơn.
Nếu ứng dụng yêu cầu thực thi các truy vấn SQL song song (tức là ứng dụng khách quy mô đầy đủ), tốt hơn nên sử dụng một luồng riêng cho mỗi kết nối.
Tình huống trao đổi khóa cho các attachment đến cơ sở dữ liệu được mã hóa phức tạp hơn một chút.
Trên mỗi attachment, thư viện máy khách chuyển các khóa từ máy khách, nhưng điều này không liên quan trực tiếp đến các luồng trong ứng dụng khách, tình huống phụ thuộc vào API được sử dụng.
Như bạn đã biết, thư viện máy khách Firebird từ 3.0 cung cấp 2 loại API: API hướng đối tượng mới, dựa trên khái niệm providers, và API isc_ kế thừa, được triển khai như một giải pháp thay thế, để duy trì tương thích với các driver Firebird cũ.
Nếu sử dụng API máy khách hướng đối tượng mới, chỉ cần tạo provider, cung cấp cho nó các khóa cần thiết, sau đó sử dụng nó cho các attachment mới.
Nếu sử dụng API máy khách isc_, cho mỗi attachment, thư viện máy khách sẽ tạo provider tạm thời của riêng nó, provider này không hiển thị trực tiếp hoặc không thể truy cập bởi người dùng cuối.
Trong trường hợp này, khóa được chuyển chính xác từ luồng nơi isc_attach_database được gọi, và bộ nhớ cục bộ của luồng được sử dụng để lưu trữ khóa đó.
Trong thực tế, vì hầu hết các thư viện máy khách sử dụng API isc_ (hiện tại, trong số các driver phổ biến, chỉ có driver Python sử dụng API OO), cần phải gọi fb_database_crypt_callback() trong mỗi luồng kết nối đến cơ sở dữ liệu được mã hóa.
Các lệnh gọi chuyển khóa (các lệnh gọi fbcrypt.dll trong ví dụ FEPF) phải được thực hiện trước khi kết nối, trong cùng luồng nơi kết nối sẽ được thiết lập.
Khi chúng ta làm việc với nhiều cơ sở dữ liệu (ví dụ: máy chủ web SaaS với nhiều cơ sở dữ liệu khách hàng), điều quan trọng cần nhớ là mỗi lần gọi fbcrypt_key() sẽ thêm một khóa vào bộ lưu trữ KeyHolder, liên kết với kết nối hiện tại.
Giá trị khóa phải được đặt trước khi kết nối, sau khi attachment, giá trị khóa không thể thay đổi.
Trong trường hợp detach, các khóa không được dỡ bỏ, chúng sẽ được giữ trong bộ nhớ cho đến khi dỡ fbcrypt.dll.
3.2. Ứng dụng Java
Driver Java (JayBird) có triển khai riêng (bằng Java thuần túy) của giao thức kết nối Firebird. Jaybird 4 (và 3.0.4+) thêm hỗ trợ cho các callback mã hóa cơ sở dữ liệu Firebird 3 trong triển khai Java thuần túy của giao thức phiên bản 13.
Từ Jaybird 4 Readme:
" Triển khai hiện tại đơn giản và chỉ hỗ trợ trả lời với một giá trị tĩnh từ thuộc tính kết nối. Lưu ý rằng phản hồi giá trị tĩnh cho mã hóa cơ sở dữ liệu không an toàn lắm vì nó có thể dễ dàng dẫn đến các cuộc tấn công phát lại hoặc lộ khóa ngoài ý muốn.
Các phiên bản tương lai của Jaybird (có thể là 5) sẽ giới thiệu hỗ trợ plugin cho các plugin mã hóa cơ sở dữ liệu yêu cầu callback phức tạp hơn."
Thực tế, điều này có nghĩa là chúng ta cần đặt giá trị cho callback mã hóa (thông thường, đó là tên khóa và cặp giá trị khóa) trong thuộc tính kết nối dbCryptConfig.
Ví dụ:
edConnectionString.setText("jdbc:firebirdsql://localhost/g:/Databases/ODS12/crypt.fdb?lc_ctype=utf8&dbCryptConfig=MyKey:0xec,0xa1,0x52,0xf6,0x4d,0x27,0xda,0x93,0x53,0xe5,0x48,0x86,0xb9,0x7d,0xe2,0x8f,0x3b,0xfa,0xb7,0x91,0x22,0x5b,0x59,0x15,0x82,0x35,0xf5,0x30,0x1f,0x04,0xdc,0x75,");
Cũng có thể chỉ định chuỗi ở dạng base64 - từ readme:
" Các chuỗi có tiền tố base64:: phần còn lại của chuỗi được giải mã dưới dạng base64 thành byte.
Các ký tự đệm = là tùy chọn, nhưng khi có mặt, chúng phải hợp lệ (nghĩa là: nếu bạn sử dụng đệm, bạn phải sử dụng đúng số lượng ký tự đệm cho độ dài).
Khi giá trị được mã hóa base64 chứa +, nó phải được thoát dưới dạng %2B trong URL JDBC. Để tương thích ngược với Jaybird 3, chúng tôi không thể chuyển sang biến thể an toàn URL của base64. "
Trong triển khai plugin quản lý khóa của IBSurgeon, việc truyền khóa theo cách này được coi là ít an toàn hơn hoặc kém an toàn: nếu mã hóa giao thức mạng không được bật ( nhân tiện, để bật nó, hãy đặt trong firebird.conf WireCrypt=Required và không sử dụng xác thực kế thừa), khóa có thể dễ dàng bị phát hiện bằng công cụ phân tích lưu lượng mạng như WireShark, vì vậy, để cho phép truyền khóa theo cách này, cần đặt UnsafeClient=true trong KeyHolder.conf của plugin quản lý khóa IBSurgeon.
3.3. Ứng dụng .NET
Provider Firebird.NET triển khai sơ đồ trao đổi khóa tương tự cho các cơ sở dữ liệu được mã hóa và cũng yêu cầu đặt tham số UnsafeClient=true trong KeyHolder.conf trong FEPF.
Ví dụ .NET về chuỗi kết nối cho cơ sở dữ liệu được mã hóa:
string connectionString =
"User=SYSDBA;" +
"Password=masterkey;" +
"Database=G:\\Databases\\ODS12.RYPT.FDB;" +
"DataSource=localhost;" +
"Port=3053;" +
"Dialect=3;" +
"Charset=NONE;" +
"Role=;" +
"Connection lifetime=15;" +
"Pooling=true;" +
"MinPoolSize=0;" +
"MaxPoolSize=50;" +
"Packet Size=8192;" +
"ServerType=0;" +
"cryptkey = TXlLZXk6MHhlYywweG…...;";
Bạn có thể nhận thấy rằng khóa mã hóa trông khác với ví dụ cho các ứng dụng gốc và JayBird, điều này là do nó là kết quả của phép biến đổi Base64, vì vậy để có khóa cho ứng dụng .NET hoặc Java, cần tính base64 từ chuỗi:
“MyKey:0xec,0xa1,0x52,0xf6,0x4d,0x27,0xda,0x93,0x53,0xe5,0x48,0x86,0xb9,0x7d,0xe2,0x8f,0x3b,0xfa,0xb7,0x91,0x22,0x5b,0x59,0x15,0x82,0x35,0xf5,0x30,0x1f,0x04,0xdc,0x75,”
và sử dụng nó làm tham số cho “cryptkey=xxx;” với “;” ở cuối chuỗi kết nối.
4. Cài đặt và Cấu hình
Để kích hoạt plugin mã hóa cơ sở dữ liệu và quản lý khóa được sử dụng, cần chỉ định tên của plugin mã hóa trong tệp cấu hình Firebird firebird.conf:
KeyHolderPlugin = KeyHolder
Hoặc, cách khác, trong databases.conf, cho bí danh của cơ sở dữ liệu được mã hóa:
myencrypted = C:\Temp\EMPLOYEE30.MPLOYEE30.FDB { KeyHolderPlugin = KeyHolder }
Sau đó, cần kiểm tra rằng tất cả các tệp cần thiết cho plugin đều có trên máy chủ.
Ví dụ dưới đây dành cho FEPF của IBSurgeon, nhưng các plugin khác cũng tương tự:
trong %FirebirdFolder$\plugins
• DbCrypt.dll
• DbCrypt.conf
• KeyHolder.dll
• KeyHolder.conf - chỉ dành cho chế độ gỡ lỗi!
Trong %FirebirdFolder$
• fbcrypt.dll
• libcrypto-1_1-x64.dll
• libssl-1_1-x64.dll
• firebird.msg
Sau đó, chúng ta có thể thực hiện mã hóa thử nghiệm trên máy chủ, bằng cách sử dụng isql:
isql.exe localhost:C:\Temp\EMPLOYEE30.MPLOYEE30.FDB -user SYSDBA -pass masterkey
SQL>alter database encrypt with dbcrypt key red;
SQL> show database;
Database: localhost:C:\Temp\EMPLOYEE30.MPLOYEE30.FDB
….
ODS = 12.0
Database encrypted
Default Character set: NONE
Nếu bạn đang dùng Linux, hãy nhớ rằng chữ hoa/chữ thường rất quan trọng, vì vậy lệnh sẽ là:
alter database encrypt with "DbCrypt" key Red;
Bây giờ, chúng ta có thể kiểm tra truy cập của máy khách vào cơ sở dữ liệu đã mã hóa. Để làm điều này, chúng ta sẽ xóa (hoặc đổi tên hoặc chỉnh sửa) tệp cấu hình KeyHolder.conf, và thử kết nối đến cơ sở dữ liệu đã mã hóa bằng ứng dụng kiểm tra đơn giản.
Để làm điều này, chúng ta phải đặt các tệp sau vào thư mục chứa ứng dụng máy khách:
- Ứng dụng demo từ FEPF- CryptTest.exe (32bit)
- Các tệp bắt buộc:
- fbclient.dll
- fbcrypt.dll
- libcrypto-1.1.dll
- libssl-1.1-x64.dll
- Các tệp tùy chọn:
- firebird.conf
- trong plugins
- ◦ KeyHolder.conf
- ◦ keyhodler.dll
Trong một số triển khai của plugin quản lý khóa, có thể tải khóa trong thư viện máy khách (fbclient.dll), mà không cần sửa đổi phần mềm máy khách.
Điều này cho phép hoạt động minh bạch của các công cụ phát triển Firebird (như Firebird SQL Studio, DatabaseWorkbench, IBExpert, FlameRobin, RedExpert, v.v.) và sử dụng minh bạch các công cụ dòng lệnh Firebird (gfix.exe, nbackup.exe, v.v.).
5. Cách theo dõi tiến trình mã hóa
Firebird chỉ mã hóa cơ sở dữ liệu khi có các kết nối đang hoạt động. Quá trình mã hóa chạy trong một luồng song song riêng biệt, và đối với các cơ sở dữ liệu lớn, việc mã hóa hoàn toàn có thể mất nhiều thời gian.
Để theo dõi quá trình mã hóa, hãy chạy truy vấn SQL từ MON$:
select mon$crypt_page * 100.0 / mon$pages as Percent from mon$database;
commit;
hoặc thực thi công cụ gstat với tùy chọn đặc biệt:
gstat -e dbname
Database "D:\ENCDB\TESTENCRYPT.FDB"
Gstat execution time Tue Mar 16 11:45:11 2021
Database header page information:
Flags 0
Generation 10697
System Change Number 3
Page size 8192
ODS version 12.0
Oldest transaction 7053
Oldest active 7054
Oldest snapshot 7054
Next transaction 7054
Sequence number 0
Next attachment ID 17834
Implementation HW=Intel/i386 little-endian OS=Windows CC=MSVC
Shadow count 0
Page buffers 0
Next header page 0
Database dialect 3
Creation date Oct 9, 2019 6:42:31
Attributes encrypted, plugin DBCRYPT
Variable header data:
Database backup GUID: {866B4967-ED58-427E-A481-DB9206CEA2ED}
Crypt checksum: MUB2NTJqchh9RshmP6xFAiIc2iI=
Key hash: ask88tfWbinvC6b1JvS9Mfuh47c=
Encryption key name: RED
Database GUID: {323FE494-1771-4608-E99D-C1B69C84578B}
*END*
Data pages: total 105, encrypted 105, non-crypted 0
Index pages: total 95, encrypted 95, non-crypted 0
Blob pages: total 0, encrypted 0, non-crypted 0
Generator pages: total 1, encrypted 1, non-crypted 0
Gstat completion time Tue Mar 16 11:45:11 2021
Xin lưu ý rằng việc thực thi gstat có thể là một quá trình kéo dài.
6. Tóm tắt
- Mã hóa cơ sở dữ liệu Firebird là một tính năng mạnh mẽ để bảo vệ thông tin trong cơ sở dữ liệu khỏi truy cập trái phép.
- Quá trình mã hóa yêu cầu một thư viện động phía máy chủ - plugin mã hóa (thường được gọi là DbCrypt), và trong phần lớn các trường hợp, plugin quản lý khóa (thường được gọi là KeyHolder).
- Việc triển khai an toàn và đáng tin cậy của các plugin DbCrypt và KeyHolder nên được thực hiện với các loại tấn công phổ biến nhất được xem xét.
- Để làm việc với cơ sở dữ liệu đã mã hóa, các ứng dụng máy khách phải truyền khóa mã hóa.
- Việc cài đặt và cấu hình plugin mã hóa phía máy chủ rất đơn giản, chỉ cần 1 tham số trong firebird.conf/databases.conf và một số tệp.
- Quá trình mã hóa có thể kéo dài, được thực hiện trong một luồng nền riêng biệt, tiến trình có thể được theo dõi bằng lệnh gọi MON$ hoặc gstat.
Tiếp theo là gì?
Chúng tôi đang làm việc trên bài kiểm tra hiệu suất chi tiết của mã hóa cơ sở dữ liệu Firebird. Nhìn chung, hiệu suất giảm 4-8%, nhưng điều này phụ thuộc vào phần cứng và cài đặt Firebird. Hãy theo dõi!
Liên hệ với chúng tôi:
Vui lòng gửi đề xuất, lỗi chính tả, lỗi, v.v. và mọi câu hỏi qua email: [email protected]