.NET Cryptography & Mã hóa dữ liệu

Ngày nay, mã hóa dữ liệu đã trở nên một nhu cầu phổ biến. Trong rất nhiều tình huống (lưu trữ password, gửi thông tin qua mạng…) chúng ta sẽ cần đến mã hóa để bảo vệ thông tin nhạy cảm khỏi những cặp mắt tò mò. Có lẽ chính Microsoft cũng nhận thức được vấn đề này nên trong .NET Framework có hẳn một namespace System.Security.Cryptography phục vụ cho việc mã hóa dữ liệu. Nhưng trước tiên, chúng ta hãy cùng xem qua một vài khái niệm mã hóa cơ bản:

Public-key encryption:

Còn gọi là mã hóa bất đối xứng (asymmetric encryption), sử dụng một cặp khóa để mã hóa/giải mã, trong đó khóa public được công khai cho mọi người và khóa private được giữ bí mật. Thông thường, khóa public sẽ được dùng để mã hóa, và chỉ những ai có khóa private mới có thể giải mã. Tuy nhiên cũng có những trường hợp ngược lại: mã hóa bằng khóa private và giải mã bằng khóa public. Trường hợp này được ứng dụng trong chữ ký điện tử, giúp chứng thực người gửi và đảm bảo thông tin đã không bị thay đổi trong quá trình truyền đi (nếu thông tin bị thay đổi sau khi đã mã hóa bằng khóa private thì sẽ không thể giải mã bằng khóa public được nữa). Các giải thuật mã hóa bất đối xứng thông dụng gồm:

  • RSA
  • DSA (Digital Signature Algorithm)

Private-key encryption:

Còn gọi là mã hóa đối xứng, chỉ sử dụng một khóa duy nhất để mã hóa và giải mã thông tin. Kiểu mã hóa này còn có một cái tên khác là mã hóa khối (block cipher) vì dữ liệu thường được mã hóa theo từng khối. Để tăng tính an toàn, .NET sử dụng một kĩ thuật gọi là chaining, trong đó thông tin từ các khối trước được sử dụng để mã hóa các khối tiếp theo, khiến cho khóa mã không thể bị suy ra ngay cả khi biết được cấu trúc dữ liệu. Các giải thuật mã hóa đối xứng thông dụng gồm:

  • RC2
  • DES, Tripple DES
  • Rijndael

Các giải thuật băm:

Các giải thuật băm phổ biến gồm MD5 và SHA1, đây là các giải thuật mã hóa một chiều. Dữ liệu sau khi mã hóa luôn có kích thước cố định và không thể khôi phục lại được nữa. Các giải thuật này cũng đảm bảo sẽ cho ra những kết quả khác nhau nếu dữ liệu đầu vào khác nhau và ngược lại.

Lựa chọn giải thuật nào cho phù hợp?

Nhìn chung, các giải thuật mã hóa đối xứng cho tốc độ rất nhanh và phù hợp khi cần mã hóa một lượng lớn dữ liệu. Tuy nhiên, khóa mã hóa là một chuỗi kí tự với chiều dài cố định và có thể bị bẻ khóa nếu có đủ thời gian (bằng các phương pháp đơn giản như vét cạn). Ngược lại, các giải thuật mã hóa bất đối xứng chậm hơn nhưng tương đối an toàn hơn (vì cần đến cả hai khóa public và private). Một kịch bản phổ biến trong trường hợp cần mã hóa thông tin khối lượng lớn là mã hóa thông tin này bằng một giải thuật đối xứng, và sử dụng giải thuật bất đối xứng để mã hóa khóa của giải thuật đối xứng đó (khi mã hóa đối xứng, khóa mã cần được gửi đi kèm với thông tin đã mã hóa để bên nhận có thể giải mã. Việc mã hóa khóa này giúp đảm bảo an toàn. Khi đó bên nhận sẽ sử dụng private key của mình để giải mã khóa mã, rồi dùng khóa mã này giải mã thông tin cần nhận)

Các giải thuật băm thường áp dụng cho các khối dữ liệu nhỏ như password. Vì là mã hóa một chiều, thông tin sẽ không thể đọc được đối với cả người quản trị hệ thống hay trong trường hợp cơ sở dữ liệu bị chiếm dụng, giúp tăng độ an toàn. Trong trường hợp password, chỉ có phiên bản đã băm của password được lưu trữ. Khi user login, chuỗi password do user nhập vào sẽ được băm lại một lần nữa bằng cùng một giải thuật băm, sau đó hai chuỗi băm được so sánh để xác định kết quả.

Hiện thực:

Đoạn code sau minh họa cách mã hóa một chuỗi kí tự trong C# sử dụng giải thuật mã hóa đối xứng Rijndael:

public string Encode(string source)
{
byte[] binarySource = Encoding.UTF8.GetBytes(source);
System.Security.Cryptography.SymmetricAlgorithm rijn = System.Security.Cryptography.SymmetricAlgorithm.Create();
MemoryStream ms = new MemoryStream();
byte[] rgbIV = Encoding.ASCII.GetBytes("initialization vector");
byte[] key = Encoding.ASCII.GetBytes("key to encrypt/decrypt data");
CryptoStream cs = new CryptoStream(ms, rijn.CreateEncryptor(key, rgbIV), CryptoStreamMode.Write);
cs.Write(binarySource, 0, binarySource.Length);
cs.Close();
return Convert.ToBase64String(ms.ToArray());
}

Đoạn code trên sử dụng phương thức Create() của lớp SymmetricAlgorithm để tạo một đối tượng cryptographic dùng cho mã hóa. SymmetricAlgorithm là lớp abstract và là lớp cha của tất cả các giải thuật mã hóa đối xứng trong .NET Framework. SymmetricAlgorithm không thể được khởi tạo trực tiếp (vì là abstract) nhưng cung cấp phương thức static Create(). Phương thức này dùng để khởi tạo đối tượng của một lớp giải thuật kế thừa từ SymmetricAlgorithm và có hai phiên bản: một phiên bản không nhận tham số (mặc định khởi tạo một đối tượng giải thuật Rijndael) và một phiên bản có nhận tham số cho phép xác định tên giải thuật cần khởi tạo đối tượng. Như đã đề cập, Rijndael sử dụng kĩ thuật chaining để bảo vệ khóa mã. Kĩ thuật này yêu cầu cung cấp một vector khởi tạo. Vector đó được thể hiện bởi biến rgbIV. Key là khóa để mã hóa dữ liệu. Dữ liệu sau khi mã hóa lại được trả về ở dạng chuỗi (đương nhiên là không thể đọc được nữa!). Cuối cùng, phương thức Decode() cho phép khôi phục dữ liệu về trạng thái ban đầu (lưu ý là hai tham số rgbIV và key của hai phương thức phải giống nhau):

private string Decode(string source)
{
byte[] binarySource = Convert.FromBase64String(source);
MemoryStream ms = new MemoryStream();
System.Security.Cryptography.SymmetricAlgorithm rijn = System.Security.Cryptography.SymmetricAlgorithm.Create();
byte[] rgbIV = Encoding.ASCII.GetBytes("initialization vector");
byte[] key = Encoding.ASCII.GetBytes("key to encrypt/decrypt data");
CryptoStream cs = new CryptoStream(ms, rijn.CreateDecryptor(key, rgbIV),
CryptoStreamMode.Write);
cs.Write(binarySource, 0, binarySource.Length);
cs.Close();
return Encoding.UTF8.GetString(ms.ToArray());
}

, ,

  1. #1 by nam on Tháng Mười 26, 2011 - 2:29 chiều

    anh có thể demo cho em thay ro hon duoc khong anh !
    em xin cam on!
    Thân .

  2. #2 by nam on Tháng Mười 26, 2011 - 2:34 chiều

    khóa để mã hóa dữ liệu là sao vậy Anh ?
    Cái chỗ ” byte[] rgbIV = Encoding.ASCII.GetBytes(“initialization vector”);
    byte[] key = Encoding.ASCII.GetBytes(“key to encrypt/decrypt data”); ” em ko hiểu rõ cho lắm
    Anh hãy giải đáp giúp em !
    Thân !

    • #3 by worm.NET on Tháng Mười 31, 2011 - 12:50 sáng

      Chào bạn, rất tiếc là mình không thể trả lời bạn sớm hơn vì tuần qua khá bận rộn. Bạn có thể download một bản demo đơn giản tại đây (file Demo.zip):
      https://skydrive.live.com/?cid=a951d7adf353803e&sc=documents&uc=1&id=A951D7ADF353803E%21111#
      Trong bài viết mình đã có giải thích sơ về hai khái niệm initialization vector và key. Cơ bản là giá trị hai tham số này trong hàm giải mã phải giống trong hàm mã hóa mới có thể giải mã dữ liệu. Giá trị cụ thể tùy thuộc vào từng giải thuật mã hóa (DES, Rijndael…). Thông thường (như trong ví dụ trong bài viết) thì initialization vector phải có chiều dài 16 kí tự và key là 8 kí tự.
      Thân.

  3. #4 by Hải on Tháng Hai 18, 2012 - 4:48 chiều

    Việc sử dụng các class sẵn có liệu có dẫn tới nguy cơ bị lộ Private Key bằng cách làm giả các class đó không?

    • #5 by worm.NET on Tháng Hai 21, 2012 - 12:13 sáng

      Chào Hải, các class được đề cập trong bài viết hoàn toàn không chứa thông tin về Private Key, chúng chỉ sử dụng Private Key như một tham số để mã hóa dữ liệu. Do đó không tồn tại nguy cơ mà bạn nói.

  4. #6 by Hoàng Quốc Long on Tháng Hai 22, 2012 - 3:04 chiều

    Mình đang tìm hiểu mấy cái này :
    Enterprise Library 5.0 (Cryptography Application Block, Policy Injection Application
    Block, Security Application Block, Validation Application Block).

    có ai bít share mình với( ai có bản Demo gửi cho mình với)

Gửi phản hồi

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Thay đổi )

Twitter picture

You are commenting using your Twitter account. Log Out / Thay đổi )

Facebook photo

You are commenting using your Facebook account. Log Out / Thay đổi )

Connecting to %s

Follow

Get every new post delivered to your Inbox.