Làm cách nào để mã hóa byte bằng mô-đun TPM của máy?
CryptProtectData
Windows cung cấp một API (tương đối) đơn giản để mã hóa một blob bằng cách sử dụng CryptProtectData
API, mà chúng ta có thể bọc một hàm dễ sử dụng:
public Byte[] ProtectBytes(Byte[] plaintext)
{
//...
}
Các chi tiết ProtectBytes
ít quan trọng hơn ý tưởng mà bạn có thể sử dụng nó khá dễ dàng:
- đây là các byte tôi muốn được mã hóa bởi một khóa bí mật được giữ trong
System
- trả lại cho tôi blob được mã hóa
Blob trả về là một cấu trúc tài liệu không có giấy tờ , chứa mọi thứ cần thiết để giải mã và trả về dữ liệu gốc (thuật toán băm, thuật toán mật mã, muối, chữ ký HMAC, v.v.).
Để hoàn thiện, đây là cách triển khai mã giả mẫu ProtectBytes
sử dụng Crypt API
byte để bảo vệ:
public Byte[] ProtectBytes(Byte[] plaintext)
{
//Setup our n-byte plaintext blob
DATA_BLOB dataIn;
dataIn.cbData = plaintext.Length;
dataIn.pbData = Addr(plaintext[0]);
DATA_BLOB dataOut;
//dataOut = EncryptedFormOf(dataIn)
BOOL bRes = CryptProtectData(
dataIn,
null, //data description (optional PWideChar)
null, //optional entropy (PDATA_BLOB)
null, //reserved
null, //prompt struct
CRYPTPROTECT_UI_FORBIDDEN || CRYPTPROTECT_LOCAL_MACHINE,
ref dataOut);
if (!bRes) then
{
DWORD le = GetLastError();
throw new Win32Error(le, "Error calling CryptProtectData");
}
//Copy ciphertext from dataOut blob into an actual array
bytes[] result;
SetLength(result, dataOut.cbData);
CopyMemory(dataOut.pbData, Addr(result[0]), dataOut.cbData);
//When you have finished using the DATA_BLOB structure, free its pbData member by calling the LocalFree function
LocalFree(HANDLE(dataOut.pbData)); //LocalFree takes a handle, not a pointer. But that's what the SDK says.
}
Làm thế nào để làm điều tương tự với TPM?
Đoạn mã trên chỉ hữu ích để mã hóa dữ liệu cho máy cục bộ. Dữ liệu được mã hóa bằng cách sử dụng System
tài khoản làm trình tạo khóa (các chi tiết, tuy thú vị, nhưng không quan trọng ). Kết quả cuối cùng là tôi có thể mã hóa dữ liệu (ví dụ: khóa chính mã hóa ổ cứng) mà chỉ máy cục bộ mới có thể giải mã được.
Bây giờ là lúc để tiến thêm một bước nữa. Tôi muốn mã hóa một số dữ liệu (ví dụ: khóa chính mã hóa ổ cứng) mà chỉ có thể được giải mã bởi TPM cục bộ. Nói cách khác, tôi muốn thay thế Môi trường thực thi tin cậy Qualcomm ( TEE ) trong sơ đồ khối bên dưới cho Android, bằng TPM trong Windows:
Lưu ý : Tôi nhận thấy rằng TPM không thực hiện việc ký dữ liệu (hoặc nếu có, nó không đảm bảo rằng việc ký cùng một dữ liệu sẽ cho cùng một đầu ra nhị phân mỗi lần). Đó là lý do tại sao tôi sẵn sàng thay thế "ký RSA" bằng "mã hóa một đốm màu 256-bit bằng khóa liên kết phần cứng" .
Vậy mã ở đâu?
Vấn đề là lập trình TPM hoàn toàn không có tài liệu trên MSDN . Không có sẵn API để thực hiện bất kỳ hoạt động nào. Thay vào đó, bạn phải tìm cho mình một bản sao của Ngăn xếp phần mềm của Nhóm Máy tính Tin cậy (hay còn gọi là TSS) , hãy tìm ra các lệnh cần gửi đến TPM, với trọng tải, theo thứ tự nào và gọi hàm Tbsip_Submit_Command của Window để gửi lệnh trực tiếp:
TBS_RESULT Tbsip_Submit_Command(
_In_ TBS_HCONTEXT hContext,
_In_ TBS_COMMAND_LOCALITY Locality,
_In_ TBS_COMMAND_PRIORITY Priority,
_In_ const PCBYTE *pabCommand,
_In_ UINT32 cbCommand,
_Out_ PBYTE *pabResult,
_Inout_ UINT32 *pcbOutput
);
Windows không có API cấp cao hơn để thực hiện các hành động.
Nó tương đương với đạo đức của việc cố gắng tạo một tệp văn bản bằng cách đưa ra các lệnh SATA I / O vào ổ cứng của bạn .
Tại sao không chỉ sử dụng Quần tây
Trusted Computing Group (TCG) đã xác định API của riêng họ: TCB Software Stack (TSS) . Một số người đã tạo ra một bản triển khai API này và được gọi là TrouSerS . Một chàng trai sau đó đã chuyển dự án đó sang Windows .
Vấn đề với mã đó là nó không thể di động vào thế giới Windows. Ví dụ, bạn không thể sử dụng nó từ Delphi, bạn không thể sử dụng nó từ C #. Nó yêu cầu:
- OpenSSL
- pThread
Tôi chỉ muốn mã để mã hóa thứ gì đó bằng TPM của mình.
Trên CryptProtectData
không yêu cầu gì khác ngoài những gì trong cơ thể hàm.
Mã tương đương để mã hóa dữ liệu bằng TPM là gì? Như những người khác đã lưu ý, bạn có thể phải tham khảo ba hướng dẫn sử dụng TPM và tự xây dựng các đốm màu . Nó có thể liên quan đến TPM_seal
lệnh. Mặc dù tôi nghĩ tôi không muốn niêm phong dữ liệu, nhưng tôi nghĩ tôi muốn ràng buộc nó:
Ràng buộc - mã hóa dữ liệu bằng khóa liên kết TPM, một khóa RSA duy nhất có nguồn gốc từ khóa lưu trữ. Niêm phong - mã hóa dữ liệu theo cách tương tự như ràng buộc, nhưng ngoài ra còn chỉ định trạng thái mà TPM phải ở để dữ liệu được giải mã (chưa được niêm phong)
Tôi cố gắng đọc ba tập cần thiết để tìm 20 dòng mã tôi cần:
Nhưng tôi không có biết mình đang đọc gì. Nếu có bất kỳ loại hướng dẫn hoặc ví dụ nào, tôi có thể có một shot. Nhưng tôi hoàn toàn lạc lối.
Vì vậy, chúng tôi yêu cầu Stackoverflow
Theo cách tương tự, tôi có thể cung cấp:
Byte[] ProtectBytes_Crypt(Byte[] plaintext)
{
//...
CryptProtectData(...);
//...
}
ai đó có thể cung cấp tương đương tương ứng:
Byte[] ProtectBytes_TPM(Byte[] plaintext)
{
//...
Tbsip_Submit_Command(...);
Tbsip_Submit_Command(...);
Tbsip_Submit_Command(...);
//...snip...
Tbsip_Submit_Command(...);
//...
}
điều đó cũng làm điều tương tự, ngoại trừ một chìa khóa bị khóa System
LSA, bị khóa trong TPM?
Bắt đầu nghiên cứu
Tôi không biết chính xác ràng buộc nghĩa là gì. Nhưng nhìn vào TPM Main - Phần 3 Lệnh - Đặc tả Phiên bản 1.2, có đề cập đến ràng buộc :
10,3 TPM_UnBind
TPM_UnBind lấy khối dữ liệu là kết quả của lệnh Tspi_Data_Bind và giải mã nó để xuất cho Người dùng. Người gọi phải cho phép sử dụng khóa sẽ giải mã khối đến. TPM_UnBind hoạt động trên cơ sở từng khối và không có khái niệm về bất kỳ mối quan hệ nào giữa khối này với khối khác.
Điều khó hiểu là không có Tspi_Data_Bind
lệnh.
Nỗ lực Nghiên cứu
Thật là kinh hoàng khi không ai thèm ghi lại TPM hoặc hoạt động của nó. Cứ như thể họ đã dành tất cả thời gian để nghĩ ra thứ hay ho này để chơi cùng, nhưng không muốn đối mặt với bước đau đớn là biến nó có thể sử dụng được cho một thứ gì đó.
Bắt đầu với cuốn sách miễn phí (hiện tại) Hướng dẫn Thực hành về TPM 2.0: Sử dụng Mô-đun Nền tảng Tin cậy trong Kỷ nguyên Bảo mật Mới :
Chương 3 - Hướng dẫn nhanh về TPM 2.0
TPM có quyền truy cập vào khóa cá nhân tự tạo, vì vậy nó có thể mã hóa các khóa bằng khóa công khai và sau đó lưu trữ khối kết quả trên đĩa cứng. Bằng cách này, TPM có thể giữ cho số lượng khóa hầu như không giới hạn để sử dụng nhưng không lãng phí bộ nhớ trong có giá trị. Các phím được lưu trên đĩa cứng có thể bị xóa, nhưng chúng cũng có thể được sao lưu, điều này đối với các nhà thiết kế dường như là một sự đánh đổi có thể chấp nhận được.
Làm cách nào để mã hóa khóa bằng khóa công khai của TPM?
Chương 4 - Các ứng dụng hiện có sử dụng TPM
Các ứng dụng nên sử dụng TPM nhưng không
Trong vài năm qua, số lượng các ứng dụng dựa trên web đã tăng lên. Trong số đó có sao lưu và lưu trữ dựa trên web. Một số lượng lớn các công ty hiện cung cấp các dịch vụ như vậy, nhưng theo chúng tôi được biết, không có khách hàng nào của các dịch vụ này cho phép người dùng khóa chìa khóa của dịch vụ sao lưu vào TPM. Nếu điều này được thực hiện, chắc chắn sẽ rất tuyệt nếu bản thân khóa TPM được sao lưu bằng cách sao chép nó trên nhiều máy. Đây dường như là một cơ hội cho các nhà phát triển.
Làm thế nào để một nhà phát triển khóa một chìa khóa cho TPM?
Chương 9 - Heirarchies
TRƯỜNG HỢP SỬ DỤNG: LƯU TRỮ MẬT KHẨU ĐĂNG NHẬP
Một tệp mật khẩu điển hình lưu trữ các mật khẩu băm nhỏ. Việc xác minh bao gồm việc ướp muối và băm một mật khẩu đã cung cấp và so sánh nó với giá trị được lưu trữ. Bởi vì tính toán không bao gồm bí mật, nó có thể bị tấn công ngoại tuyến vào tệp mật khẩu.
Trường hợp sử dụng này sử dụng khóa HMAC do TPM tạo. Tệp mật khẩu lưu trữ một HMAC của mật khẩu muối. Xác minh bao gồm ướp muối và HMAC mật khẩu được cung cấp và so sánh nó với giá trị được lưu trữ. Vì kẻ tấn công ngoại tuyến không có khóa HMAC, kẻ tấn công không thể thực hiện một cuộc tấn công bằng cách thực hiện phép tính.
Điều này có thể hoạt động. Nếu TPM có khóa HMAC bí mật và chỉ TPM của tôi biết khóa HMAC, thì tôi có thể thay thế "Ký (hay còn gọi là mã hóa TPM bằng khóa riêng)" bằng "HMAC". Nhưng sau đó ở dòng tiếp theo, anh ấy đảo ngược hoàn toàn bản thân:
TPM2_Create, chỉ định một khóa HMAC
Đó không phải là bí mật TPM nếu tôi phải chỉ định khóa HMAC. Thực tế là khóa HMAC không phải là bí mật có ý nghĩa khi bạn nhận ra đây là chương về các tiện ích mật mã mà TPM cung cấp. Thay vì bạn phải tự viết SHA2, AES, HMAC hoặc RSA, bạn có thể sử dụng lại những gì TPM đã có sẵn.
Chương 10 - Phím
Là một thiết bị bảo mật, khả năng ứng dụng sử dụng khóa trong khi vẫn giữ chúng an toàn trong thiết bị phần cứng là điểm mạnh nhất của TPM. TPM có thể tạo và nhập các khóa được tạo bên ngoài. Nó hỗ trợ cả phím bất đối xứng và đối xứng.
Thông minh! Bạn làm nó như thế nào!?
Trình tạo khóa
Có thể cho rằng, sức mạnh lớn nhất của TPM là khả năng tạo khóa mật mã và bảo vệ bí mật của nó trong ranh giới phần cứng. Bộ tạo khóa dựa trên bộ tạo số ngẫu nhiên của TPM và không dựa vào các nguồn ngẫu nhiên bên ngoài. Do đó, nó loại bỏ các điểm yếu dựa trên phần mềm phần mềm yếu với nguồn entropy không đủ.
Liệu TPM có khả năng tạo ra các khóa mật mã và bảo vệ bí mật của nó trong vòng một ranh giới phần cứng? Là như vậy, làm thế nào?
Chương 12 - Thanh ghi cấu hình nền tảng
PCR để cho phép
SỬ DỤNG TRƯỜNG HỢP: ĐÓNG KHÓA KÍCH THÍCH ĐĨA CỨNG VÀO TIỂU BIỂU TÌNH
Các ứng dụng mã hóa toàn đĩa an toàn hơn nhiều nếu TPM bảo vệ khóa mã hóa hơn là nếu nó được lưu trữ trên cùng một đĩa, chỉ được bảo vệ bằng mật khẩu. Đầu tiên, phần cứng TPM có bảo vệ chống búa (xem Chương 8 để biết mô tả chi tiết về bảo vệ chống tấn công từ điển TPM), khiến một cuộc tấn công brute-force vào mật khẩu là không thực tế. Một khóa chỉ được bảo vệ bằng phần mềm sẽ dễ bị tấn công hơn nhiều so với mật khẩu yếu. Thứ hai, khóa phần mềm được lưu trữ trên đĩa dễ bị đánh cắp hơn nhiều. Lấy đĩa (hoặc một bản sao lưu của đĩa) và bạn sẽ có được chìa khóa. Khi TPM giữ chìa khóa, toàn bộ nền tảng, hoặc ít nhất là đĩa và bo mạch chủ, phải bị đánh cắp.
Việc niêm phong cho phép khóa được bảo vệ không chỉ bằng mật khẩu mà còn bằng chính sách. Một chính sách điển hình sẽ khóa khóa đối với các giá trị PCR (trạng thái phần mềm) hiện tại tại thời điểm niêm phong. Điều này giả định rằng trạng thái lúc khởi động đầu tiên không bị xâm phạm. Bất kỳ phần mềm độc hại được cài đặt sẵn nào có mặt ở lần khởi động đầu tiên sẽ được đo lường trong PCR và do đó khóa sẽ được niêm phong ở trạng thái phần mềm bị xâm phạm. Một doanh nghiệp kém tin tưởng hơn có thể có hình ảnh đĩa tiêu chuẩn và con dấu đối với PCR đại diện cho hình ảnh đó. Các giá trị PCR này sẽ được tính toán trước trên một nền tảng được cho là đáng tin cậy hơn. Một doanh nghiệp thậm chí còn phức tạp hơn sẽ sử dụng TPM2_PolicyAuthorize và cung cấp một số vé cho phép một tập hợp các giá trị PCR đáng tin cậy. Xem Chương 14 để biết mô tả chi tiết về ủy quyền chính sách và ứng dụng của nó để giải quyết vấn đề PCRbrittleness.
Mặc dù mật khẩu cũng có thể bảo vệ khóa, nhưng có một lợi ích bảo mật ngay cả khi không có mật khẩu khóa TPM. Kẻ tấn công có thể khởi động nền tảng mà không cần cung cấp mật khẩu TPMkey nhưng không thể đăng nhập nếu không có tên người dùng và mật khẩu hệ điều hành. OSsecurity bảo vệ dữ liệu. Kẻ tấn công có thể khởi động một hệ điều hành thay thế, chẳng hạn như từ đĩa DVD hoặc USB trực tiếp thay vì từ ổ cứng, để vượt qua bảo mật đăng nhập hệ điều hành. Tuy nhiên, cấu hình khởi động và phần mềm khác nhau này sẽ thay đổi giá trị PCR. Vì các PCR mới này không khớp với các giá trị được niêm phong, TPM sẽ không phát hành khóa giải mã và không thể giải mã ổ cứng.
Thông minh! Đây chính xác là trường hợp sử dụng mà tôi muốn. Đây cũng là trường hợp sử dụng mà Microsoft sử dụng TPM. Tôi phải làm nó như thế nào!?
Vì vậy, tôi đọc toàn bộ cuốn sách đó, và nó không cung cấp gì hữu ích. Khá ấn tượng vì nó có 375 trang. Bạn tự hỏi cuốn sách chứa đựng những gì - và nhìn lại nó, tôi không biết.
Vì vậy, chúng tôi từ bỏ hướng dẫn cuối cùng để lập trình TPM và thay vào đó chuyển sang một số tài liệu từ Microsoft:
Từ Bộ công cụ nhà cung cấp tiền điện tử nền tảng TPM của Microsoft . Nó đề cập chính xác những gì tôi muốn làm:
Khóa xác nhận hoặc EK
EK được thiết kế để cung cấp một mã nhận dạng mật mã đáng tin cậy cho nền tảng. Một doanh nghiệp có thể duy trì cơ sở dữ liệu về các Khóa xác nhận thuộc về TPM của tất cả các PC trong doanh nghiệp của họ hoặc bộ điều khiển kết cấu trung tâm dữ liệu có thể có cơ sở dữ liệu về TPM trong tất cả các cánh. Trên Windows, bạn có thể sử dụng nhà cung cấp NCrypt được mô tả trong phần “Nhà cung cấp tiền điện tử nền tảng trong Windows 8” để đọc phần công khai của EK.
Ở đâu đó bên trong TPM là một khóa riêng RSA. Chìa khóa đó được khóa trong đó - không bao giờ bị thế giới bên ngoài nhìn thấy. Tôi muốn TPM ký một cái gì đó bằng khóa riêng của nó (tức là mã hóa nó bằng khóa riêng của nó).
Vì vậy, tôi muốn hoạt động cơ bản nhất có thể tồn tại:
Mã hóa thứ gì đó bằng khóa riêng của bạn. Tôi thậm chí chưa (chưa) yêu cầu những thứ phức tạp hơn:
- "niêm phong" nó dựa trên trạng thái PCR
- tạo khóa và lưu trữ nó trong memroy dễ bay hơi hoặc không bay hơi
- tạo một khóa đối xứng và cố gắng tải nó vào TPM
Tôi đang yêu cầu hoạt động cơ bản nhất mà một TPM có thể thực hiện. Tại sao không thể nhận được bất kỳ thông tin về cách làm điều đó?
Tôi có thể lấy dữ liệu ngẫu nhiên
Tôi cho rằng tôi đã rất kinh ngạc khi nói rằng ký RSA là điều cơ bản nhất mà TPM có thể làm. Các nhất điều cơ bản TPM có thể được yêu cầu phải làm là cho tôi byte ngẫu nhiên. Điều đó tôi đã tìm ra cách làm:
public Byte[] GetRandomBytesTPM(int desiredBytes)
{
//The maximum random number size is limited to 4,096 bytes per call
Byte[] result = new Byte[desiredBytes];
BCRYPT_ALG_HANDLE hAlgorithm;
BCryptOpenAlgorithmProvider(
out hAlgorithm,
BCRYPT_RNG_ALGORITHM, //AlgorithmID: "RNG"
MS_PLATFORM_CRYPTO_PROVIDER, //Implementation: "Microsoft Platform Crypto Provider" i.e. the TPM
0 //Flags
);
try
{
BCryptGenRandom(hAlgorithm, @result[0], desiredBytes, 0);
}
finally
{
BCryptCloseAlgorithmProvider(hAlgorithm);
}
return result;
}
The Fancy Thing
Tôi nhận thấy số lượng người sử dụng TPM rất thấp. Đó là lý do tại sao không ai trên Stackoverflow có câu trả lời. Vì vậy, tôi không thể thực sự quá tham lam trong việc tìm ra giải pháp cho vấn đề chung của mình. Nhưng điều tôi thực sự muốn làm là "niêm phong" một số dữ liệu:
- trình bày TPM một số dữ liệu (ví dụ: 32 byte vật liệu chính)
- có TPM mã hóa dữ liệu, trả về một số cấu trúc khối mờ
- sau đó yêu cầu TPM giải mã đốm màu
- việc giải mã sẽ chỉ hoạt động nếu các thanh ghi PCR của TPM giống như khi chúng được mã hóa.
Nói cách khác:
Byte[] ProtectBytes_TPM(Byte[] plaintext, Boolean sealToPcr)
{
//...
}
Byte[] UnprotectBytes_TPM(Byte[] protectedBlob)
{
//...
}
Cryptography Next Gen (Cng, hay còn gọi là BCrypt) hỗ trợ TPM
API mật mã ban đầu trong Windows được gọi là API Crypto.
Bắt đầu từ Windows Vista, API Crypto đã được thay thế bằng Cryptography API: Next Generation (tên gọi nội bộ là BestCrypt , viết tắt là BCrypt , đừng nhầm lẫn với thuật toán băm mật khẩu ).
Windows đi kèm với hai nhà cung cấp BCrypt :
- Microsoft Primitive Provider (
MS_PRIMITIVE_PROVIDER
) mặc định : Triển khai phần mềm mặc định của tất cả các phần mềm nguyên thủy (băm, mã hóa đối xứng, chữ ký số, v.v.) - Nhà cung cấp tiền điện tử nền tảng Microsoft (
MS_PLATFORM_CRYPTO_PROVIDER
): Nhà cung cấp cung cấp quyền truy cập TPM
Nhà cung cấp tiền điện tử nền tảng không được ghi lại trên MSDN, nhưng có tài liệu từ trang web Nghiên cứu của Microsoft năm 2012:
Bộ công cụ nhà cung cấp tiền điện tử nền tảng TPM
Bộ công cụ và nhà cung cấp tiền điện tử nền tảng TPM chứa mã mẫu, tiện ích và tài liệu để sử dụng chức năng liên quan đến TPM trong Windows 8. Các hệ thống con được mô tả bao gồm nhà cung cấp tiền điện tử nền tảng Crypto-Next-Gen (CNG) được TPM hậu thuẫn và cách nhà cung cấp dịch vụ chứng thực có thể sử dụng các tính năng mới của Windows. Cả hai hệ thống dựa trên TPM1.2 và TPM2.0 đều được hỗ trợ.
Có vẻ như ý định của Microsoft là giới thiệu chức năng tiền điện tử TPM với Nhà cung cấp tiền điện tử nền tảng Microsoft của Cryptography NG API .
Mã hóa khóa công khai bằng Microsoft BCrypt
Cho rằng:
- tôi muốn thực hiện mã hóa không đối xứng RSA (sử dụng TPM)
- Microsoft BestCrypt hỗ trợ mã hóa không đối xứng RSA
- Microsoft BestCrypt có Nhà cung cấp TPM
một con đường phía trước có thể là tìm ra cách thực hiện ký kỹ thuật số bằng cách sử dụng Microsoft Cryptography Next Gen API .
Bước tiếp theo của tôi sẽ là tìm ra mã để thực hiện mã hóa trong BCrypt, với khóa công khai RSA, sử dụng trình cung cấp tiêu chuẩn ( MS_PRIMITIVE_PROVIDER
). Ví dụ:
modulus
: 0xDC 67 FA F4 9E F2 72 1D 45 2C B4 80 79 06 A0 94 27 50 8209 DD 67 CE 57 B8 6C 4A 4F 40 9F D2 D1 69 FB 995D 85 0C 07 A1 F9 47 1B 56 16 6E F6 7F B9 CF 2A 58 36 37 99 29 AA 4F A8 12 E8 4F C7 82 2B 9D 72 2A 9C DE 6F C2 EE 12 6D CF F0 F2 B8 C4 DD 7C 5C 1A C8 17 51 A9 AC DF 08 22 04 9D 2B D7 F9 4B 09 DE 9A EB 5C 51 1A D8 F8 F9 56 9E F8 FB 37 9B 3F D3 74 65 24 0D FF 34 75 57 A4 F5 BF 55publicExponent
: 65537
Với mã đó đang hoạt động, tôi có thể chuyển sang sử dụng Nhà cung cấp TPM ( MS_PLATFORM_CRYPTO_PROVIDER
).
22/2/2016: Và với việc Apple bắt buộc phải giúp giải mã dữ liệu người dùng, ngày càng có nhiều quan tâm đến việc làm thế nào để TPM thực hiện nhiệm vụ đơn giản nhất mà nó được phát minh ra - mã hóa thứ gì đó.
Nó gần tương đương với việc mọi người sở hữu một chiếc ô tô, nhưng không ai biết cách khởi động một chiếc ô tô. Nó có thể làm những điều thực sự hữu ích và thú vị, nếu chúng ta có thể vượt qua Bước 1 .