Tải bản đầy đủ (.doc) (42 trang)

bảo vệ mã nguôn chương trình java

Bạn đang xem bản rút gọn của tài liệu. Xem và tải ngay bản đầy đủ của tài liệu tại đây (839.42 KB, 42 trang )

LỜI MỞ ĐẦU
Hiện nay lĩnh vực công nghệ phần mềm phát triển hết sức mạnh mẽ, các sản
phẩm phần mềm xuất hiện ở mọi ngành nghề từ y tế bệnh viện, tài chính ngân hàng
đến giáo dục hay các cơ quan nhà nước. Mỗi sản phẩm phần mềm đều chứa đựng
những thuật toán cốt yếu, những dữ liệu nhạy cảm tối quan trọng phản ảnh giá trị
của phần mềm đó, đem lại sự cạnh tranh tốt so với các sản phẩm phần mềm tương
tự. Ví dụ như trong bộ phần mềm kế toán Misa, các quy trình nghiệp vụ được tích
hợp trong đó cùng với các thuật toán xử lý dữ liệu một cách nhanh và ổn định nhất,
hay như máy tìm kiếm của google với các thuật toán tìm kiếm dữ liệu nhanh nhất,
… Mỗi sản phẩm đó đã khẳng định vị thế vượt trội của nó trong từng lĩnh vực
tương ứng cũng chính nhờ các thuật toán cốt lõi được cài đặt trong đó. Các công ty
đối thủ cạnh tranh trực tiếp luôn tìm cách đánh cắp mã nguồn, lấy được thuật toán
và các dữ liệu quan trọng để cài tiến, xây dựng sản phẩm của riêng họ vượt trội hơn.
Chính vì thế vấn đề bảo vệ mã nguồn ứng dụng tránh sự đánh cắp, dịch ngược thu
được mã nguồn và các dữ liệu quan trọng là một bài toán hết sức cấp thiết và có ý
nghĩa thực tiễn cao trong thời đại bùng nổ của công nghệ thông tin hiện nay.
Trong phát triển phầm mềm hiện nay, Java là một môi trường mạnh mẽ đối với
các công ty, các nhà phát triển phần mềm. Tuy nhiên, với ngôn ngữ lập trình java
các ứng dụng rất dễ bị tấn công dịch ngược mã nguồn vì java là một ngôn ngữ có
tính cấu trúc rõ ràng, định dạng bytecode của ứng dụng hàm chứa nhiều thông tin
quan trọng phục vụ cho quá trình thông dịch của máy ảo java. Trong báo cáo này,
chúng tôi đã đưa ra hai giải pháp nhằm ngăn chặn quá trình tấn công đánh cắp mã
nguồn đối với các ứng dụng java. Ở giải pháp thứ nhất, chúng tôi sẽ cài đặt các
thuật toán cũng như dữ liệu nhạy cảm được đặt trên server và chia sẻ cho ứng dụng
phía người dùng dưới dạng các dịch vụ. Phần mềm ứng dụng phía người dùng thực
hiện các tính toán thông qua việc gọi đến các dịch vụ trên máy server. Khi đó, các
thành phần dữ liệu nhạy cảm cũng như các thuật toán cốt lõi của ứng dụng được
quản lý tập trung trên server nên ứng dụng nếu bị dịch ngược mã nguồn cũng không
bị lỗ các thuật toán, dữ liệu quan trọng đó. Trong giải pháp thứ hai, chúng tôi thực
hiện xáo trộn mã nguồn ứng dụng với kỹ thuật Obfuscation. Khi đó, nếu có bị dịch
1




ngược thì kẻ tấn công cũng chỉ thu được mã nguồn rất hỗn độn, rất khó đọc và rất
khác biệt với mã nguồn gốc nên các thuật toán hay dữ liệu của ứng dụng cũng được
che giấu.
Trong báo cáo này, chúng tôi trình bày những nội dung đã nghiên cứu và các kết
quả đạt được của nhóm đề tài trong 3 chương:
Chương 1: Nguy cơ bị lộ mã nguồn chương trình java. Trong chương này,
chúng tôi chỉ ra quá trình phát triển một ứng dụng java từ viết code đến biên dịch và
thông dịch chương trình đó. Sau đó chúng tôi phân tích nguy cơ khiến ứng dụng
java có thể bị lộ mã nguồn.
Chương 2: Kỹ thuật dịch ngược mã nguồn. Chương này trình bày rõ cơ sở của
việc dịch ngược một chương trình java và cách thức thực hiện để dịch ngược
chương trình từ mã bytecode của ứng dụng thông qua các công cụ dịch ngược.
Chương 3: Nghiên cứu kỹ thuật bảo vệ mã nguồn ứng dụng Java. Trong đó,
nhóm đề tài trình bày hai giải pháp thực hiện việc bảo vệ chống bị lộ mã nguồn.
Trong quá trình thực hiện đề tài, nhóm chúng tôi đã cố gắng và làm việc nghiêm
túc khoa học. Tuy nhiên, do hạn chế về thời gian cũng như kiến thức về vấn đề này
cùng với sự phát triển mạnh mẽ của công nghệ nên không tránh khỏi những thiếu
sót, hạn chế. Chúng tôi rất mong nhận được sự đóng góp của các nhà khoa học, các
cấp quản lý cùng đồng nghiệp để chỉnh sửa bổ sung và hoàn thiện hơn nữa.
Chúng tôi xin chân thành cảm ơn Ban giám đốc, phòng Quản lý NCKH, khoa
Công nghệ thông tin, các phòng chức năng – Học viện Kỹ thuật Mật mã và các bạn
đồng nghiệp đã tạo điều kiện thuận lợi cho việc nghiên cứu và hoàn thiện đề tài này.

Hà Nội, tháng 11/2013

2



CHƯƠNG 1: NGUY CƠ BỊ LỘ MÃ
NGUỒN CHƯƠNG TRÌNH JAVA
1. Môi trường và quá trình biên dịch một chương trình Java
Java là một ngôn ngữ lập trình hướng đối tượng được đưa ra bởi Sun
MicroSysem. Mỗi chương trình được viết bằng ngôn ngữ java có thể chạy trên bất
kỳ một hệ thống nào có cài máy ảo Java (Java Virtual Machine).
Tất cả các chương trình muốn thực thi được thì phải được biên dịch ra mã
máy. Mã máy của từng kiến trúc CPU của mỗi máy tính là khác nhau (tập lệnh
mã máy của CPU Intel, CPU Solarix, CPU Macintosh … là khác nhau), vì vậy
trước đây một chương trình sau khi được biên dịch xong chỉ có thể chạy được
trên một kiến trúc CPU cụ thể nào đó. Đối với CPU Intel chúng ta có thể chạy các
hệ điều hành như Microsoft Windows, Unix, Linux, OS/2, … Chương trình thực
thi được trên Windows được biên dịch dưới dạng file có đuôi .EXE còn trên
Linux thì được biên dịch dưới dạng file có đuôi .ELF, vì vậy trước đây một
chương trình chạy được trên Windows muốn chạy được trên hệ điều hành khác
như Linux chẳng hạn thì phải chỉnh sửa và biên dịch lại. Ngôn ngữ lập trình
Java ra đời, nhờ vào máy ảo Java mà khó khăn nêu trên đã được khắc phục. Một
chương trình viết bằng ngôn ngữ lập trình Java sẽ được biên dịch ra mã của máy
ảo java (mã java bytecode). Sau đó máy ảo Java chịu trách nhiệm chuyển mã
java bytecode thành mã máy tương ứng. Sun Microsystem chịu trách nhiệm phát
triển các máy ảo Java chạy trên các hệ điều hành trên các kiến trúc CPU khác
nhau.
Quá trình thông dịch:
Java là một ngôn ngữ lập trình vừa biên dịch vừa thông dịch. Chương trình
nguồn viết bằng ngôn ngữ lập trình Java có đuôi *.java. Trước tiên, tập tin .Java
(chứa mã nguồn java) được biên dịch thành tập tin có đuôi *.class (chứa mã
bytecode – định dạng sau khi biên dịch từ mã nguồn) và sau đó sẽ được trình thông
dịch thông dịch thành mã máy tương ứng với nền tảng cài đặt máy ảo Java.
3



Dịch và chạy một chương trình Java:
Giả sử chúng ta có đoạn mã sau:
/*Viết chương trình in dòng HelloWorld lên màn hình Console*/
class HelloWorldApp{
public static void main(String[] args){
//In dong chu “HelloWorld”
System.out.println(“Hello World!!!”);
}
}
Lưu lại với tên HelloWorldApp.java sau đó tiến hành dịch và chạy như sau:

-

Mở cửa sổ Command Prompt

-

Chuyển đến thư mục chứa file java

-

Gọi lệnh javac để compile mã nguồn thành mã bytecode:
HelloworldApp.java

-

Gọi lệnh java để thực thi chương trình: java HelloworldApp

javac


2. Nguy cơ bị lộ mã nguồn chương trình Java
Trong phát triển phần mềm, java là một môi trường mạnh để xây dựng các ứng
dụng. Với ngôn ngữ java chúng ta có thể phát triển rất nhiều loại ứng dụng như:
applet (để nhúng trên các trang HTML), ứng dụng console (chạy dưới dạng màn
4


hình console), ứng dụng đồ họa người dùng (application GUI), các applet (các
chương trình web server) hay các ứng dụng chạy trên điện thoại di động (J2ME), …
Trong mỗi ứng dụng có thể chứa rất nhiều các thuật toán quan trọng là mấu chốt, là
chìa khóa thành công của phần mềm. Khi đó các công ty đối thủ cạnh tranh luôn
luôn mong muốn nắm bắt được những thuật toán cốt lõi trong phần mềm đó, vì vậy
họ sẽ tìm cách lấy được mã nguồn chương trình. Hơn thế nữa, trong các ứng dụng
ngoài thuật toán còn có những phần dữ liệu hết sức quan trọng (ví dụ như thông tin
truy cập vào server data, hay các resource bên trong ứng dụng, …). Việc có được
những dữ liệu đó cũng rất hữu ích đối với các công ty đối thủ. Vậy để có được
những thuật toán, những dữ liệu quan trọng đó thì họ phải làm thế nào? Một cách
thức hữu hiệu nhất là dịch ngược ứng dụng để thu được mã nguồn, và từ đó họ sẽ có
được những gì mình cần.
Một ứng dụng được viết bằng Java, khi chuyển giao ứng dụng cho khách hàng sẽ
chuyển cho họ các file sau khi được biên dịch từ mã nguồn java thành mã bytecode
(các file có đuôi .class). Ứng dụng sẽ được thông dịch thành mã máy thích hợp để
được thực thi thông qua máy ảo Java (Java Virtual Machine – JVM) được cài đặt
trên máy người dùng. Các file .class này hoàn toàn được che giấu đối với người
dùng, họ không thể đọc hiểu được những file đã được biên dịch này. Tuy nhiên, với
sự trợ giúp của các công cụ dịch ngược hiện nay (decompiler tools) thì chúng ta
hoàn toàn có thể dịch ngược từ mã bytecode thành mã nguồn java như ban đầu.
Giả sử chúng ta có một chương trình java đơn giản như sau:
import java.io.*;

class Demo
{
public static void main(String args[]) throws Exception
{
int size;
InputStream f = new FileInputStream("filedemo.txt");
System.out.println("Bytes available to read:"+ (size = f.available()));
char str[] = new char[300];
for(int count = 0;count < size;count++)
{
5


str[count] = ((char)f.read());
System.out.print(str[count]);
}
System.out.println("");
f.close();
}
}
Lưu lại thành file Demo.java
Biên dịch file Demo.java ta thu được file Demo.class là file chữa mã bytecode
của chương trình, bằng câu lệnh sau: javac Demo.java
Sử dụng công cụ dịch ngược Java decompiler ta sẽ thu được kết quả như sau:

Như vậy bằng kỹ thuật dịch ngược ta hoàn toàn có thể thu được mã nguồn
chương trình, từ đó nắm bắt được các thuật toán cũng như dữ liệu của ứng dụng.
Hiện nay có rất nhiều công cụ hỗ trợ việc dịch ngược mã nguồn ứng dụng từ mã
6



bytecode. Java decompiler là một tool dịch ngược gọn nhẹ và hoàn toàn miễn phí
rất phổ biến trong lĩnh vực này. Với công cụ này chúng ta có thể thực hiện dịch
ngược một ứng dụng với cấu trúc các package phức tạp, kết quả thu được gần như
tương đồng với mã nguồn gốc của ứng dụng.

7


CHƯƠNG 2: KỸ THUẬT DỊCH NGƯỢC
MÃ NGUỒN CHƯƠNG TRÌNH JAVA
1. Cơ sở việc dịch ngược
Trong một môi trường lý tưởng, việc dịch ngược mã nguồn là không cần thiết,
ngay cả khi những nhà phát triển phần mềm không có ý thức làm tốt tài liệu thiết
kế. Tuy nhiên trong thực tế có một số trường tình huống thì việc dịch ngược là hết
sức cần thiết và hữu dụng:
 Khôi phục lại mã nguồn chẳng may bị mất.
 Muốn học cách cài đặt của một thuật toán đặc trưng hay một bí quyết nào
đó.
 Khắc phục những sự cố, lỗi của một ứng dụng hay một thư viện mà nó
không có tài liệu thiết kế tốt.
 Khắc phục những lỗi khẩn cấp trong mã nguồn của bên thứ ba mà trong
tay không có mã nguồn đó.
 Học cách bảo vệ mã nguồn để tránh bị đánh cắp.
Việc dịch ngược sẽ mang lại mã nguồn chương trình từ mã bytecode. Đây là quá
trình đảo ngược của quá trình biên dịch, quá trình này thực hiện được bởi mã
bytecode có cấu trúc khá chuẩn và có tài liệu đặc tả tốt. Ngôn ngữ Java được thiết
kế với khả năng thực thi một cách linh hoạt và thông minh. Mã nguồn chương trình
được biên dịch sang một định dạng bytecode – độc lập với mọi nền tảng, tức là nó
có thể thực thi trên bất kỳ một nền tảng nào, một máy tính nào mà không cần viết

lại mã nguồn, cũng không cần chuyển đổi mã bytecode, mã bytecode được lưu trữ
trong các class file. Mã bytecode là một biểu diễn trung gian của mã nguồn chương
trình và không được mã hóa cho bất kì một nền tảng xác định nào. Tức là, trình biên
dịch không thể tối ưu hóa mã nguồn cho một phần cứng xác định, mã bytecode phải
được thông dịch sang mã máy để thực thi và các thông tin mức cao phải được đính
kèm với mã nguồn để phục vụ cho quá trình thông dịch.
Trong Java, quá trình thông dịch được thực hiện bởi máy ảo Java (Java Virtual
Machine – JVM). JVM là một máy trừu tượng, nó có thể hiểu và thực thi mã
8


bytecode trong một máy thật. JVM phải thực thi được trên bất kỳ một nền tảng nào
mà người dùng muốn chạy chương trình Java trên đó, do vậy nó phải được đặc tả
một cách dễ hiểu.
Java bytecode được mã hóa một cách đơn giản, không tối ưu, chứa đựng nhiều
thông tin mức cao. Việc lưu giữ những thông tin mức cao để cho quá trình thực thi
mã bytecode bởi các máy tính khác nhau, các nền tảng khác nhau nên định dạng của
class file là đơn giản, khá rõ ràng, ít che giấu đối với các nền tảng. Mã bytecode
không được tối ưu nên nó được biểu diễn đơn giản hơn, rõ ràng hơn. Mã bytecode
độc lập kiến trúc, chứa nhiều thông tin về mã nguồn. Do đó, từ mã bytecode chúng
ta dễ dàng dịch ngược được thành mã nguồn java ban đầu.

2. Dịch ngược một chương trình Java
Xuất phát từ những tình huống cần có được mã nguồn từ mã bytecode như đã
nêu trong phần 1, những nhà phát triển môi trường Java cũng đã cung cấp một công
cụ dịch ngược mã nguồn, có tên là javap được cài đặt sẵn trong bộ JDK (Java
Development Kit) của Java.
Cú pháp: javap [options] tên_lớp
Trong đó: options là các tùy chọn của lệnh javap, ví dụ:






help: hiển thị các thông báo hỗ trợ sử dụng lệnh javap
public: hiển thị các lớp, các thành phần là public
protected: hiển thị các lớp, các thành phần là protected
p: hiển thị tất cả các lớp, các thành phần

Giả sử chúng ta có một chương trình Java thực hiện việc hiển thị một dòng chữ
lên màn hình, HelloworldApp.java như sau:
public class HelloWorldApp {
public static void main(String[] args) {
// TODO code application logic here
System.out.println(" Hello world!!!");
}
}

9


Sau khi biên dịch ta thu được file HelloworldApp.class chứa mã bytecode của
chương trình. Đây là định dạng không đọc được như mã nguồn thông thường, nó sẽ
được thông dịch sang mã máy bởi máy ảo java (JVM).

Sử dụng lệnh javap dịch ngược mã bytecode ta thu được mã nguồn chương trình
như sau:

10



Mã nguồn thu được ở trên về cơ bản đã phản ánh được các thành phần trong một
lớp và một phần mã bytecode các câu lệnh của từng phương thức. Từ mã bytecode
ở trên người ta có thể dựng lại được mã nguồn hoàn chỉnh của chương trình java
gốc. Hiện nay có rất nhiều công cụ thương mại cũng như miễn phí, cho phép dịch
ngược mã nguồn hoàn chỉnh. Tiêu biểu là hai công cụ hoàn toàn miễn phí như JAD
và JODE. Các công cụ này chính là sự phát triển từ công cụ cơ sở javap như đã
trình bày ở trên bằng cách phân tích thông tin từ mã bytecode để dựng ngược mã
nguồn hoàn chỉnh của chương trình.
Giả sử chúng ta có một ứng dụng java J2ME thực hiện tính toán và hiển thị lịch
âm – dương, trong đó có phương thức convertSolar2Lunar chuyển đổi ngày dương
lịch sang ngày âm lịch, có mã nguồn gốc như sau:
public static int[] convertSolar2Lunar(int dd, int mm, int yy, double timeZone) {
int lunarDay, lunarMonth, lunarYear, lunarLeap;
int dayNumber = jdFromDate(dd, mm, yy);
int k = INT((dayNumber - 2415021.076998695) / 29.530588853);
int monthStart = getNewMoonDay(k + 1, timeZone);
if (monthStart > dayNumber) {
monthStart = getNewMoonDay(k, timeZone);
}
int a11 = getLunarMonth11(yy, timeZone);
int b11 = a11;
if (a11 >= monthStart) {
lunarYear = yy;
a11 = getLunarMonth11(yy - 1, timeZone);
} else {
lunarYear = yy + 1;
b11 = getLunarMonth11(yy + 1, timeZone);
}
lunarDay = dayNumber - monthStart + 1;

int diff = INT((monthStart - a11) / 29);
lunarLeap = 0;
lunarMonth = diff + 11;
11


if (b11 - a11 > 365) {
int leapMonthDiff = getLeapMonthOffset(a11, timeZone);
if (diff >= leapMonthDiff) {
lunarMonth = diff + 10;
if (diff == leapMonthDiff) {
lunarLeap = 1;
}
}
}
if (lunarMonth > 12) {
lunarMonth = lunarMonth - 12;
}
if (lunarMonth >= 11 && diff < 4) {
lunarYear -= 1;
}
return new int[]{lunarDay, lunarMonth, lunarYear, lunarLeap};
}
Sau khi biên dịch toàn bộ ứng dụng thành định dạng file jar chứa tất cả các file
class (mã bytecode) của chương trình, chúng ta thực hiện dịch ngược với sự trợ
giúp của công cụ Java Decompiler sẽ thu được kết quả như sau:

12



Dưới đây là mã nguồn của method convertSolar2Lunar sau khi dịch ngược từ mã
bytecode của ứng dụng.
public static int[] convertSolar2Lunar(int dd, int mm, int yy, double timeZone)
{
int dayNumber = jdFromDate(dd, mm, yy);
int k = INT((dayNumber - 2415021.0769986948D) / 29.530588853000001D);
int monthStart = getNewMoonDay(k + 1, timeZone);
if (monthStart > dayNumber) {
monthStart = getNewMoonDay(k, timeZone);
}
int a11 = getLunarMonth11(yy, timeZone);
int b11 = a11;
int lunarYear;
if (a11 >= monthStart) {
int lunarYear = yy;
a11 = getLunarMonth11(yy - 1, timeZone);
} else {
lunarYear = yy + 1;
b11 = getLunarMonth11(yy + 1, timeZone);
}
int lunarDay = dayNumber - monthStart + 1;
int diff = INT((monthStart - a11) / 29);
int lunarLeap = 0;
int lunarMonth = diff + 11;
if (b11 - a11 > 365) {
int leapMonthDiff = getLeapMonthOffset(a11, timeZone);
if (diff >= leapMonthDiff) {
lunarMonth = diff + 10;
if (diff == leapMonthDiff) {
lunarLeap = 1;

}
13


}
}
if (lunarMonth > 12) {
lunarMonth -= 12;
}
if ((lunarMonth >= 11) && (diff < 4)) {
lunarYear--;
}
return new int[] { lunarDay, lunarMonth, lunarYear, lunarLeap };
}
Như chúng ta thấy, mã nguồn thu được sau khi dịch ngược gần như hoàn toàn
tương ứng với mã nguồn ban đầu. Thứ tự các biến, các phương thức có thể có chút
thay đổi so với mã nguồn gốc, nhưng thứ tự logic hoàn toàn tương ứng. Với công cụ
dịch ngược JAD ở trên chúng ta hoàn toàn có thể dịch ngược cả ứng dụng với các
package lồng nhau phức tạp cũng như những file class riêng biệt, từ đó dễ dàng thu
được mã nguồn gốc.
Vấn đề bảo vệ mã nguồn chống việc lấy cắp mã nguồn hay thay đổi mã nguồn là
điều rất có ý nghĩa. Việc dịch ngược đối với chương trình Java là không thể ngăn
cản, tuy nhiên chúng ta có thể sử dụng kỹ thuật rối mã để sau khi dịch ngược cũng
không thể có được mã nguồn gốc. Chương 3 sẽ trình bày chi tiết về kỹ thuật rối mã
(Obfuscation techniques).

14


CHƯƠNG 3: NGHIÊN CỨU KỸ THUẬT

BẢO VỆ MÃ NGUỒN ỨNG DỤNG JAVA
1. Sử dụng kiến trúc hướng dịch vụ
Trong phát triển phần mềm ứng dụng hay các website, mỗi sản phẩm thường
chứa đựng rất nhiều thuật toán quan trọng tạo nên tính nổi trội của sản phẩm, mang
lại tính cạnh tranh cao so với các sản phẩm tương tự. Chính vì vậy việc bảo vệ mã
nguồn chống sự đánh cắp trở nên hết sức cấp thiết đối với mỗi công ty sản xuất
phần mềm.
Mỗi ứng dụng Java sẽ được biên dịch sang mã bytecode (được chứa trong các
file .class) trước khi chúng được thông dịch sang mã máy để thực thi. Như đã đề
cập trong chương 2, ứng dụng java ở dạng mã bytecode rất dễ dàng để dịch ngược
để thu được mã nguồn. Chính vì vậy, trong chương này chúng tôi sẽ trình bày một
số kỹ thuật để bảo vệ mã nguồn ứng dụng Java. Một phương pháp khá đơn giản
nhưng mang lại hiệu quả cao, đó là triển khai các thuật toán dưới dạng các dịch vụ
(services). Khi đó các thuật toán cũng như các thành phần quan trọng sẽ được triển
khai tập trung trên server do chúng ta quản lý và sẽ cung cấp dưới dạng dịch vụ mà
các ứng dụng của khách hàng (trên máy client) yêu cầu.

Request
Calculate
service

SalaryCal
App
Client

Response

Server
Server


15


Với mô hình trên, các nhà phát triển phần mềm chỉ bàn giao cho khách hàng
chương trình ứng dụng chạy phía client mà trong ứng dụng đó không chứa những
thành phần quan trọng, các thuật toán mấu chốt vì chúng được triển khai trên máy
server mà chúng ta quản lý. Giải pháp này hoàn toàn che giấu được mã nguồn quan
trọng, tránh được nguy cơ đánh cắp mã nguồn. Tuy nhiên, với giải pháp này ứng
dụng cần có môi trường internet để sử dụng được dịch vụ (service) mà server cung
cấp.
Trong mô hình triển khai ở trên chúng tôi trình bày hai kỹ thuật trong java là sử
dụng webservice – một thể hiện của service sử dụng các chuẩn giao thức web để
giao tiếp và rmi (remote method invocation) – thủ tục gọi phương thức đầu xa thông
qua socket TCP.

1.1 Triển khai qua web service
1.1.1. Kiến trúc web service
Mô hình kiến trúc của web service gồm 3 thành phần: web service provider, web
service broker và web service requester.

 Service provider: Sử dụng Web Services Description Language (WSDL) để
mô tả dịch vụ mà mình có thể cung cấp cho Service Broker.
 Service broker: Lưu trữ thông tin về các service được cung cấp bởi các
Service Provider. Cung cấp chức năng tìm kiếm hỗ trợ Service Requester
trong việc xác định Service Provider phù hợp. Thành phần chính của Service
Broker là Universal Discovery, Description, and Integration (UDDI).
16


 Service Requester: Dùng WSDL để đặc tả nhu cầu sử dụng (loại service, thời

gian sử dụng, resource cần thiết, mức giá ...) và gửi cho Service Broker. Bằng
việc sử dụng UDDI và chức năng tìm kiếm của Service Broker, Service
Requester có thể tìm thấy Service Provider thích hợp. Ngay sau đó, giữa
Service Requester và Service Provider thiết lập kênh giao tiếp sử dụng SOAP
để trao đổi thông tin trong việc sử dụng service.
Khi muốn sử dụng một web service nào đó, application client sẽ truy vấn đến
UDDI để xác định web service cần tìm dựa vào thông tin nào đó như tên, loại
service. Khi đã xác định được web service cần cho ứng dụng, client lấy thông tin về
địa chỉ WSDL của web service này dựa trên UDDI. Tài liệu WSDL này sẽ mô tả
cách thức liên lạc với web service, định dạng gói truy vấn và phản hồi. Dựa vào
những thông tin này client sẽ tạo những gói tin SOAP để liên lạc với service
provider.
Trong mô hình hoạt động của web service, các thành phần trao đổi với nhau dựa
trên các giao thức của web service là SOAP và WSDL mà bản chất của chúng là các
gói tin định dạng XML.
1.1.2. Web Service Description Language – WSDL
WSDL được sử dụng để mô tả chức năng, interface giao tiếp của một web
service theo cú pháp tổng quát của XML. Việc đặc tả này bao gồm các thông tin
như: tên dịch vụ, giao thức sử dụng khi gọi các hàm của dịch vụ web, loại thông tin
(tham số, kiểu dữ liệu, giao diện truy xuất dịch vụ web, …). Hình dưới mô tả chi
tiết các thành phần được mô tả trong WSDL

17


 Services: Chứa các method có thể được sử dụng thông qua các web protocol.
 Ports: Địa chỉ dùng để kết nối đến web service. Thông thường, ports được mô
tả bằng một HTTP URL.
 Port Types: định nghĩa một web service, các tác vụ mà service cung cấp và
định dạng các thông điệp được sử dụng để khởi động các tác vụ này.

 Operations: Mỗi operation có thể được xem như một method hay một lời gọi
hàm trong các ngôn ngữ lập trình cổ điển.
 Binding: chỉ định port type, các operation, SOAP binding stype
(RPC/Document), SOAP protocol được dùng.
 Message: Mỗi message tương ứng với một operation và chứa các thông tin cần
thiết để thực thi operation đó. Mỗi message có một name duy nhất và một hay
nhiều logical part. Các logical part được phân biệt với nhau qua name và có
thể lưu trữ các tham số cần cho operation.
 Element: Được định nghĩa trong Types. Mỗi element có một name duy nhất
và kiểu dữ liệu. Element được dùng để đặc tả dữ liệu dùng trong message.
Element có thể đặc tả các dữ liệu đơn giản (string, integer) hay phức tạp hơn
như array, struct, ...
 XSD file: Các element thường được định nghĩa trong các XML Schema
Definition (XSD) file. XSD file có thể ở trong cùng file WSDL hoặc ở file
riêng biệt.
WSDL thường được dùng kết hợp với XML schema và SOAP để cung cấp dịch
vụ web qua internet. Một client khi kết nối đến web service có thể đọc WSDL để
xác định các chức năng được cung cấp trong web service đó và từ đó dựa vào
SOAP lấy ra chính xác chức năng cần thiết cung cấp bởi web service đó.
1.1.3. Simple Object Access Protocol – SOAP
SOAP là một protocol giao tiếp dùng trong Web service được xây dựng dựa trên
XML. SOAP được sử dụng để đặc tả và trao đổi thông tin về các cấu trúc dữ liệu
cũng như các kiểu dữ liệu giữa các thành phần trong hệ thống.
Sử dụng SOAP, ứng dụng có thể yêu cầu thực thi method trên máy tính ở xa mà
không cần quan tâm đến chi tiết về platform cũng như các phần mềm trên máy tính
đó.
18


Hình vẽ dưới đây mô tả giao tiếp của một ứnng dụng với một web service được

thực hiện qua thông điệp SOAP sử dụng network protocol HTTP. Ứng dụng sẽ đặc
tả yêu cầu trong SOAP message và thông qua network protocol gởi đến cho web
service. Web service sẽ nhận và phân tích yêu cầu sau đó trả về kết quả thích hợp.

Hình vẽ dưới mô tả cấu trúc của một thông điệp SOAP. Một thông điệp SOAP bao
gồm các thành phần sau:

 Protocol Header: Cho biết thông tin về các chuẩn giao thức được sử dụng
 SOAP Envelop: Thông tin chính của message bao gồm:
o SOAP Header
o SOAP body: Thông tin về name và data được đặc tả dưới
dạng XML. Ngoài ra còn có trường lỗi được dùng để gửi các
web service exception.

19


Cấu trúc của toàn bộ SOAP message
Chứa các giao thức chuẩn (HTTP,
SMTP) và SOAP header
SOAP message
SOAP header

Chứa tên, dữ liệu của service dưới định
dạng XML
Thông tin lỗi nếu xảy ra

Dưới đây là chương trình demo việc triển khai một ứng dụng với web service.
Chương trình ứng dụng phía client là một form nhập thông tin cần thiết để thực hiện
việc tính lương nhân viên trong tháng. Sau khi nhập đầy đủ thông tin, người dùng

click button Get Salary thì phía client sẽ tạo một SOAP request message với định
dạng được chỉ ra như ở dưới.

20


Gói tin SOAP request đóng gói các thông tin về số ngày làm việc thực tế, hệ số
lương, lương cơ bản, phụ cấp, số ngày phải làm và phụ cấp. Gói tin SOAP được gửi
đến server để web service thực hiện phân tích gói tin lấy các tham số đầu vào cung
cấp cho phương thức Get_Salary được cài đặt trên máy server, thực hiện tính toán
trả về giá trị lương thực lĩnh. Thông tin kết quả sẽ được đóng gói trong gói tin
SOAP response theo định dạng bên dưới để gửi về cho client phân tích và hiển thị
kết quả.
SOAP Request
<?xml version="1.0" encoding="UTF-8" standalone="no" ?>
SOAP-ENV:encodingStyle=" />xmlns:SOAP-ENV=" />xmlns:SOAP-ENC= />xmlns:xsi=" />xmlns:xsd=" /><SOAP-ENV:Body>
<GetSalary xmlns="t/">
<workDay xmlns="">22</workDay>
<level xmlns="">2.34</level>
<basicSalary xmlns="">1150</basicSalary>
<addition xmlns="">0.3</addition>
<dayOfMonth xmlns="">24</dayOfMonth>
</GetSalary>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
SOAP Response
<?xml version="1.0" encoding="UTF-8" ?>
xmlns:SOAP-ENV= />xmlns:xsi= />xmlns:xsd=" /><SOAP-ENV:Body>

21


<ns2:GetSalaryResponse xmlns:ns2="t/">
<return>3206.7751</return>
</ns2:GetSalaryResponse>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>

1.2 Triển khai với RMI (Remote Method Invocation)
Trong phần trên chúng tôi đã trình bày kỹ thuật sử dụng web service để ẩn giấu
mã nguồn các thuật toán cũng như các thành phần quan trọng và chỉ chia sẻ chúng
dưới dạng dịch vụ (service). Trong phần này chúng tôi giới thiệu kỹ thuật gọi
phương thức đầu xa – RMI thay vì sử dụng web service.
RMI là một cơ chế cho phép một đối tượng đang chạy trên một máy ảo Java này
( Java Virtual Machine) gọi các phương thức của một đối tượng đang tồn tại trên
một máy ảo Java khác (JVM).
1.2.1. Kiến trúc RMI
Kiến trúc của chương trình RMI được mô tả qua hình vẽ sau:

Chương trình Server

Đường logic

Chương trình Client

Skeleton

Stub


Tầng tham chiếu từ xa

Tầng tham chiếu từ xa

Tầng giao vận

Network

Tầng giao vận

Trong đó:
 Server: là chương trình cung cấp các đối tượng có thể gọi từ xa
 Client: là chương trình có tham chiếu đến các phương thức của đối tượng đầu
xa trên server.
 Stub: là một đối tượng được ủy quyền, truyền tải yêu cầu đối tượng từ client
tới server RMI. Mỗi dịch vụ RMI được định nghĩa như là một giao tiếp, chứ
không phải là một chương trình cài đặt, các ứng dụng client giống như các
chương trình hướng đối tượng khác. Tuy nhiên ngoài việc thực hiện công việc
22


của chính nó, stub còn truyền một thông điệp tới một dịch vụ RMI ở xa, chờ
đáp ứng, và trả về đáp ứng cho phương thức gọi.
 Skeleton: lắng nghe các yêu cầu RMI đến và truyền các yêu cầu này tới dịch
vụ RMI. Đối tượng skeleton không cung cấp bản cài đặt của dịch vụ RMI. Nó
chỉ đóng vai trò như là chương trình nhận các yêu cầu, và truyền các yêu cầu.
Sau khi tạo ra một giao tiếp RMI, thì người phát triển phải cung cấp một phiên
bản cài đặt cụ thể của giao tiếp đó. Khi có yêu cầu đến, skeleton nhận diện và
kích hoạt phương thức tương ứng trên server RMI.
 Tầng tham chiếu từ xa: là hệ thống hỗ trợ truyền thông của RMI

 Tầng giao vận: truyền thông giữa các máy tính thông qua giao thức mạng
1.2.2. Cơ chế hoạt động của RMI
Các hệ thống RMI phục vụ cho việc truyền tin thường được chia thành hai loại:
client và server. Một server cung cấp dịch vụ RMI, và client gọi các phương thức
trên đối tượng của dịch vụ này.
Server RMI phải đăng ký với một dịch vụ tra tìm và đăng ký tên. Dịch vụ này
cho phép các client có thể truy tìm chúng. Một chương trình đóng vai trò như vậy
có tên là rmiregistry, chương trình này chạy như một tiến trình độc lập và cho phép
các ứng dụng đăng ký dịch vụ RMI hoặc nhận một tham chiếu tới dịch vụ được đặt
tên. Mỗi khi server được đăng ký, nó sẽ chờ các yêu cầu RMI từ client. Gắn với mỗi
đăng ký dịch vụ là một tên được biểu diễn bằng một xâu ký tự để cho phép các
client lựa chọn dịch vụ thích hợp.
Các client RMI sẽ gửi các thông điệp RMI để gọi một phương thức trên một đối
tượng từ xa. Trước khi thực hiện gọi phương thức từ xa, client phải nhận được một
tham chiếu từ xa. Tham chiếu này thường có được bằng cách tra tìm một dịch vụ
trong trình đăng ký RMI. Ứng dụng client yêu cầu một tên dịch vụ cụ thể, và nhận
một URL trỏ tới tài nguyên từ xa. Khuôn dạng dưới đây được sử dụng để biểu diễn
một tham chiếu đối tượng từ xa:
rmi://hostname:port/servicename
Trong đó hostname là tên của máy chủ hoặc một địa chỉ IP, port xác định cổng
dịch vụ, và servicename là một xâu ký tự mô tả dịch vụ.

23


JVM

Skeleton

Stub


Client
Object

JVM

Client

Remote
Object

Server

Khi có được tham chiếu tới đối tượng đầu xa, client có thể tương tác với dịch vụ
đó nhờ sự trợ giúp của stub (bên phía máy client) và skeleton (bên phía máy server).
Việc truyền tin giữa stub và skeleton được thực hiện thông qua Socket TCP. Mỗi
khi được tạo ra, skeleton lắng nghe các yêu cầu đến được phát ra bởi các đối tượng
stub. Các tham số trong hệ thống RMI không chỉ hạn chế đối với các kiểu dữ liệu
nguyên tố, bất kỳ đối tượng nào có khả năng tuần tự hóa đều có thể được truyền
như một tham số hoặc được trả về từ phương thức từ xa. Khi một stub truyền một
yêu cầu tới một đối tượng skeleton, nó phải đóng gói các tham số (hoặc là các kiểu
dữ liệu nguyên tố, các đối tượng hoặc cả hai) để truyền đi, quá trình này được gọi là
marshalling. Sau khi marshalling, dữ liệu sẽ được stub chuyển cho tầng tham chiếu
đầu xa để chuyển tiếp cho tầng giao vận và truyền đi thông qua môi trường mạng
máy tính đến phía tầng giao vận của Server. Tại đây, dữ liệu được truyền ngược đến
cho đối tượng skeleton. Tại đó, các tham số được khôi phục lại để tạo nên các kiểu
dữ liệu nguyên tố và các đối tượng, quá trình này còn được gọi là unmarshaling.
Skeleton đọc các tham số, truyền và gọi phương thức thực sự ở Server. Sau khi tính
toán, thực thi phương thức, dữ liệu được gửi ngược lại cho client theo quá trình
tương tự.

Ví dụ dưới đây là một ứng dụng tính lương nhân viên theo tháng. Trong đó,
chương trình phía client là một form cho phép người dùng nhập vào các thông tin
cần thiết như: số ngày thực làm, lương cơ bản, phụ cấp, hệ số lương và số ngày phải
làm việc mỗi tháng. Khi gọi phương thức Get_Salary trên server, thông qua các đối
tượng stub và skeleton, phương thức đầu xa phía server sẽ được gọi và trả kết quả
về cho client để hiện thị lương thực lĩnh của nhân viên trong tháng đó.

24


Như vậy để che giấu mã nguồn của các thuật toán hay các thành phần quan trọng
trong một chương trình Java chúng ta có thể cài đặt chúng như các dịch vụ (web
service hay rmi server) được triển khai trên máy server và chia sẻ dịch vụ đó cho
các ứng dụng phía người dùng. Tuy nhiên, giải pháp này đòi hỏi ứng dụng chỉ chạy
được trên môi trường mạng máy tính. Trong thực tế phát triển phần mềm, có nhiều
ứng dụng đòi hỏi chạy độc lập trên máy của người dùng. Khi đó chúng tôi sử dụng
một giải pháp thứ hai nhằm bảo vệ mã nguồn quan trọng của ứng dụng – kỹ thuật
xáo trộn mã nguồn chương trình (Java Obfuscation).
25


×