Các ứng dụng trong ngân hàng thường phải xử lý các thông tin nhạy cảm như số chứng minh thư, số thẻ tín dụng hay mật khẩu. Những thông tin đó thường được lưu trong các chuỗi (string). Nếu người khác (ứng dụng khác) có thể đọc được bộ nhớ thì những thông tin nhạy cảm đó sẽ bị lộ. Hơn thế nữa, lớp String trong .NET không thể bị sửa đổi. Điều đó có nghĩa là mỗi khi thay đổi giá trị của một chuỗi, chúng ta đã tạo ra những bản sao của nó. Với đoạn mã sau:
Các ứng dụng trong ngân hàng thường phải xử lý các thông tin nhạy cảm như số chứng minh thư, số thẻ tín dụng hay mật khẩu. Những thông tin đó thường được lưu trong các chuỗi (string). Nếu người khác (ứng dụng khác) có thể đọc được bộ nhớ thì những thông tin nhạy cảm đó sẽ bị lộ. Hơn thế nữa, lớp String trong .NET không thể bị sửa đổi. Điều đó có nghĩa là mỗi khi thay đổi giá trị của một chuỗi, chúng ta đã tạo ra những bản sao của nó. Với đoạn mã sau:
string password = “”;
while (condition)
{
char someChar = ...; //get from Console.ReadKey() for example
password += someChar;
}
chương trình sẽ tạo ra rất nhiều chuỗi khác nhau khi người dùng nhập: “H”, “He”, “Hel”, “Hell”, “Hello”, ... Lập trình viên không thể chủ động dọn đống rác đó nên không thể kiểm soát được thông tin.
Để khắc phục vấn đề đó, .NET 2.0 giới thiệu lớp System.Security.SecureString với các tính năng sau:
- Giá trị của chuỗi được mã hóa bằng DPAPI;
- Sử dụng giao diện IDisposable để cho phép lập trình viên quyết định việc dọn rác (bằng cách gọi phương thức Dispose);
- Không thể dễ dàng chuyển sang String để tránh làm lộ thông tin.
Giá trị của một đối tượng SecureString được tự động mã hóa khi một instance của nó được khởi tạo hoặc khi giá trị bị thay đổi. Có thể gọi phương thức MakeReadOnly để ngăn chặn các sửa đổi trên giá trị của một đối tượng SecureString nếu cần.
Từ phiên bản 3.0 SP2, .NET giới thiệu thêm control PasswordBox với thuộc tính SecurePassword để giúp lập trình viên nhận mật khẩu ngay ở dạng SecureString. Nếu sử dụng các phiên bản cũ hơn, lập trình viên cũng có thể sử dụng các hộp thoại nhập mật khẩu dạng SecureString được phát triển và cung cấp miễn phí trên Internet.
Dưới đây là một đoạn mã ví dụ về cách sử dụng SecureString trong ứng dụng console:
/// <summary>
/// Read a password from the console into a SecureString
/// </summary>
/// <returns>Password stored in a secure string</returns>
public static SecureString GetPassword()
{
SecureString password = new SecureString();
// get the first character of the password
ConsoleKeyInfo nextKey = Console.ReadKey(true);
while(nextKey.Key != ConsoleKey.Enter)
{
if(nextKey.Key == ConsoleKey.BackSpace)
{
if(password.Length > 0)
{
password.RemoveAt(password.Length - 1);
// erase the last * as well
Console.Write(nextKey.KeyChar);
Console.Write(“ “);
Console.Write(nextKey.KeyChar);
}
}
else
{
password.AppendChar(nextKey.KeyChar);
Console.Write(“*”);
}
nextKey = Console.ReadKey(true);
}
Console.WriteLine();
// lock the password down
password.MakeReadOnly();
return password
}
Sau khi đọc mật khẩu, ứng dụng có thể dùng nó để khởi tạo một tiến trình mới với một tài khoản khác:
Console.Write(“Username: “);
string user = Console.ReadLine();
string[ ] userParts = user.Split(‘\\’);
Console.Write(“Password: “);
SecureString password = GetPassword();
try
{
ProcessStartInfo psi = new ProcessStartInfo(args[0]);
psi.UseShellExecute = false;
if(userParts.Length == 2)
{
psi.Domain = userParts[0];
psi.UserName = userParts[1];
}
else
{
psi.UserName = userParts[0];
}
psi.Password = password;
Process.Start(psi);
}
catch(Win32Exception e)
{
Console.WriteLine(“Error starting application”);
Console.WriteLine(e.Message);
}
Do có rất ít hàm chấp nhận kiểu SecureString (chỉ có 3 lớp: Process, CspParameters và X509Certificate hiểu SecureString) nên việc dùng nó khá phức tạp: lập trình viên cần dùng phương thức SecureStringToBSTR của lớp Marshal chuyển SecureString thành BSTR rồi sau khi sử dụng thì gọi phương thức ZeroFreeBSTR để xóa sạch con trỏ và dữ liệu mà nó trỏ tới.
SecureString ss = new SecureString();
/* ... */
IntPtr bstr = Marshal.SecureStringToBSTR( ss );
try
{
// use the bstr
}
finally
{
if (bstr != IntPtr.Zero)
Marshal.ZeroFreeBSTR( bstr );
}
Nếu cẩn thận hơn, chúng ta có thể dùng GCHandle để ngăn bộ dọn rác (garbage collector) chép chuỗi giải mã sang chỗ khác và vận dụng cơ chế Constrained Execution Regions để đảm bảo xóa dữ liệu luôn được thực hiện. Tuy nhiên, điều đó nằm ngoài phạm vi bài viết này, đề nghị bạn đọc nào quan tâm tự tìm hiểu thêm.
Sau khi .NET giới thiệu SecureString, rất nhiều lập trình viên Java tìm cách phát triển một thứ tương tự. Tuy nhiên, do Java không gắn chặt với hệ điều hành như .NET nên không thể có một lớp tương tự (bảo mật bằng mật khẩu của người dùng đăng nhập vào hệ điều hành) hay tương thích với SecureString.
Đến 2008 - 2009, Java cũng cho ra đời lớp GuardedString với tính năng mã hóa và cho phép lập trình viên chủ động xóa dữ liệu. Bạn đọc nào quan tâm có thể tham khảo tại địa chỉ: http://docs.oracle.com/cd/E23943_01/apirefs.1111/e24834/org/identityconnectors/common/security/GuardedString.html
Mã nguồn của lớp GuardedString (miễn phí theo Common Development and Distribution License) có thể xem tại địa chỉ sau: http://codenav.org/code.html?project=/org/connid/bundles/org.connid.bundles.openam/0.2&path=/Source%20Packages/org.identityconnectors.common.security/GuardedString.java.
(Cnth theo Tạp chí thnh)