1.1 Lưu trữ khóa bất đối xứng một cách an toàn V V 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. 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)); } } Lớp RSACryptoServiceProvider và DSACryptoServiceProvider không cung cấp phương thức nào trực tiếp gỡ bỏ kho chứa khóa. Để xóa các khóa đã được lưu trữ, bạn hãy thiết lập giá trị của PersistKeyInCsp là false và gọi phương thức Clear hay Dispose của đối tượng RSACryptoServiceProvider hay DSACryptoServiceProvider. Phương thức DeleteKeys dưới đây sẽ trình bày kỹ thuật này: // Phương thức này tạo một đối tượng RSACryptoServiceProvider // và xóa các khóa hiện có khỏi kho chứa khóa. public static void DeleteKeys(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 cần xó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 // không lưu trữ khóa vào kho chứa khóa. rsaAlg.PersistKeyInCsp = false; // Hiển thị PUBLIC KEY. Vì chúng ta gọi Dispose() // sau lời gọi này nên các khóa hiện có sẽ không thay đổi // cho đến khi phương thức được gọi lần thứ hai. System.Console.WriteLine(rsaAlg.ToXmlString(false)); // Vì mã lệnh nằm trong khối "using" nên Dispose được gọi // trên đố i tượng RSACryptoServiceProvider. Vì đối tượng // này được cấu hình là không lưu trữ khóa nên kho chứa khóa // sẽ bị xóa. Thay vì gọi Dispose(), gọi rsaAlg.Clear() // sẽ có cùng tác dụng, vì nó gián tiếp gọi Dispose(). } } Win32 CryptoAPI hỗ trợ cả user-key-store và machine-key-store. Hệ điều hành Windows bảo đảm một user-key-store chỉ có thể được truy xuất bởi người đã tạo ra nó, nhưng một machine-key-store có thể được truy xuất bởi bất kỳ người dùng nào của máy. Theo mặc định, lớp RSACryptoServiceProvider và DSACryptoServiceProvider sẽ sử dụng user- key-store. Bạn có thể chỉ định sử dụng machine-key-store bằng cách thiết l ập thuộc tính tĩnh UseMachineKeyStore của lớp RSACryptoServiceProvider hay DSACryptoServiceProvider là true. Điều này sẽ có tác dụng với tất cả mã lệnh đang chạy trong miền ứng dụng hiện hành. Nếu muốn kiểm soát chặt chẽ hơn, bạn có thể thiết lập thuộc tính CspParameters.Flags là giá trị System.Security.Cryptography.CspProviderFlags.UseMachineKeyStore trước khi tạo đối tượng mật hóa bất đối xứng. # Bạn nên xét các yêu cầu bảo mật một cách cẩn thận trước khi chọn sử dụng machine-key-store. Thực tế là người dùng nào có quyền truy xuất máy đều có thể giành được quyền truy xuất các khóa trong kho lưu trữ, điều này phủ định hầu hết các lợi ích do phép mật hóa bất đối xứng mang lại. 1.2 Trao đổi khóa phiên đối xứng một cách an toàn V V Bạn cần trao đổi dữ liệu đã-được-mật-hóa-đối-xứng với ai đó, và bạn cần một biện pháp an toàn để phân bổ khóa phiên (session key) đối xứng cùng với dữ liệu. # # Sử dụng cơ chế trao đổi khóa do lớp System.Security.Cryptography. RSACryptoServiceProvider hiện thực. Theo cơ chế này, khóa đối xứng sẽ được mật hóa bất đối xứng bằng khóa công khai (public key) của người nhận. Theo đó, bạn có thể gửi khóa đối xứng đã-được-mật-hóa cùng với dữ liệu đã-được- mật-hóa. Người nhận phải giải mật hóa khóa đối xứng bằng khóa riêng (private key), rồi m ới tiến hành giải mật hóa dữ liệu. Mỗi khi mật hóa dữ liệu (bằng giải thuật đối xứng) để chuyển giao, bạn nên tạo một khóa mới, được gọi là khóa phiên (session key). Sử dụng khóa phiên có hai lợi ích chính: • Nếu ai đó (không được phép) lấy được nhiều khối ciphertext đã được mật hóa bằng cùng một khóa đối xứng, khả năng người đó giải được dữ liệu sẽ tăng cao. • Nếu ai đó tìm được khóa phiên của bạn, người này chỉ có thể truy xuất được một tập dữ liệu nào đó đã-được-mật-hóa, chứ không phải tất cả các bí mật của bạn ở quá khứ và tương lai. Vấn đề đối với khóa phiên là phân bổ và bảo mật khóa. Một giải pháp là thỏa thuận một lượng lớn khóa phiên với nhữ ng người mà bạn cần trao đổi dữ liệu với họ. Thật không may, việc này nhanh chóng trở nên khó quản lý; và thực tế là tất cả các khóa của bạn trong tương lai đều được lưu trữ tại một nơi nào đó, điều này tăng khả năng chúng sẽ bị xâm hại. Cách tốt hơn là gửi khóa phiên theo một dạng được mật hóa mạnh cùng với dữ liệu mà bạn đã m ật hóa với khóa đó—quá trình này được gọi là trao đổi khóa (key exchange). Quá trình trao đổi khóa sử dụng phép mật hóa bất đối xứng để mật hóa khóa phiên đối xứng. Nếu muốn gửi dữ liệu cho ai đó, bạn tạo một khóa phiên đối xứng, mật hóa dữ liệu, và rồi mật hóa khóa phiên bằng khóa công khai của người nhận. Khi nhận được dữ liệu, người nhận giải mật hóa khóa phiên b ằng khóa riêng của họ, và rồi giải mật hóa dữ liệu. Quan trọng là việc trao đổi khóa cho phép bạn trao đổi các lượng dữ liệu lớn (đã-được- mật-hóa) với bất cứ ai, thậm chí những người bạn chưa từng tiếp xúc trước đây, miễn là bạn có thể truy xuất khóa công khai đối xứng của họ. # Một cách lý tưởng, bạn sử dụng một giải thuật bất đối xứng để mật hóa tất cả dữ liệu, như thế tránh được nhu cầu trao đổi các khóa đối xứng. Tuy nhiên, tốc độ của các giải thuật bất đối xứng khi mật hóa và giải mật hóa dữ liệu khiến chúng không thực tế cho việc sử dụng với các lượng lớn dữ liệu. Sử dụng các giải thuật bất đối xứng để mật hóa các khóa phiên đối xứng là một giải pháp tuy phức tạp hơn, nhưng là tốt nhất ở cả hai mặt: tính linh hoạt và tính nhanh chóng. Thư viện lớp .NET Framework hỗ trợ việc trao đổi khóa chỉ với giải thuật RSA, nhưng bạ n phải lựa chọn giữa hai formatting scheme: Optimal Asymmetric Encryption Padding (OAEP) và PKCS #1 v 1.5. Bàn về các formatting scheme này vượt quá phạm vi của quyển sách này. Nói chung, bạn nên sử dụng OAEP formatting trừ khi bạn có nhu cầu giao tiếp với một hệ thống cũ có sử dụng PKCS formatting. Hai lớp dưới đây hiện thực cơ chế trao đổi khóa, mỗi cơ chế ứng với một formatting scheme: • System.Security.Cryptography.RSAOAEPKeyExchangeFormatter • System.Security.Cryptography.RSAPKCS1KeyExchangeFormatter Để chuẩn bị một khóa đối xứng dùng cho trao đổi, bạn phải tạo một đối tượng formatter với kiểu như mong muốn và rồi ấn định một đối tượng giải thuật bất đối xứng (RSACryptoServiceProvider) cho formatter bằng phương thức SetKey của formatter. Bạn phải cấu hình cho giải thuật bất đối xứng sử dụng khóa công khai củ a người nhận. Khi đã cấu hình xong, gọi phương thức CreateKeyExchange của formatter và truyền một mảng byte chứa khóa phiên đối xứng mà bạn cần định dạng. Phương thức CreateKeyExchange trả về một mảng byte chứa dữ liệu bạn sẽ gửi đi. Giải định dạng cho khóa ngược với quá trình định dạng. Có hai lớp deformatter, mỗi lớp ứng với một formatting scheme. • System.Security.Cryptography.RSAOAEPKeyExchangeDeformatter • System.Security.Cryptography.RSAPKCS1KeyExchangeDeformatter Để giải định dạng một khóa phiên đã được định dạng, hãy tạo một đối tượng deformatter với kiểu phù hợp rồi gọi phương thức SetKey của nó để ấn định một đối tượng giải thuật bất đối xứng. Bạn phải nạp khóa riêng của bạn vào giải thuật bất đối xứng. Cuối cùng, gọi phương th ức DecryptKeyExchange với đối số là dữ liệu trao đổi. Phương thức này trả về một mảng byte chứa khóa phiên đối xứng gốc. File KeyExchangeExample.cs chứa ví dụ minh họa cho việc trao đổi khóa. Phương thức Main mô phỏng việc tạo, định dạng, trao đổi, và giải định dạng một khóa phiên đối xứng. Nó sẽ tạo một cặp khóa bất đối xứng để sử dụng cho cả ví dụ này. Thực tế, người gửi (người tạo khóa đối xứng) chỉ có khóa công khai của người nhận; người nhận có khóa riêng (được giữ bí mật). # Cũng như nên sử dụng một khóa đối xứng có chiều dài phù hợp với tính bí mật của dữ liệu đang được bảo vệ, bạn nên mật hóa khóa phiên bằng một giải thuật bất đối xứng và chiều dài khóa ít nhất cũng phải tương đương với khóa đối xứng. Nếu khóa bất đối xứng yếu hơn khóa đối xứng, có khả năng kẻ tấn công sẽ phá vỡ giải thuật bất đối xứng và thu lấy khóa đối xứng thay vì cố giải mật hóa dữ liệu đã-được-mật-hóa-đối-xứng. Xem [http://www.ietf.org/rfc/rfc3766.txt] (có trong đĩa CD đính kèm) để biết chi tiết về sự tương đương giữa chiều dài khóa đối xứng và b ất đối xứng. Kế đó, phương thức Main gọi phương thức FormatKeyExchange với đối số là một mảng byte chứa khóa đối xứng và một đối tượng RSAParameters chứa khóa công khai của người nhận. Phương thức FormatKeyExchange trả về một mảng byte chứa khóa đối xứng đã-được-mật-hóa và đã-được-định-dạng, chuẩn bị gửi đi. Kế tiếp, phương thức Main g ọi phương thức DeformatKeyExchange với đối số là dữ liệu trao đổi đã được định dạng và đối tượng CspParameters chứa một tham chiếu đến kho chứa khóa MyKeys (chứa khóa riêng của người nhận). Trong suốt quá trình này, phương thức Main sẽ hiển thị khóa phiên gốc, dữ liệu trao đổi đã được định dạng, và cuối cùng là khóa phiên đã được giải định dạng. using System; using System.Text; using System.Security.Cryptography; public class KeyExchangeExample { public static void Main() { // 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); } // Tạo giải thuật đối xứng Triple-DES và sử dụng // khóa được sinh tự động làm khóa phiên. using (SymmetricAlgorithm symAlg = SymmetricAlgorithm.Create("3DES")) { // Hiển thị khóa phiên gốc. Console.WriteLine("Session Key at Source = {0}\n\r", BitConverter.ToString(symAlg.Key)); // Chuẩn bị khóa phiên đối xứng dùng cho trao đổi // (sử dụng phương thức FormatKeyExchange, phương thức // này cần khóa dùng để mật hóa và PUBLIC KEY // của người nhận). byte[] exchangeData = FormatKeyExchange(symAlg.Key, recipientsPublicKey); // Hiển thị khóa phiên đã-được-mật-hóa (do phương thứ c // FormatKeyExchange trả về). Console.WriteLine("Exchange Data = {0}\n\r", BitConverter.ToString(exchangeData)); // ****** GỬI KHÓA ****** // Bây giờ, khóa phiên có thể được gửi đi bằng các // kênh giao tiếp bình thường. // Trích khóa phiên từ dữ liệu trao đổi bằng // phương thức DeformatKeyExchange. byte[] sessionKey = DeformatKeyExchange(exchangeData, cspParams); // Hiển thị khóa phiên vừa được trích. Console.WriteLine("Session Key at Destination = {0}\n\r", BitConverter.ToString(sessionKey)); // Nhấn Enter để kết thúc. Console.ReadLine(); } } // Phương thức dùng để mật hóa và định dạng khóa phiên đối xứng. // Để mật hóa khóa phiên, chúng ta cần truy xuất PUBLIC KEY // của người nhận (trong cấu trúc RSAParameters). private static byte[] FormatKeyExchange(byte[] sessionKey, RSAParameters rsaParams) { // Tạo một giải thuật bất đối xứng RSA. using (RSACryptoServiceProvider asymAlg = new RSACryptoServiceProvider()) { // Nạp PUBLIC KEY của người nhận. asymAlg.ImportParameters(rsaParams); // Tạo một RSA OAEP formatter để định dạng dữ liệu trao đổi. RSAOAEPKeyExchangeFormatter formatter = new RSAOAEPKeyExchangeFormatter(); // Chỉ định giải thuật RSA dùng để mật hóa khóa phiên. formatter.SetKey(asymAlg); // Mật hóa và định dạng khóa phiên rồi trả về kết quả. return formatter.CreateKeyExchange(sessionKey); } } // Phương thức dùng để giải mật hóa dữ liệu trao đổi và trích khóa phiên // đối xứng. Để giải mật hóa dữ liệu trao đổi, chúng ta cần truy xuất // PRIVATE KEY (từ kho chứa khóa do đối số cspParams chỉ định). private static byte[] DeformatKeyExchange(byte[] exchangeData, CspParameters cspParams) { // Tạo một giải thuật bất đối xứng RSA. using (RSACryptoServiceProvider asymAlg = new RSACryptoServiceProvider(cspParams)) { // Tạo một RSA OAEP deformatter để trích khóa phiên // từ dữ liệu trao đổi. RSAOAEPKeyExchangeDeformatter deformatter = new RSAOAEPKeyExchangeDeformatter(); // Chỉ định giải thuật RSA dùng để giải mật hóa dữ liệu trao đổi. deformatter.SetKey(asymAlg); // Giải mật hóa dữ liệu trao đổi và trả về khóa phiên. return deformatter.DecryptKeyExchange(exchangeData); } } } Chạy KeyExchangeExample sẽ sinh ra kết xuất tương tự như sau: Session Key at Source = EE-5B-16-5B-AC-46-3D-72-CC-73-19-D9-0B-8A-19-E2- A6-02-13-BE-F8-CE-DF-40 Exchange Data = 60-FA-3B-63-41-25-F1-AD-08-F9-FC-67-CD-C6-FB-3E-0F-C3- 62- C6-3F-5C-C0-7E-D1-60-2D-19-58-07-EE-BB-7C-53-A5-C2-FB-CA-D7-64-FF-BA- 33-77-AC-52-87-5F-75-E7-57-99-01-90-CD-70-36-1E-53-0C-82-C6-CE-B8-BC- 8B-C9-39-6F-29-39-5F-6C-A6-43-E5-B0-A1-42-46-1C-9B-1C-72-EB-5E-67-06-44- C0-CE-AB-70-B8-39-8E-9F-01-E8-49-51-36-D6-27-09-94-DA-42-CE-79-C2-72- 88-4D-CE-63-B4-A0-AC-07-AF-26-A7-76-DE-21-BE-A5 Session Key at Destination = EE-5B-16-5B-AC-46-3D-72-CC-73-19-D9-0B-8A-19- E2-A6-02-13-BE-F8-CE-DF- . Người nhận phải giải mật hóa khóa đối xứng bằng khóa riêng (private key), rồi m ới tiến hành giải mật hóa dữ liệu. Mỗi khi mật hóa dữ liệu (bằng giải thuật. mật hóa bất đối xứng bằng khóa công khai (public key) của người nhận. Theo đó, bạn có thể gửi khóa đối xứng đã-được -mật- hóa cùng với dữ liệu đã-được- mật- hóa.