1.1 Bảo đảm tính toàn vẹn dữ liệu bằng mã băm có khóa
V
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àiliệ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
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ượ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
// đượ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
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
ICryptoTransform này kết hợp với một đố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
bọ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àiliệ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.
Khó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.
Bả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;
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
// 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);
Stream 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();
destFile.Close();
}
}
}
. 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.
RC2CryptoServiceProvider
40, 48 56, 64, 72,
80, 88, 96, 104,
1 12, 120 , 128
Rijndael
RijndaelManaged 128 , 1 92, 25 6
Mặc dù bạn có thể tạo ra các thể