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 (7.15 MB, 706 trang )
569
Chương 14: Mật mã
Encrypt và khóa công khai (public key) của người nhận để mật hóa thông điệp.
Sau đo, người nhận sẽ sử dụng phương thức RSACryptoServiceProvider.Decrypt
và khóa riêng (private key) để giải mật hóa bí mật đã-được-mật-hóa.
.NET Framework định nghĩa một hệ thống phân cấp theo lớp cho các giải thuật bất đối xứng
tương tự như đã định nghĩa cho các giải thuật đối xứng (đã được thảo luận trong mục 14.6).
Tất cả các giải thuật bất đối xứng phải thừa kế một lớp cơ sở trừu tượng chung có tên là
System.Security.Cryptography.AsymmetricAlgorithm. Có hai hiện thực giải thuật bất đối
xứng cụ thể:
•
•
System.Security.Cryptography.RSACryptoServiceProvider
System.Security.Cryptography.DSACryptoServiceProvider
Vì có đuôi là CryptoServiceProvider nên cả hai lớp này đều bọc lấy các chức năng do Win32
CryptoAPI cung cấp. Tuy nhiên, chỉ có lớp RSACryptoServiceProvider là hỗ trợ việc mật hóa
dữ liệu. Lớp DSACryptoServiceProvider hiện thực Digital Signature Algorithm (DSA), bạn có
thể sử dụng giải thuật này chỉ để tạo chữ ký số (xem Federal Information Processing
Standard [FIPS] 186-2 tại [http://www.itl.nist.gov/fipspubs] để biết thêm chi tiết về DSA).
Mặc dù bạn có thể tạo một đối tượng giải thuật bất đối xứng bằng phương thức tĩnh Create
của lớp cơ sở AsymmetricAlgorithm, nhưng bù lại bạn sẽ phải trả giá chút ít cho việc này. Lớp
AsymmetricAlgorithm không khai báo các phương thức mà RSACryptoServiceProvider sử
dụng để mật hóa và giải mật hóa dữ liệu. Thay vào đó, bạn phải trực tiếp thể hiện hóa lớp
RSACryptoServiceProvider bằng một trong các phương thức khởi dựng của nó.
Trước khi mật hóa hay giải mật hóa dữ liệu với đối tượng RSACryptoServiceProvider, bạn cần
truy xuất các khóa thích hợp. Khóa của giải thuật bất đối xứng khác nhiều so với khóa của giải
thuật đối xứng. Thứ nhất, nó có hai thành phần: khóa công khai (public key) và khóa riêng
(private key). Thứ hai, thay vì chỉ là một dãy các byte được sinh ngẫu nhiên, khóa bất đối
xứng được tạo theo một cách thức đặc biệt. Có một mối quan hệ toán đặc biệt giữa khóa công
khai và khóa riêng; mối quan hệ này cho phép giải thuật bất đối xứng mật hóa dữ liệu bằng
một khóa và giải mật hóa dữ liệu bằng một khóa khác. Mỗi giải thuật bất đối xứng sử dụng
cách thức tạo khóa của chính nó, và các lớp hiện thực cụ thể đóng gói các chức năng cần thiết
để tạo ra các khóa mới.
Khóa công khai không cần được giữ bí mật và chủ sở hữu có thể tùy ý gửi nó cho bạn thông
qua e-mail, hoặc post nó lên một website hay một server phân phối khóa để mọi người cùng
thấy. Những ai muốn gửi bí mật thì sử dụng khóa công khai để mật hóa bí mật. Sau đó, người
nhận sử dụng khóa riêng để giải mật hóa bí mật. Khóa riêng phải được giữ bí mật; những ai sở
hữu khóa riêng đều có thể giải mật hóa dữ liệu đã-được-mật-hóa bằng khóa công khai.
Để tạo một bí mật được-mật-hóa-bất-đối-xứng, bạn phải có khóa công khai của người nhận và
nạp nó vào một đối tượng RSACryptoServiceProvider. Có hai cách nạp khóa công khai:
•
Sử dụng phương thức RSACryptoServiceProvider.ImportParameters để nhập một cấu
trúc System.Security.Cryptography.RSAParameters, cấu trúc này chứa thông tin khóa
công khai của người nhận. Chủ sở hữu có thể tạo cấu trúc RSAParameters bằng phương
thức RSACryptoServiceProvider.ExportParameters và gửi nó cho bạn. Tuy nhiên,
người này có thể gửi cho bạn khóa công khai ở dạng byte, và bạn phải tự nạp giá trị này
vào cấu trúc RSAParameters.
570
Chương 14: Mật mã
•
Sử dụng phương thức RSACryptoServiceProvider.FromXmlString để nạp dữ liệu khóa
công khai từ một chuỗi XML. Chủ sở hữu có thể tạo dữ liệu XML này bằng phương thức
RSACryptoServiceProvider.ToXmlString và gửi nó cho bạn.
Cả
phương
thức
và
ToXmlString
của
lớp
RSACryptoServiceProvider đều nhận một đối số luận lý, nếu là true, đối tượng
RSACryptoServiceProvider sẽ xuất cả khóa công khai và khóa riêng. Bạn chỉ định
giá trị này là false khi cần xuất khóa cho mục đích phân phối hay lưu trữ.
ExportParameters
Một khi đã nạp khóa công khai của người nhận vào đối tượng RSACryptoServiceProvider,
bạn có thể mật hóa dữ liệu. Giải thuật bất đối xứng chậm hơn giải thuật đối xứng khi mật hóa
và giải mật hóa dữ liệu. Vì lý do này, bạn không nên sử dụng giải thuật bất đối xứng để mật
hóa lượng dữ liệu lớn. Thông thường, nếu cần mật hóa lượng dữ liệu lớn, bạn nên sử dụng
giải thuật đối xứng và rồi mật hóa khóa đối xứng bằng giải thuật bất đối xứng để bạn có thể
gửi khóa đối xứng cùng với dữ liệu. Mục 14.10 sẽ thảo luận về vấn đề này. Để bảo đảm tính
nhất quán trong việc sử dụng, lớp RSACryptoServiceProvider không hỗ trợ mô hình mật hóa
và giải mật hóa dựa-trên-System.IO.Stream (đã được sử dụng trong mục 14.6).
Để mật hóa dữ liệu với đối tượng RSACryptoServiceProvider, bạn hãy gọi phương thức
Encrypt, truyền cho nó một mảng byte chứa plaintext; Encrypt sẽ trả về một mảng byte chứa
ciphertext. Phương thức Encrypt cũng mhận một đối số luận lý cho biết kiểu padding mà đối
tượng RSACryptoServiceProvider sẽ sử dụng. Padding cho biết đối tượng bất đối xứng sẽ xử
lý plaintext như thế nào trước khi mật hóa. Padding bảo đảm giải thuật bất đối xứng không
cần xử lý từng khối dữ liệu, và bảo vệ ciphertext đối với các dạng tấn công bằng mật mã. Diễn
giải các dạng padding vượt quá phạm vi của quyển sách này. Nói chung, nếu đang sử dụng
Microsoft Windows XP trở về sau, bạn nên chỉ định đối số padding là true; nếu không, bạn
phải chỉ định đối số padding là là false (nếu không thì Encrypt sẽ ném ngoại lệ
System.Security.Cryptography.CryptographicException).
Giải mật hóa dữ liệu cũng đơn giản như mật hóa dữ liệu. Người nhận cần tạo một đối tượng
RSACryptoServiceProvider và nạp nó cùng với khóa riêng. Thông thường, khóa này sẽ được
lưu trữ trong một kho chứa khóa (key container) do CryptoAPI quản lý (sẽ được thảo luận kỹ
hơn trong mục 14.9). Người nhận gọi RSACryptoServiceProvider.Decrypt và truyền cho nó
ciphertext mà bạn đã gửi. Người nhận phải chỉ định cơ chế padding, và nó cũng phải giống
như khi mật hóa dữ liệu. Phương thức Decrypt trả về một mảng byte chứa plaintext đã-đượcgiải-mật-hóa. Nếu plaintext mô tả một chuỗi, người nhận phải chuyển mảng byte thành giá trị
chuỗi thích hợp bằng lớp System.Text.Encoding.
Lớp
Lớp RSACryptoServiceProvider thừa kế các phương thức có tên là EncryptValue
và DecryptValue từ lớp cha của nó là System.Security.Cryptography.RSA. Lớp
RSACryptoServiceProvider không hiện thực các phương thức này và ném ngoại lệ
System.NotSupportedException nếu bạn gọi chúng.
dưới đây trình bày cách
RSACryptoServiceProvider để mật hóa một chuỗi và rồi giải mật hóa:
AsymmetricEncryptionExample
using System;
sử
dụng
lớp
571
Chương 14: Mật mã
using System.Text;
using System.Security.Cryptography;
public class AsymmetricEncryptionExample {
public static void Main(string[] args) {
// Khai báo một biến RSAParameters, biến này sẽ chứa
// thông tin PUBLIC KEY của người nhận.
RSAParameters recipientsPublicKey;
// Khai báo một biến CspParameters, biến này sẽ cho biết
// PRIVATE KEY được lưu trữ trong kho chứa khóa nào.
// Thông thường, chỉ có người nhận mới có thể truy xuất
// thông tin này. Với mục đích minh họa, chúng ta sẽ tạo
// một cặp khóa ngay đầu ví dụ và sử dụng các khóa này
// cho cả bên gửi và bên nhận.
CspParameters cspParams = new CspParameters();
cspParams.KeyContainerName = "MyKeys";
// Tạo cặp khóa bất đối xứng bằng lớp RSACryptoServiceProvider.
// Lưu các khóa này vào một kho chứa khóa có tên là "MyKeys"
// và trích thông tin PUBLIC KEY vào biến recipientsPublicKey.
using (RSACryptoServiceProvider rsaAlg =
new RSACryptoServiceProvider(cspParams)) {
// Cấu hình cho giải thuật lưu khóa vào kho chứa khóa.
rsaAlg.PersistKeyInCsp = true;
// Trích PUBLIC KEY.
recipientsPublicKey = rsaAlg.ExportParameters(false);
}
// Hiển thị thông điệp plaintext gốc.
Console.WriteLine("Original message = {0}", args[0]);
// Chuyển thông điệp gốc thành mảng byte. Tốt nhất là không
// truyền các thông tin bí mật ở dạng chuỗi giữa các phương thức.
572
Chương 14: Mật mã
byte[] plaintext = Encoding.Unicode.GetBytes(args[0]);
// Mật hóa thông điệp bằng phương thức EncryptMessage.
// Phương thức này cần PUBLIC KEY của người nhận.
byte[] ciphertext = EncryptMessage(plaintext,
recipientsPublicKey);
// Hiển thị ciphertext do phương thức EncryptMessage trả về.
// Sử dụng phương thức BitConverter.ToString method cho đơn giản
// mặc dù nó chèn dấu gạch nối (-) vào giữa các giá trị byte
// (không đúng với biểu diễn dữ liệu trong bộ nhớ).
Console.WriteLine("Formatted Ciphertext = {0}",
BitConverter.ToString(ciphertext));
// Giải mật hóa thông điệp (đã-được-mật-hóa) bằng phương thức
// DecryptMessage. Phương thức này cần truy xuất PRIVATE KEY
// của người nhận (chỉ có người nhận mới có thể truy xuất được).
// Chúng ta sẽ truyền cho nó một đối tượng CspParameters
// (cho biết PRIVATE KEY được lưu trữ trong kho chứa khóa nào).
// Giải pháp này an toàn hơn là truyền PRIVATE KEY thô
// giữa các phương thức.
byte[] decData = DecryptMessage(ciphertext, cspParams);
// Chuyển thông điệp đã-được-giải-mật-hóa từ mảng byte
// thành chuỗi và hiển thị nó ra cửa sổ Console.
Console.WriteLine("Decrypted message = {0}",
Encoding.Unicode.GetString(decData));
// Nhấn Enter để kết thúc.
Console.ReadLine();
}
// Phương thức dùng để mật hóa (theo RSA) một thông điệp bằng
// PUBLIC KEY (nằm trong một cấu trúc RSAParameters).
private static byte[] EncryptMessage(byte[] plaintext,
RSAParameters rsaParams) {
573
Chương 14: Mật mã
// Khai báo mảng byte chứa ciphertext.
byte[] ciphertext = null;
// Tạo một thể hiện của giải thuật RSA.
using (RSACryptoServiceProvider rsaAlg =
new RSACryptoServiceProvider()) {
rsaAlg.ImportParameters(rsaParams);
// Mật hóa plaintext bằng OAEP padding
// (chỉ được hỗ trợ trên Windows XP trở về sau).
ciphertext = rsaAlg.Encrypt(plaintext, true);
}
// Xóa các giá trị được giữ trong mảng byte chứa plaintext.
// Điều này bảo đảm dữ liệu bí mật không còn trong bộ nhớ
// sau khi bạn giải phóng tham chiếu đến nó.
Array.Clear(plaintext, 0, plaintext.Length);
return ciphertext;
}
// Phương thức dùng để giải mật hóa một thông điệp (đã-được-mật-hóa// theo-RSA) bằng PRIVATE KEY (do đối tượng CspParameters chỉ định).
private static byte[] DecryptMessage(byte[] ciphertext,
CspParameters cspParams ) {
// Khai báo mảng byte chứa plaintext (đã-được-giải-mật-hóa).
byte[] plaintext = null;
// Tạo một thể hiện của giải thuật RSA.
using (RSACryptoServiceProvider rsaAlg =
new RSACryptoServiceProvider(cspParams)) {
// Giải mật hóa plaintext bằng OAEP padding.
plaintext = rsaAlg.Decrypt(ciphertext, true);
}
574
Chương 14: Mật mã
return plaintext;
}
}
Lệnh AsymmetricEncryptionExample "I love you!" sẽ sinh ra kết xuất tương tự như sau:
Original message = I love you!
Formatted Ciphertext = 1F-53-05-2B-9D-CC-20-6B-5D-D3-D4-0B-C9-5F-CA-FA-C1-61-6C3B-5B-9E-EA-B9-D0-AF-E5-2B-05-BC-D4-94-DD-71-D6-21-2A-B0-82-6B-16-C0-89-3E-24-B3B3-A3-15-FE-16-7A-B0-58-14-43-CD-69-1A-FD-08-39-2D-09-A6-41-86-96-78-B4-3D-D6-C739-8A-90-84-D6-68-E6-5D-86-32-14-67-51-A7-B7-5A-EF-CF-F4-6D-E4-B0-18-6A-16-2A-AF54-B7-3C-B8-19-6E-A5-86-BF-3E-B2-6D-17-E3-1D-E8-AD-D1-A8-D9-54-93-8E-F1-E8-5D-AC4A
Decrypted message = I love you!
9.
Nếu bạn chạy ví dụ này nhiều lần với cùng thông điệp và khóa, ciphertext sẽ
khác nhau. Đó là vì cơ chế padding sinh ra dữ liệu ngẫu nhiên để tránh các dạng
tấn công bằng mật mã. Mặc dù hơi rắc rối nhưng đây chính là cách hành xử mà
ta mong đợi.
Lưu trữ khóa bất đối xứng một cách an toàn
Bạn cần lưu trữ cặp khóa bất đối xứng vào một nơi an toàn để ứng dụng của bạn
có thể truy xuất được dễ dàng.
Dựa vào chức năng lưu trữ khóa do hai lớp giải thuật bất đối xứng cung cấp
(RSACryptoServiceProvider và DSACryptoServiceProvider—thuộc không gian tên
System.Security.Cryptography).
Cả hai lớp giải thuật bất đối xứng—RSACryptoServiceProvider và DSACryptoServiceProvider
—đều bọc lấy các chức năng do CSP (Cryptographic Service Provider—một thành phần của
Win32 CryptoAPI) hiện thực. Ngoài các dịch vụ như mật hóa, giải mật hóa, và chữ ký số, mỗi
CSP còn cung cấp một kho chứa khóa (key container).
Kho chứa khóa là vùng lưu trữ dành cho các khóa mà CSP quản lý; CSP sử dụng cơ chế bảo
mật của hệ điều hành và phép mật hóa mạnh để bảo vệ nội dung của kho chứa khóa. Kho chứa
khóa cho phép ứng dụng dễ dàng truy xuất khóa mà không ảnh hưởng đến tính bảo mật của
khóa. Khi gọi các hàm của một CSP, ứng dụng cần chỉ định tên của kho chứa khóa và CSP sẽ
truy xuất các khóa cần thiết. Vì khóa không truyền từ CSP đến ứng dụng nên ứng dụng không
thể làm hại tính bảo mật của khóa.
Lớp RSACryptoServiceProvider và DSACryptoServiceProvider cho phép bạn cấu hình hiện
thực CSP nằm dưới bằng một thể hiện của lớp System.Security.Cryptography.
CspParameters. Để cấu hình cho một đối tượng RSACryptoServiceProvider hay
DSACryptoServiceProvider sử dụng một kho chứa khóa cụ thể, bạn phải hoàn tất các bước
dưới đây:
1.
Tạo một đối tượng CspParameters.
575
Chương 14: Mật mã
2.
Thiết lập trường KeyContainerName của đối tượng CspParameters là một giá trị chuỗi
mô tả tên của kho chứa khóa cần sử dụng; chuỗi có thể chứa khoảng trắng.
3.
Tạo một đối tượng RSACryptoServiceProvider hay DSACryptoServiceProvider, và
truyền đối tượng CspParameters làm đối số cho phương thức khởi dựng.
Nếu kho chứa khóa tồn tại bên trong tầm vực của CSP và chứa các khóa thích hợp, CSP sẽ sử
dụng các khóa này khi thực hiện các thao tác mật mã. Nếu kho chứa khóa hay khóa không tồn
tại, CSP sẽ tự động tạo khóa mới. Để buộc CSP lưu trữ các khóa mới được tạo vào kho chứa
khóa, bạn phải thiết lập thuộc tính PersistKeyInCsp (của đối tượng
RSACryptoServiceProvider hay DSACryptoServiceProvider) là true.
Phương thức LoadKeys dưới đây là một đoạn trích trong file StoreAsymmetricKeyExample.cs
(xem đĩa CD đính kèm). LoadKeys tạo một đối tượng RSACryptoServiceProvider và cấu hình
cho nó sử dụng một kho chứa khóa có tên là MyKeys. Bằng cách chỉ định PersistKeyInCsp là
true, giải thuật sẽ tự động lưu trữ các khóa mới được tạo vào kho chứa khóa này.
// Phương thức này tạo một đối tượng RSACryptoServiceProvider
// và nạp các khóa từ một kho chứa khóa nếu chúng tồn tại; nếu không,
// RSACryptoServiceProvider sẽ tự động tạo các khóa mới và lưu
// chúng vào kho chứa khóa để sử dụng sau này.
public static void LoadKeys(string container) {
// Tạo một đối tượng CspParameters và thiết lập trường
// KeyContainerName là tên của kho chứa khóa.
System.Security.Cryptography.CspParameters cspParams =
new System.Security.Cryptography.CspParameters();
cspParams.KeyContainerName = container;
// Tạo một đối tượng giải thuật RSA và truyền đối tượng
// CspParameters làm đối số trong phương thức khởi dựng.
using (System.Security.Cryptography.RSACryptoServiceProvider
rsaAlg = new
System.Security.Cryptography.RSACryptoServiceProvider(cspParams)){
// Cấu hình cho đối tượng RSACryptoServiceProvider
// lưu trữ khóa vào kho chứa khóa.
rsaAlg.PersistKeyInCsp = true;
// Hiển thị PUBLIC KEY.
System.Console.WriteLine(rsaAlg.ToXmlString(false));
}
}