Giải thuật/Tên lớp Kích thước khóa bit Kích thước mã băm bit HMACSHA1 bất kỳ 160 MACTripleDES 64, 128, 192 64 Cũng như các giải thuật băm chuẩn, bạn có thể trực tiếp tạo ra các đối tượn
Trang 11.1 Bảo đảm tính toàn vẹn dữ liệu bằng mã băm có khóa
V Bạn cần chuyển một file cho ai đó và cấp cho người này một phương cách để xác minh tính toàn vẹn của file
# Cấp cho người nhận một khóa bí mật (key) Khóa này có thể là một số được
sinh ngẫu nhiên, nhưng nó cũng có thể là một nhóm từ mà bạn và người nhận
đã thỏa thuận Sử dụng khóa cùng với một trong những lớp giải thuật băm có khóa dẫn xuất từ lớp System.Security.Cryptography.KeyedHashAlgorithm để tạo mã băm có khóa Gửi mã băm này cùng với file Khi nhận được file, người nhận sẽ tạo mã băm có khóa cho file này bằng khóa Nếu hai mã băm giống nhau, người nhận sẽ biết rằng file này do bạn gửi đến và nó không bị thay đổi trong quá trình chuyển giao
Mã băm rất hữu ích khi so sánh hai mẩu dữ liệu để xác định chúng có giống nhau hay không (cả khi bạn không thể truy xuất được dữ liệu gốc) Tuy nhiên, bạn không thể sử dụng mã băm để cam đoan với người nhận về tính toàn vẹn của dữ liệu Nếu có ai đó chặn được dữ liệu, người này có thể thay thế dữ liệu và tạo mã băm mới Khi người nhận kiểm tra mã băm, nó có vẻ đúng nhưng thực tế dữ liệu không giống với những gì bạn gửi lúc ban đầu
Một giải pháp đơn giản và hiệu quả cho vấn đề toàn vẹn dữ liệu là mã băm có khóa
(keyed hash code) Mã băm có khóa cũng tương tự như mã băm bình thường (đã được
thảo luận trong mục 14.2 và 14.3); tuy nhiên, mã băm có khóa kết hợp thêm một phần tử
dữ liệu bí mật (khóa), phần tử này chỉ có người gửi và người nhận biết Nếu không có khóa, không ai có thể tạo được mã băm đúng từ tập dữ liệu cho trước
# Khóa phải được giữ bí mật Nếu ai đó biết khóa thì có thể tạo ra mã băm có khóa hợp lệ, nghĩa là bạn sẽ không thể xác định họ có thay đổi nội dung của tài liệu hay không Vì lý do này, bạn không nên chuyển giao hay lưu trữ khóa cùng với tài liệu cần được bảo vệ tính toàn vẹn Mục 14.10 sẽ cung cấp một cơ chế
mà bạn có thể sử dụng để trao đổi khóa một cách an toàn
Tạo mã băm có khóa cũng tương tự như tạo mã băm bình thường vì lớp trừu tượng System.Security.Cryptography.KeyedHashAlgorithm mở rộng lớp System.Security.Cryptography.HashAlgorithm Lớp KeyedHashAlgorithm cung cấp một
lớp cơ sở để tất cả các giải thuật băm có khóa dẫn xuất từ đó Thư viện lớp NET
Framework có hai hiện thực giải thuật băm có khóa được liệt kê trong bảng 14.2; mỗi
hiện thực là một thành viên của không gian tên System.Security.Cryptography
Bảng 14.2 Các hiện thực giải thuật băm có khóa
Trang 2Giải thuật/Tên lớp Kích thước khóa (bit) Kích thước mã băm (bit)
HMACSHA1 bất kỳ 160 MACTripleDES 64, 128, 192 64
Cũng như các giải thuật băm chuẩn, bạn có thể trực tiếp tạo ra các đối tượng giải thuật băm có khóa, hoặc bạn có thể sử dụng phương thức tĩnh KeyedHashAlgorithm.Create với
đối số là tên giải thuật Sử dụng factory cho phép bạn viết mã lệnh tổng quát và mã lệnh
này có thể làm việc với bất kỳ hiện thực giải thuật băm có khóa nào, nhưng theo bảng 14.2, mỗi lớp hỗ trợ các chiều dài khóa khác nhau nên bạn phải cung cấp giá trị này trong
mã lệnh tổng quát
Nếu sử dụng phương thức khởi dựng để tạo đối tượng băm có khóa, bạn có thể truyền
khóa cho phương thức này Khi sử dụng factory, bạn phải thiết lập khóa bằng thuộc tính
Key (được thừa kế từ lớp KeyedHashAlgorithm) Một khi đã cấu hình khóa, gọi phương thức ComputeHash với đối số là một mảng byte hay một đối tượng System.IO.Stream Giải thuật băm có khóa sẽ xử lý dữ liệu nhập và trả về một mảng byte chứa mã băm có khóa Bảng 14.2 cho thấy kích thước của mã băm do mỗi giải thuật băm có khóa sinh ra Lớp KeyedHashStreamExample dưới đây trình bày cách tạo mã băm có khóa từ một file Bạn phải chỉ định tên file và một khóa làm đối số dòng lệnh Ứng dụng này sử dụng lớp
HMACSHA1 để tạo mã băm có khóa và rồi hiển thị nó ra cửa sổ Console
using System;
using System.IO;
using System.Text;
using System.Security.Cryptography;
public class KeyedHashStreamExample {
public static void Main(string[] args) {
// Tạo mảng byte từ chuỗi key (là đối số dòng lệnh thứ hai)
byte[] key = Encoding.Unicode.GetBytes(args[1]);
// Tạo một đối tượng HMACSHA1
// (truyền key cho phương thức khởi dựng)
using (HMACSHA1 hashAlg = new HMACSHA1(key)) {
// Mở một FileStream để đọc file (tên file
Trang 3// được chỉ định trong đối số dòng lệnh thứ nhất)
using (Stream file = new FileStream(args[0],
FileMode.Open)) {
// Tạo mã băm có khóa cho nội dung file
byte[] hash = hashAlg.ComputeHash(file);
// Hiển thị mã băm có khóa ra cửa sổ Console
Console.WriteLine(BitConverter.ToString(hash));
}
}
}
}
Lệnh KeyedHashStreamExample KeyedHashStreamExample.cs secretKey sẽ sinh ra
mã băm như sau:
95-95-2A-8E-44-D4-3C-55-6F-DA-06-44-27-79-29-81-15-C7-2A-48
Ứng dụng KeyedHashMessageExample.cs (có trong đĩa CD đính kèm) trình bày cách tạo
một mã băm có khóa từ một chuỗi Ứng dụng này yêu cần hai đối số dòng lệnh: một thông điệp và một khóa, và sẽ tạo ra mã băm có khóa cho chuỗi thông điệp bằng khóa
này Ví dụ, lệnh KeyedHashMessageExample "Two hundred dollars is my final
offer" secretKey sẽ sinh ra mã băm như sau:
83-43-0D-9D-07-6F-AA-B7-BC-79-CD-6F-AD-7B-FA-EA-19-D1-24-44
1.2 Bảo vệ file bằng phép mật hóa đối xứng
V Bạn cần mật hóa một file bằng giải thuật mật hóa đối xứng (symmetric encryption)
# Trước hết, bạn phải thể hiện hóa một trong các lớp giải thuật đối xứng cụ thể dẫn xuất từ lớp System.Security.Cryptography.SymmetricAlgorithm Sau đó, gọi phương thức CreateEncryptor hay CreateDecryptor của đối tượng SymmetricAlgorithm để thu lấy một đối tượng có hiện thực giao diện System.Security.Cryptography.ICryptoTransform Sử dụng đối tượng
System.Security.Cryptography.CryptoStream để mật hóa hay giải mật hóa dữ liệu đọc từ một file (được truy xuất bằng một đối tượng System.IO.FileStream)
Lớp trừu tượng SymmetricAlgorithm cung cấp một lớp cơ sở để tất cả các hiện thực giải
thuật đối xứng cụ thể dẫn xuất từ đó Thư viện lớp NET Framework có bốn hiện thực
giải thuật đối xứng cụ thể được liệt kê trong bảng 14.3, mỗi lớp là một thành viên của không gian tên System.Security.Cryptography Các lớp có đuôi là CryptoServiceProvider
Trang 4bọc lấy các chức năng do Win32 CryptoAPI cung cấp, trong khi các lớp có đuôi là
Managed (hiện tại chỉ có RijndaelManaged) được hiện thực hoàn toàn bằng mã lệnh được-quản-lý Bảng này cũng cho thấy chiều dài khóa mà mỗi giải thuật hỗ trợ (chiều dài mặc định được in đậm) Nói chung, khóa càng dài, càng khó giải mật hóa ciphertext nếu không có khóa, nhưng cũng có nhiều yếu tố khác cần xem xét
Bảng 14.3 Các hiện thực giải thuật đối xứng
Tên giải thuật Tên lớp Chiều dài khóa (bit)
DES DESCryptoServiceProvider 64
TripleDES hay
3DES TripleDESCryptoServiceProvider 128, 192
RC2 RC2CryptoServiceProvider 40, 48 56, 64, 72, 80, 88, 96, 104,
112, 120, 128
Rijndael RijndaelManaged 128, 192, 256
Mặc dù bạn có thể tạo ra các thể hiện của các lớp giải thuật đối xứng một cách trực tiếp,
lớp cơ sở SymmetricAlgorithm là một factory cho các lớp hiện thực cụ thể dẫn xuất từ
đó Gọi phương thức tĩnh SymmetricAlgorithm.Create với đối số là tên giải thuật sẽ trả
về một đối tượng thuộc kiểu đã được chỉ định Sử dụng factory cho phép bạn viết mã lệnh
tổng quát, và mã lệnh này có thể làm việc với bất kỳ hiện thực giải thuật đối xứng nào: string algName = "3DES";
SymmetricAlgorithm alg = SymmetricAlgorithm.Create(algName);
# Nếu bạn gọi SymmetricAlgorithm.Create và không chỉ định tên giải thuật, SymmetricAlgorithm sẽ trả về một đối tượng RijndaelManaged Nếu bạn chỉ định một giá trị không hợp lệ, SymmetricAlgorithm sẽ trả về null Bạn có thể
cấu hình các ánh xạ tên/lớp mới bằng file cấu hình (xem tài liệu NET Framework SDK để biết thêm chi tiết)
Trước khi mật hóa dữ liệu với một trong các lớp giải thuật đối xứng, bạn cần một khóa
(key) và một vectơ khởi động (initialization vector) Khóa là thông tin bí mật dùng để mật
hóa và giải mật hóa dữ liệu Vectơ khởi động là dữ liệu ngẫu nhiên được truyền cho giải thuật mật hóa Bạn phải sử dụng cùng khóa và vectơ khởi động cho cả mật hóa và giải mật hóa dữ liệu Tuy nhiên, chỉ có khóa là cần phải được giữ bí mật, bạn có thể lưu trữ hay gửi vectơ khởi động cùng với dữ liệu đã-được-mật-hóa
Trang 5Khóa cho mỗi lớp dẫn xuất từ SymmetricAlgorithm có thể được truy xuất thông qua thuộc tính Key, và vectơ khởi động có thể được truy xuất thông qua thuộc tính IV Cách đơn giản nhất và ít lỗi nhất để tạo khóa và vectơ khởi động mới là để lớp tự tạo chúng giùm bạn Sau khi đã tạo một đối tượng giải thuật đối xứng, nếu bạn không thiết lập các thuộc tính Key và IV cho nó, đối tượng này sẽ tự động tạo ra các giá trị mới ngay khi bạn cho gọi một thành viên có sử dụng các giá trị Key và IV Một khi đã được thiết lập, đối tượng giải thuật đối xứng sẽ tiếp tục sử dụng các giá trị Key và IV này Để thay đổi giá trị của Key và IV, bạn có thể gán trực tiếp các giá trị mới hoặc gọi phương thức GenerateKey và GenerateIV (buộc đối tượng giải thuật đối xứng tạo ra các giá trị ngẫu nhiên mới)
Bạn không thể trực tiếp thực hiện mật hóa và giải mật hóa với một đối tượng giải thuật đối xứng Một khi đã tạo và cấu hình đối tượng giải thuật đối xứng, bạn phải gọi phương thức CreateEncryptor hay CreateDecryptor của nó để thu lấy một đối tượng có hiện thực giao diện System.Security.Cryptography.ICryptoTransform Kế đó, bạn có thể sử dụng các phương thức của đối tượng ICryptoTransform này để mật hóa và giải mật hóa dữ liệu Tuy nhiên, đối tượng ICryptoTransform yêu cầu bạn truyền dữ liệu theo từng khối (có kích thước cố định) và lấp (bằng tay) khối dữ liệu cuối cùng vì khối này ít khi có kích thước đúng
Giao diện ICryptoTransform không quá khó sử dụng, nhưng không mấy thân thiện; do
vậy NET Framework kèm thêm lớp System.Security.Cryptography.CryptoStream Đây
là lớp dẫn xuất từ System.IO.Stream, dùng để đơn giản hóa việc mật hóa và giải mật hóa
dữ liệu được đọc từ các đối tượng Stream khác Lớp này cho phép bạn mật hóa và giải mật hóa dữ liệu từ các file và các kết nối mạng một cách dễ dàng bằng một mô hình xử lý quen thuộc, và nó cung cấp cho bạn tất cả các tiện ích quen thuộc khi truy xuất dữ liệu dựa-vào-Stream
Phương thức khởi dựng của CryptoStream yêu cầu ba đối số: một Stream nằm dưới, một thể hiện của ICryptoTransform, và một giá trị thuộc kiểu liệt kê System.Security.Cryptography.CryptoStreamMode Giá trị CryptoStreamMode cho biết chế độ của đối tượng CryptoStream mới; các giá trị hợp lệ là Read và Write Khi bạn gọi phương thức Read hay Write của CryptoStream, CryptoStream sẽ sử dụng thể hiện ICryptoTransform để mật hóa và giải mật hóa dữ liệu đang truyền qua CryptoStream Đối tượng CryptoStream bảo đảm kích thước khối dùng cho thể hiện ICryptoTransform luôn đúng
Cấu hình của một đối tượng CryptoStream có tính linh hoạt cao, nhưng có thể hơi khó hiểu Bảng 14.4 mô tả hoạt động của một đối tượng CryptoStream dựa trên chế độ của CryptoStream và kiểu thể hiện ICryptoTransform được sử dụng trong phương thức khởi dựng của CryptoStream
Trang 6Bảng 14.4 Hoạt động của đối tượng CryptoStream
Chế độ của
CryptoStream
Chỉ thị của ICryptoTransform Mô tả
Read Mật hóa Stream nằm dưới chứa plaintext nguồn CryptoStream.Read ghi ciphertext ra bộ
đệm xuất
Read Giải mật hóa Stream nằm dưới chứa ciphertext nguồn CryptoStream.Read ghi plaintext ra bộ đệm
xuất
Write Mật hóa CryptoStream.Write chỉ định plaintext cần mật hóa Stream nằm dưới nhận ciphertext
đã-được-mật-hóa
Write Giải mật hóa CryptoStream.Write chỉ định ciphertext cần giải mật hóa Stream nằm dưới nhận
plaintext đã-được-giải-mật-hóa
Lớp SymmetricEncryptionExample dưới đây trình bày cách sử dụng giải thuật Triple
DES để mật hóa một file và rồi giải mật hóa file đó Phương thức Main nhận tên của file
cần mật hóa làm đối số dòng lệnh Trước tiên, nó sẽ tạo khóa và vectơ khởi động; sau đó, gọi phương thức EncryptFile, kế tiếp là phương thức DecryptFile, và sinh ra hai file: file thứ nhất chứa phiên bản được-mật-hóa của file nguồn, file thứ hai chứa phiên bản đã-được-giải-mật-hóa của file đã-được-mật-hóa (giống file nguồn)
using System;
using System.IO;
using System.Security.Cryptography;
public class SymmetricEncryptionExample {
public static void Main(string[] args) {
// Tạo một giải thuật Triple DES mới để thu lấy khóa dùng cho
// ví dụ này Khóa này sẽ được dùng chung trong các phương thức
// EncryptFile và DecryptFile Bình thường, khóa được
// thỏa thuận giữa người gửi và người nhận, hoặc được gửi
// (bởi người gửi) cùng với file đã-được-mật-hóa
byte[] key;
byte[] iv;
Trang 7
using(SymmetricAlgorithm alg =
SymmetricAlgorithm.Create("3DES")){
key = alg.Key;
iv = alg.IV;
}
// Mật hóa file Tiền tố "encrypted" sẽ được thêm vào tên file // nguồn và được sử dụng làm tên của file đã-được-mật-hóa
EncryptFile(args[0], "encrypted"+args[0], (byte[])key.Clone(), (byte[])iv.Clone());
// Giải mật hóa file đã-được-mật-hóa Tiền tố "decrypted" sẽ được // thêm vào tên file gốc và được sử dụng làm tên của file
// đã-được-giải-mật-hóa
DecryptFile("encrypted"+args[0], "decrypted"+args[0], key, iv); }
// Phương thức dùng để mật hóa một file (bằng giải thuật Triple DES) // với key và iv cho trước
private static void EncryptFile(string srcFileName,
string destFileName, byte[] key, byte[] iv) {
// Tạo các stream để truy xuất file nguồn và file đích
Stream srcFile =
new FileStream(srcFileName, FileMode.Open, FileAccess.Read); Stream destFile =
new FileStream(destFileName, FileMode.Create,
FileAccess.Write);
// Tạo một giải thuật Triple DES mới để mật hóa file
using(SymmetricAlgorithm alg =
SymmetricAlgorithm.Create("3DES")){
// Cấu hình thuộc tính Key và IV của giải thuật
alg.Key = key;
alg.IV = iv;
// Tạo một CryptoStream để mật hóa nội dung của
Trang 8// Stream nguồn khi nó được đọc Gọi phương thức
// CreateEncryptor của SymmetricAlgorithm
// để nhận thể hiện ICryptoTransform và
// truyền nó cho CryptoStream
CryptoStream cryptoStream = new CryptoStream(srcFile, alg.CreateEncryptor(),
CryptoStreamMode.Read);
// Khai báo bộ đệm dùng để đọc dữ liệu từ file nguồn
// thông qua CryptoStream và ghi nó ra file đích
int bufferLength;
byte[] buffer = new byte[1024];
// Đọc file nguồn (từng khối 1024 byte) và ghi phiên bản
// đã-được-mật-hóa ra file đích
do {
bufferLength = cryptoStream.Read(buffer, 0, 1024);
destFile.Write(buffer, 0, bufferLength);
} while (bufferLength > 0);
// Đóng stream và xóa các dữ liệu bí mật
destFile.Flush();
Array.Clear(key,0,key.Length);
Array.Clear(iv,0,iv.Length);
cryptoStream.Clear();
cryptoStream.Close();
srcFile.Close();
destFile.Close();
}
}
// Phương thức dùng để giải mật hóa một file đã-được-mật-hóa bằng // giải thuật Triple DES với key và iv cho trước
private static void DecryptFile(string srcFileName,
string destFileName, byte[] key, byte[] iv) {
// Tạo các stream để truy xuất file nguồn và file đích
Stream srcFile =
new FileStream(srcFileName, FileMode.Open, FileAccess.Read);
Trang 9Stream destFile =
new FileStream(destFileName, FileMode.Create,
FileAccess.Write);
// Tạo một giải thuật Triple DES mới để giải mật hóa file
using(SymmetricAlgorithm alg =
SymmetricAlgorithm.Create("3DES")){
// Cấu hình thuộc tính Key và IV của giải thuật
alg.Key = key;
alg.IV = iv;
// Tạo một CryptoStream để giải mật hóa nội dung của dữ liệu // đã-được-mật-hóa khi nó được ghi Gọi phương thức
// CreateDecryptor của SymmetricAlgorithm để nhận thể hiện // ICryptoTransform và truyền nó cho CryptoStream
CryptoStream cryptoStream = new CryptoStream(destFile, alg.CreateDecryptor(),
CryptoStreamMode.Write);
// Khai báo bộ đệm dùng để đọc dữ liệu từ file đã-được-
// mật-hóa và ghi ra file đích thông qua CryptoStream
int bufferLength;
byte[] buffer = new byte[1024];
// Đọc file đã-được-mật-hóa (từng khối 1024 byte) và ghi // phiên bản đã-được-giải-mật-hóa ra file đích
do {
bufferLength = srcFile.Read(buffer, 0, 1024);
cryptoStream.Write(buffer, 0, bufferLength);
} while (bufferLength > 0);
// Đóng stream và xóa các dữ liệu bí mật
cryptoStream.FlushFinalBlock();
Array.Clear(key,0,key.Length);
Array.Clear(iv,0,iv.Length);
cryptoStream.Clear();
cryptoStream.Close();
srcFile.Close();
Trang 10destFile.Close(); }
}
}