1.1 Truy lại khóa đối xứng từ password V V Bạn cần tạo một khóa đối xứng từ một password để người dùng chỉ cần nhớ password và không cần lưu trữ khóa. # # Sử dụng lớp System.Security.Cryptography.PasswordDeriveBytes để tạo khóa đối xứng từ chuỗi password. Hiếm có người nào nhớ được giá trị của một khóa đối xứng, và không thực tế khi bắt người dùng nhập các số dài như thế bằng tay. Điều này nghĩa là khóa phải được lưu trữ ở một dạng an toàn sao cho ứng dụng có thể truy xuất được (trong smart card, đĩa mềm, cơ sở dữ liệ u hay file). Vấn đề liên quan với việc cấp, phân bổ, truy xuất, và lưu trữ khóa là một trong những mặt khó nhất trong quá trình hiện thực bất kỳ giải pháp mậtmã nào (vấn đề này được quy chung về quản lý khóa). Khi cần ghi nhớ một bí mật (khóa), bạn không chỉ lo bảo vệ dữ liệu mà còn phải lo bảo vệ các bí mật dùng để bảo vệ dữ liệu! Một cách lưu trữ khóa là cấp cho người dùng m ột password dễ nhớ hơn và sử dụng một giao thức truy lại khóa (key derivation protocol) để tạo một khóa đối xứng từ password. Theo đó, mỗi khi cần mật hóa hay giải mật hóa dữ liệu, người dùng chỉ cần nhập password và máy tính sẽ tạo ra khóa (nếu người dùng nhập cùng password, giao thức này sẽ tạo ra cùng khóa). # Truy lại khóa từ những từ hay nhóm từ dễ nhớ làm giảm đáng kể tính ngẫu nhiên của khóa, dẫn đến giảm tính bảo mật được cấp bởi những hàm mậtmã có sử dụng khóa đó. Trong trường hợp tồi tệ nhất, hacker có thể đoán được password, và crack dữ liệu của bạn thông qua việc giải mãmậtmã (cryptanalysis). Thư viện lớp .NET Framework có một hiện thự c truy lại khóa đối xứng: PasswordDeriveBytes. Lớp này sử dụng một giải thuật băm được áp dụng lặp đi lặp lại cho một password để tạo ra một khóa với chiều dài như mong muốn. Khi cấu hình một đối tượng PasswordDeriveBytes, bạn có thể chỉ định tên giải thuật băm cũng như số lần lặp (mặc định, SHA-1 được áp dụng 100 lần). Ngoài ra, bạn cũng cầ n cung cấp giá trị salt. Salt là dữ liệu ngẫu nhiên mà quá trình truy lại khóa sẽ sử dụng để làm cho khóa tìm được trở nên bền vững hơn đối với các dạng tấn công bằng mật mã. Bạn không cần giữ bí mật cho giá trị salt; bạn phải lưu trữ và sử dụng nó khi truy lại khóa từ password sau này (nếu không có giá trị salt đúng, bạn sẽ không thể truy lại khóa đúng và không thể giải mật hóa được). # Bạn không thể tạo khóa bất đối xứng bằng giao thức truy lại khóa. Giải thuật mật hóa bất đối xứng (asymmetric encryption) dựa vào các mối liên hệ toán cụ thể giữa các thành phần khóa công khai (public key) và khóa riêng (private key). Như thế, mỗi giải thuật mật hóa bất đối xứng yêu cầu bạn phải tuân theo một quy trình riêng để có thể tạo ra các khóa mới. Ví dụ dưới đây trình bày cách sử dụng lớp PasswordDeriveBytes để tạo một khóa đối xứng gồm 64 bit từ một chuỗi password. Ví dụ này yêu cầu hai đối số dòng lệnh: tên giải thuật băm và password (tên của các giải thuật băm đã được liệt kê trong bảng 14.1). using System; using System; using System.Security.Cryptography; public class DerivedKeyExample { public static void Main(string[] args) { // Sử dụng một bộ tạo số ngẫu nhiên để tạo giá trị salt. byte[] salt = new byte[8]; RandomNumberGenerator.Create().GetBytes(salt); // Tạo một đối tượng PasswordDeriveBytes để tạo khóa từ // password. Cần cung cấp password nguồn (là đối số dòng // lệnh thứ hai) và salt. PasswordDeriveBytes pdb = new PasswordDeriveBytes(args[1], salt); // Thiết lập giải thuật băm dùng để tạo khóa, tên // giải thuật được chỉ định trong đối số dòng lệnh thứ nhất. // Giải thuật được sử dụng mặc định là SHA-1. pdb.HashName = args[0]; // Thiết lập số lần lặp là 200 (đây là số lần giả i thuật băm // được áp dụng cho password để tạo khóa). Mặc định là 100. pdb.IterationCount = 200; // Tạo một khóa gồm 8 byte (64 bit) từ password. // Chiều dài của khóa bị giới hạn bởi chiều dài của // mã băm - 160 bit đối với SHA-1. byte[] key = pdb.GetBytes(8); // Hiển thị khóa và salt. Console.WriteLine("Key = {0}", BitConverter.ToString(key)); Console.WriteLine("Salt = {0}", BitConverter.ToString(salt)); } } Chạy lệnh DerivedKeyExample SHA1 S0meVereeStr@ngeP@$$w0rd (sử dụng giải thuật băm SHA-1 để truy lại một khóa gồm 8 byte từ chuỗi "S0meVereeStr@ngeP@$$w0rd") sẽ sinh ra kết xuất tương tự như sau: Key = 53-72-74-5B-A4-88-A4-80 Salt = 70-82-79-F4-3B-F9-DF-D2 Chú ý rằng, mỗi khi bạn chạy cùng một lệnh, DerivedKeyExample sinh ra khóa khác nhau. Đó là do tác dụng của salt. Nếu bạn bỏ đi dòng lệnh gán một giá trị ngẫu nhiên vào salt (được in đậm trong đoạn mã trên), sau đó biên dịch lại và chạy DerivedKeyExample, bạn sẽ nhận thấy ví dụ này luôn tạo ra cùng một khóa với một password cho trước. 1.2 Gửi một bí mật bằng phép mật hóa bất đối xứng V V Bạn cần sử dụng phép mật hóa bất đối xứng (asymmetric encryption) để gửi một bí mật. # # Thể hiện hóa lớp giải thuật bất đối xứng System.Security.Cryptography. RSACryptoServiceProvider. Sử dụng phương thức RSACryptoServiceProvider. 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. • 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 ExportParameters 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ữ. 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 đã-được-giả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 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. Lớp AsymmetricEncryptionExample dưới đây trình bày cách sử dụng lớp RSACryptoServiceProvider để mật hóa một chuỗi và rồi giải mật hóa: using System; 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. 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) { // 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); } 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-6C-3B-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-B3-B3-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-C7-39-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-AF-54-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-AC-4A Decrypted message = I love you! # 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. . = 1F- 53- 05-2B-9D-CC-20-6B-5D-D3-D4-0B-C9-5F-CA-FA- C1-61-6C-3B-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-B3-B3-A3-15-FE-16-7A-B0-58-14- 43- CD-69-1A-FD-08-. 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