SecureString có thực tế trong ứng dụng C # không?


224

Vui lòng sửa cho tôi nếu các giả định của tôi sai ở đây, nhưng hãy để tôi giải thích lý do tại sao tôi hỏi.

Lấy từ MSDN, a SecureString:

Đại diện cho văn bản cần được giữ bí mật. Văn bản được mã hóa để bảo mật khi được sử dụng và bị xóa khỏi bộ nhớ máy tính khi không còn cần thiết.

Tôi hiểu điều này, hoàn toàn có ý nghĩa khi lưu trữ mật khẩu hoặc thông tin riêng tư khác trong SecureStringmột System.String, bởi vì bạn có thể kiểm soát cách thức và thời điểm nó thực sự được lưu trữ trong bộ nhớ, bởi vì System.String:

là bất biến và, khi không còn cần thiết, không thể được lập trình theo lịch trình để thu gom rác; nghĩa là, thể hiện chỉ đọc sau khi nó được tạo và không thể dự đoán khi nào thể hiện sẽ bị xóa khỏi bộ nhớ máy tính. Do đó, nếu một đối tượng Chuỗi chứa thông tin nhạy cảm như mật khẩu, số thẻ tín dụng hoặc dữ liệu cá nhân, có nguy cơ thông tin có thể bị tiết lộ sau khi sử dụng vì ứng dụng của bạn không thể xóa dữ liệu khỏi bộ nhớ máy tính.

Tuy nhiên, trong trường hợp ứng dụng GUI (ví dụ: máy khách ssh), ứng dụng SecureString phải được xây dựng từ a System.String . Tất cả các điều khiển văn bản sử dụng một chuỗi làm kiểu dữ liệu cơ bản của nó .

Vì vậy, điều này có nghĩa là mỗi khi người dùng nhấn một phím, chuỗi cũ đã bị loại bỏ và một chuỗi mới được xây dựng để thể hiện giá trị bên trong hộp văn bản là gì, ngay cả khi sử dụng mặt nạ mật khẩu. Và chúng ta không thể kiểm soát khi nào hoặc nếu bất kỳ giá trị nào trong số đó bị loại khỏi bộ nhớ .

Bây giờ là lúc để đăng nhập vào máy chủ. Đoán xem cái gì? Bạn cần truyền một chuỗi qua kết nối để xác thực . Vì vậy, hãy chuyển đổi chúng ta SecureStringthành một System.String.... và bây giờ chúng ta có một chuỗi trên heap mà không có cách nào để buộc nó đi qua bộ sưu tập rác (hoặc ghi 0 'vào bộ đệm của nó).

Quan điểm của tôi là : không có vấn đề gì bạn làm, ở đâu đó dọc theo dòng, đó SecureStringsẽ được chuyển đổi thành một System.String, có nghĩa là nó sẽ ít nhất tồn tại trên đống tại một số điểm (không có bất kỳ đảm bảo thu gom rác thải).

Quan điểm của tôi không phải là : liệu có cách nào để tránh việc gửi một chuỗi đến kết nối ssh hay phá vỡ việc có một điều khiển lưu trữ một chuỗi (tạo một điều khiển tùy chỉnh). Đối với câu hỏi này, bạn có thể thay thế "kết nối ssh" bằng "hình thức đăng nhập", "hình thức đăng ký", "hình thức thanh toán", "hình thức thực phẩm bạn sẽ cho ăn con chó con của bạn nhưng không phải con bạn", Vân vân.

  • Vì vậy, tại thời điểm nào việc sử dụng SecureStringthực sự trở nên thiết thực?
  • Có bao giờ giá trị thời gian phát triển thêm để xóa bỏ hoàn toàn việc sử dụng một System.Stringđối tượng?
  • Là toàn bộ mục SecureStringđích đơn giản là giảm lượng thời gian a System.Stringtrên heap (giảm nguy cơ chuyển sang tệp hoán đổi vật lý)?
  • Nếu kẻ tấn công đã có phương tiện để kiểm tra heap, thì rất có thể anh ta (A) đã có phương tiện để đọc tổ hợp phím, hoặc (B) đã có máy vật lý ... Vì vậy, việc sử dụng sẽ SecureStringngăn anh ta đến Dù sao dữ liệu?
  • Đây có phải chỉ là "bảo mật thông qua che khuất"?

Xin lỗi nếu tôi đặt câu hỏi quá dày, sự tò mò chỉ khiến tôi tốt hơn. Hãy trả lời bất kỳ hoặc tất cả các câu hỏi của tôi (hoặc cho tôi biết rằng các giả định của tôi là hoàn toàn sai). :)


25
Hãy nhớ rằng đó SecureStringkhông thực sự là một chuỗi an toàn. Đó chỉ là một cách để giảm cửa sổ thời gian trong đó ai đó có thể kiểm tra bộ nhớ của bạn và lấy thành công dữ liệu nhạy cảm. Đây không phải là chống đạn và nó không có ý định. Nhưng những điểm bạn đang nâng lên rất hợp lệ. Liên quan: stackoverflow.com/questions/14449579/ từ
Theodoros Chatzigiannakis

1
@TheodorosChatzigiannakis, yeah, đó là khá nhiều những gì tôi nghĩ. Tôi đã dành cả ngày hôm nay để suy nghĩ về việc cố gắng tìm ra một cách an toàn để lưu trữ mật khẩu cho tuổi thọ của ứng dụng, và nó chỉ khiến tôi tự hỏi, nó có đáng không?
Steven Jeffries

1
Có lẽ không đáng. Nhưng sau đó, một lần nữa, bạn có thể sẽ bảo vệ chống lại một kịch bản cực đoan. Cực kỳ không theo nghĩa là không ai có thể có được quyền truy cập vào máy tính của bạn, nhưng theo nghĩa là nếu ai đó có được quyền truy cập này, máy tính sẽ bị xem xét (cho tất cả ý định và mục đích) và tôi không ' Tôi nghĩ rằng không có ngôn ngữ hoặc bất kỳ kỹ thuật nào bạn có thể sử dụng để bảo vệ chống lại điều này hoàn toàn.
Theodoros Chatzigiannakis

8
Nó bảo vệ chống lại mật khẩu hiển thị trong nhiều năm , rất lâu sau khi mọi người ngừng nghĩ rằng có thể có vấn đề. Trên một ổ cứng bị loại bỏ, được lưu trong tệp hoán trang. Chà bỏ bộ nhớ để tỷ lệ cược cho điều này là rất quan trọng, không thể làm điều đó với System.String vì nó là bất biến. Nó đòi hỏi cơ sở hạ tầng mà ít chương trình ngày nay có quyền truy cập, xen kẽ với mã gốc để các phương thức Marshal.SecureStringXxx () hữu ích đang trở nên hiếm. Hoặc một cách hay để nhắc người dùng về nó :)
Hans Passant

1
SecureString là một kỹ thuật bảo mật chuyên sâu. Sự đánh đổi giữa chi phí phát triển và lợi ích bảo mật không thuận lợi trong hầu hết các tình huống. Có rất ít trường hợp sử dụng tốt cho nó.
usr

Câu trả lời:


237

Có thực sự sử dụng rất thực tế SecureString.

Bạn có biết bao nhiêu lần tôi đã xem những kịch bản như vậy? (câu trả lời là: nhiều!):

  • Một mật khẩu xuất hiện trong một tệp nhật ký vô tình.
  • Mật khẩu đang được hiển thị ở một nơi nào đó - một khi GUI đã hiển thị một dòng lệnh của ứng dụng đang được chạy và dòng lệnh bao gồm mật khẩu. Rất tiếc .
  • Sử dụng hồ sơ bộ nhớ để hồ sơ phần mềm với đồng nghiệp của bạn. Đồng nghiệp nhìn thấy mật khẩu của bạn trong bộ nhớ. Nghe có vẻ không thật? Không có gì.
  • Tôi đã từng sử dụng RedGatephần mềm có thể nắm bắt "giá trị" của các biến cục bộ trong trường hợp ngoại lệ, rất hữu ích. Mặc dù vậy, tôi có thể tưởng tượng rằng nó sẽ vô tình ghi lại "mật khẩu chuỗi".
  • Một bãi chứa sự cố bao gồm mật khẩu chuỗi.

Bạn có biết làm thế nào để tránh tất cả những vấn đề này? SecureString. Nó thường đảm bảo bạn không mắc phải những sai lầm ngớ ngẩn như vậy. Làm thế nào để nó tránh nó? Bằng cách đảm bảo rằng mật khẩu được mã hóa trong bộ nhớ không được quản lý và giá trị thực chỉ có thể được truy cập khi bạn chắc chắn 90% những gì bạn đang làm.

Theo nghĩa này, SecureStringhoạt động khá dễ dàng:

1) Mọi thứ đều được mã hóa

2) Cuộc gọi của người dùng AppendChar

3) Giải mã mọi thứ trong BỘ NHỚ KHÔNG GIỚI HẠN và thêm ký tự

4) Mã hóa mọi thứ một lần nữa trong BỘ NHỚ.

Nếu người dùng có quyền truy cập vào máy tính của bạn thì sao? Một virus có thể có quyền truy cập vào tất cả SecureStrings? Đúng. Tất cả những gì bạn cần làm là tự móc vào RtlEncryptMemorykhi bộ nhớ đang được giải mã, bạn sẽ nhận được vị trí của địa chỉ bộ nhớ không được mã hóa và đọc nó ra. Voila! Trên thực tế, bạn có thể tạo ra một loại virus sẽ liên tục quét để sử dụng SecureStringvà ghi nhật ký tất cả các hoạt động với nó. Tôi không nói rằng nó sẽ là một nhiệm vụ dễ dàng, nhưng nó có thể được thực hiện. Như bạn có thể thấy, "sức mạnh" SecureStringhoàn toàn biến mất một khi có người dùng / virus trong hệ thống của bạn.

Bạn có một vài điểm trong bài viết của bạn. Chắc chắn, nếu bạn sử dụng một số điều khiển UI chứa "mật khẩu chuỗi" bên trong, thì việc sử dụng thực tế SecureStringkhông hữu ích. Mặc dù vậy, nó vẫn có thể bảo vệ chống lại sự ngu ngốc mà tôi đã liệt kê ở trên.

Ngoài ra, như những người khác đã lưu ý, WPF hỗ trợ PasswordBox sử dụng SecureStringnội bộ thông qua thuộc tính SecurePassword của nó .

Điểm mấu chốt là; nếu bạn có dữ liệu nhạy cảm (mật khẩu, thẻ tín dụng, ..), hãy sử dụng SecureString. Đây là những gì C # Framework đang theo dõi. Ví dụ, NetworkCredentiallớp lưu mật khẩu như SecureString. Nếu bạn nhìn vào điều này , bạn có thể thấy hơn 80 công dụng khác nhau trong .NET framework SecureString.

Có nhiều trường hợp khi bạn phải chuyển đổi SecureStringthành chuỗi, vì một số API mong đợi nó.

Vấn đề thông thường là:

  1. API là CHUNG. Nó không biết rằng có một dữ liệu nhạy cảm.
  2. API biết rằng nó xử lý dữ liệu nhạy cảm và sử dụng "chuỗi" - đó chỉ là thiết kế tồi.

Bạn nêu lên điểm tốt: điều gì xảy ra khi SecureStringđược chuyển đổi thành string? Điều này chỉ có thể xảy ra vì điểm đầu tiên. Ví dụ: API không biết rằng đó là dữ liệu nhạy cảm. Cá nhân tôi đã không thấy điều đó xảy ra. Lấy chuỗi ra khỏi SecureString không đơn giản.

Nó không đơn giản vì một lý do đơn giản ; nó không bao giờ có ý định cho phép người dùng chuyển đổi SecureString thành chuỗi, như bạn đã nói: GC sẽ khởi động. Nếu bạn thấy mình làm điều đó, bạn cần lùi lại và tự hỏi: Tại sao tôi thậm chí làm điều này, hay tôi thực sự cần này, tại sao?

Có một trường hợp thú vị tôi đã thấy. Cụ thể, chức năng WinApi LogonUser lấy LPTSTR làm mật khẩu, có nghĩa là bạn cần gọi SecureStringToGlobalAllocUnicode. Điều đó về cơ bản cung cấp cho bạn mật khẩu không được mã hóa sống trong bộ nhớ không được quản lý. Bạn cần loại bỏ điều đó ngay khi bạn hoàn thành:

// Marshal the SecureString to unmanaged memory.
IntPtr rawPassword = Marshal.SecureStringToGlobalAllocUnicode(password);
try
{
   //...snip...
}
finally 
{
   // Zero-out and free the unmanaged string reference.
   Marshal.ZeroFreeGlobalAllocUnicode(rawPassword);
}

Bạn luôn có thể mở rộng SecureStringlớp bằng một phương thức mở rộng, chẳng hạn như ToEncryptedString(__SERVER__PUBLIC_KEY), cung cấp cho bạn một stringphiên bản SecureStringđược mã hóa bằng khóa chung của máy chủ. Chỉ có máy chủ mới có thể giải mã nó. Vấn đề đã được giải quyết: Bộ sưu tập rác sẽ không bao giờ nhìn thấy chuỗi "gốc", vì bạn không bao giờ để lộ nó trong bộ nhớ được quản lý. Đây chính xác là những gì đang được thực hiện trong PSRemotingCryptoHelper( EncryptSecureStringCore(SecureString secureString)).

Và như một thứ rất gần như liên quan: Mono SecureString hoàn toàn không mã hóa . Việc thực hiện đã được bình luận vì .. chờ đợi cho nó .. "Nó bằng cách nào đó gây ra sự cố thử nghiệm nunit" , điều này dẫn đến điểm cuối cùng của tôi:

SecureStringkhông được hỗ trợ ở mọi nơi Nếu nền tảng / kiến ​​trúc không hỗ trợ SecureString, bạn sẽ có một ngoại lệ. Có một danh sách các nền tảng được hỗ trợ trong tài liệu.


Tôi nghĩ SecureStringsẽ thực tế hơn rất nhiều nếu có một cơ chế truy cập các ký tự riêng lẻ. Hiệu quả cho một cơ chế như vậy có thể được tăng cường bằng cách có một kiểu cấu trúcSecureStringTempBuffer mà không có bất kỳ thành viên nào, có thể được chuyển qua phương refthức SecureString"đọc một ký tự" [nếu mã hóa / giải mã được xử lý trong các khối 8 ký tự, cấu trúc như vậy có thể chứa dữ liệu cho 8 ký tự và vị trí của ký tự đầu tiên trong số các ký tự đó trong SecureString].
supercat

2
Tôi gắn cờ này trong mọi kiểm toán mã nguồn mà tôi thực hiện. Nó cũng nên được lặp đi lặp lại rằng nếu bạn sử dụngSecureString nó cần phải được sử dụng tất cả các cách thông qua ngăn xếp.
Casey

1
Tôi đã triển khai phần mở rộng .ToEncryptedString () nhưng bằng cách sử dụng chứng chỉ. Bạn có phiền khi xem và cho tôi biết nếu tôi làm điều này sai không. Tôi đang hy vọng engouhg an toàn của nó gist.github.com/sturlath/99cfbfff26e0ebd12ea73316d0843330
Sturla

@Sturla - EngineSettingsPhương thức mở rộng của bạn là gì?
InteXX


15

Một vài vấn đề trong các giả định của bạn.

Trước hết, lớp SecureString không có hàm tạo Chuỗi. Để tạo một đối tượng, bạn phân bổ một đối tượng và sau đó nối các ký tự.

Trong trường hợp GUI hoặc bàn điều khiển, bạn có thể dễ dàng chuyển từng phím được nhấn vào một chuỗi an toàn.

Lớp được thiết kế theo cách mà bạn không thể, do nhầm lẫn, truy cập vào giá trị được lưu trữ. Điều này có nghĩa là bạn không thể lấy stringmật khẩu trực tiếp từ nó.

Vì vậy, để sử dụng nó, ví dụ, để xác thực qua web, bạn sẽ phải sử dụng các lớp thích hợp cũng an toàn.

Trong .NET framework, bạn có một vài lớp có thể sử dụng SecureString

  • Kiểm soát PasswordBox của WPF giữ mật khẩu dưới dạng SecureString trong nội bộ.
  • Thuộc tính Mật khẩu của System.Diagnostics.ProcessInfo là SecureString.
  • Hàm tạo cho X509Certert2 lấy SecureString cho mật khẩu.

(hơn)

Để kết luận, lớp SecureString có thể hữu ích, nhưng đòi hỏi sự chú ý nhiều hơn từ nhà phát triển.

Tất cả điều này, với các ví dụ, được mô tả kỹ trong tài liệu về SecureString của MSDN


10
Tôi hoàn toàn không hiểu đoạn thứ ba đến cuối câu trả lời của bạn. Làm thế nào bạn có thể chuyển một SecureStringmạng qua mà không nối tiếp nó vào một string? Tôi nghĩ rằng quan điểm của OP là sẽ đến lúc bạn muốn thực sự sử dụng giá trị được giữ an toàn trong một SecureString, điều đó có nghĩa là bạn phải đưa nó ra khỏi đó, và nó không còn an toàn nữa. Trong tất cả Thư viện lớp khung, hầu như không có phương thức nào chấp nhận SecureStringđầu vào trực tiếp (theo như tôi có thể thấy), vậy thì điểm nào giữ một giá trị bên trong một SecureStringở vị trí đầu tiên?
stakx - không còn đóng góp

@ DamianLeszczyński-Vash, tôi biết rằng SecureString không có hàm tạo chuỗi, nhưng quan điểm của tôi là bạn (A) tạo SecureString và nối từng char từ một chuỗi vào nó, hoặc (B) tại một số điểm cần sử dụng giá trị được lưu trữ trong SecureString dưới dạng một chuỗi để chuyển nó tới một số API. Điều đó đang được nói, bạn có đưa ra một số điểm tốt, và tôi có thể thấy làm thế nào trong một số trường hợp rất cụ thể nó có thể hữu ích.
Steven Jeffries

1
@stakx Bạn sẽ gửi nó qua mạng bằng cách nhận char[]và gửi từng cái charqua một ổ cắm - không tạo ra các đối tượng có thể thu gom rác trong quy trình.
dùng253751

Char [] có thể được thu thập và có thể thay đổi, vì vậy nó có thể được ghi đè.
Peter Ritchie

Tất nhiên, cũng dễ quên ghi đè lên mảng đó. Dù bằng cách nào, bất kỳ API nào bạn chuyển ký tự cũng có thể tạo một bản sao.
cHao

10

SecureString hữu ích nếu:

  • Bạn xây dựng ký tự theo từng ký tự (ví dụ: từ đầu vào bảng điều khiển) hoặc lấy nó từ API không được quản lý

  • Bạn sử dụng nó bằng cách chuyển nó tới API không được quản lý (SecureStringToBSTR).

Nếu bạn từng chuyển đổi nó thành một chuỗi được quản lý, bạn đã đánh bại mục đích của nó.

CẬP NHẬT để phản hồi bình luận

... hoặc BSTR như bạn đề cập, dường như không an toàn hơn

Sau khi được chuyển đổi thành BSTR, thành phần không được quản lý tiêu thụ BSTR có thể làm giảm bộ nhớ. Bộ nhớ không được quản lý an toàn hơn theo nghĩa là nó có thể được thiết lập lại theo cách này.

Tuy nhiên, có rất ít API trong .NET Framework hỗ trợ SecureString, vì vậy bạn có quyền nói rằng nó có giá trị rất hạn chế ngày nay.

Trường hợp sử dụng chính mà tôi sẽ thấy là trong một ứng dụng khách yêu cầu người dùng nhập mã hoặc mật khẩu có độ nhạy cao. Đầu vào của người dùng có thể được sử dụng theo từng ký tự để xây dựng SecureString, sau đó điều này có thể được chuyển đến một API không được quản lý, điều này thay đổi BSTR mà nó nhận được sau khi sử dụng. Bất kỳ kết xuất bộ nhớ tiếp theo sẽ không chứa chuỗi nhạy cảm.

Trong một ứng dụng máy chủ, thật khó để thấy nó sẽ hữu ích ở đâu.

CẬP NHẬT 2

Một ví dụ về API .NET chấp nhận SecureString là hàm tạo này cho lớp X509Certert . Nếu bạn chơi trò chơi với ILSpy hoặc tương tự, bạn sẽ thấy SecureString được chuyển đổi nội bộ thành bộ đệm không được quản lý ( Marshal.SecureStringToGlobalAllocUnicode), sau đó bị xóa khi kết thúc bằng ( Marshal.ZeroFreeGlobalAllocUnicode).


1
+1, nhưng bạn sẽ không luôn luôn "đánh bại mục đích của nó" chứ? Vì hầu như không có phương thức nào trong Thư viện lớp khung chấp nhận SecureStringlàm đầu vào, nên sử dụng chúng là gì? Bạn sẽ luôn phải chuyển đổi trở lại stringsớm hay muộn (hoặc BSTRnhư bạn đề cập, điều này dường như không an toàn hơn). Vậy tại sao phải bận tâm với SecureStringtất cả?
stakx - không còn đóng góp

5

Microsoft không khuyến nghị sử dụng SecureString cho các mã mới hơn.

Từ tài liệu của Lớp SecureString :

Quan trọng

Chúng tôi không khuyên bạn nên sử dụng SecureStringlớp học để phát triển mới. Để biết thêm thông tin, xem SecureStringkhông nên được sử dụng

Mà khuyến cáo:

Đừng dùng SecureString mã mới. Khi chuyển mã sang .NET Core, hãy xem xét rằng nội dung của mảng không được mã hóa trong bộ nhớ.

Cách tiếp cận chung của việc xử lý thông tin đăng nhập là tránh chúng và thay vào đó dựa vào các phương tiện khác để xác thực, chẳng hạn như chứng chỉ hoặc xác thực Windows. trên GitHub.


4

Như bạn đã xác định chính xác, SecureStringcung cấp một lợi thế cụ thể hơn string: tẩy xóa xác định. Có hai vấn đề với thực tế này:

  1. Như những người khác đã đề cập và như bạn đã nhận thấy, điều này không đủ. Bạn phải đảm bảo rằng mọi bước của quy trình (bao gồm truy xuất đầu vào, xây dựng chuỗi, sử dụng, xóa, vận chuyển, v.v.) xảy ra mà không làm mất mục đích sử dụng SecureString. Điều này có nghĩa là bạn phải cẩn thận để không bao giờ tạo một stringbộ đệm bất biến được quản lý bởi GC hoặc bất kỳ bộ đệm nào khác sẽ lưu trữ thông tin nhạy cảm (hoặc bạn cũng sẽ phải theo dõi điều đó ). Trong thực tế, điều này không phải lúc nào cũng dễ dàng đạt được, bởi vì nhiều API chỉ cung cấp một cách để làm việc stringchứ không phảiSecureString . Và ngay cả khi bạn quản lý mọi thứ đúng ...
  2. SecureStringbảo vệ chống lại các loại tấn công rất cụ thể (và đối với một số trong số chúng, nó thậm chí không đáng tin cậy). Ví dụ, SecureStringcho phép bạn thu nhỏ cửa sổ thời gian trong đó kẻ tấn công có thể xóa bộ nhớ của quá trình của bạn và trích xuất thành công thông tin nhạy cảm (một lần nữa, như bạn đã chỉ ra một cách chính xác), nhưng hy vọng rằng cửa sổ quá nhỏ để kẻ tấn công chụp ảnh bộ nhớ của bạn không được coi là bảo mật.

Vậy, khi nào bạn nên sử dụng nó? Chỉ khi bạn làm việc với thứ gì đó có thể cho phép bạn làm việc với SecureStringtất cả các nhu cầu của bạn và thậm chí sau đó bạn vẫn nên lưu ý rằng điều này chỉ an toàn trong những trường hợp cụ thể.


2
Bạn đang cho rằng kẻ tấn công có thể chụp nhanh bộ nhớ của một tiến trình bất cứ lúc nào. Đó không hẳn là trường hợp. Hãy xem xét một bãi đổ vỡ. Nếu sự cố xảy ra sau khi ứng dụng được thực hiện với dữ liệu nhạy cảm, kết xuất sự cố không được chứa dữ liệu nhạy cảm.

4

Dưới đây văn bản được sao chép từ máy phân tích mã tĩnh HP Fortify

Trừu tượng: Phương thức PassString () trong PassGenerator.cs lưu trữ dữ liệu nhạy cảm theo cách không an toàn (nghĩa là trong chuỗi), giúp có thể trích xuất dữ liệu thông qua việc kiểm tra heap.

Giải trình: Dữ liệu nhạy cảm (như mật khẩu, số an sinh xã hội, số thẻ tín dụng, v.v.) được lưu trữ trong bộ nhớ có thể bị rò rỉ nếu nó được lưu trữ trong một đối tượng Chuỗi được quản lý. Các đối tượng chuỗi không được ghim, vì vậy trình thu gom rác có thể định vị lại các đối tượng này theo ý muốn và để lại một vài bản sao trong bộ nhớ. Các đối tượng này không được mã hóa theo mặc định, vì vậy bất kỳ ai có thể đọc bộ nhớ của quá trình sẽ có thể xem nội dung. Hơn nữa, nếu bộ nhớ của quá trình được hoán đổi vào đĩa, nội dung không được mã hóa của chuỗi sẽ được ghi vào một tệp hoán đổi. Cuối cùng, do các đối tượng Chuỗi là bất biến, nên việc loại bỏ giá trị của Chuỗi khỏi bộ nhớ chỉ có thể được thực hiện bởi trình thu gom rác CLR. Bộ thu gom rác không bắt buộc phải chạy trừ khi CLR thiếu bộ nhớ, do đó không có gì đảm bảo khi nào việc thu gom rác sẽ diễn ra.

Khuyến nghị: Thay vì lưu trữ dữ liệu nhạy cảm trong các đối tượng như Chuỗi, hãy lưu trữ chúng trong đối tượng SecureString. Mỗi đối tượng lưu trữ nội dung của nó ở định dạng được mã hóa trong bộ nhớ mọi lúc.


3

Tôi muốn giải quyết điểm này:

Nếu kẻ tấn công đã có phương tiện để kiểm tra heap, thì rất có thể chúng (A) đã có phương tiện để đọc tổ hợp phím, hoặc (B) đã có máy thực sự ... Vì vậy, việc sử dụng sẽ SecureStringngăn không cho chúng đến Dù sao dữ liệu?

Kẻ tấn công có thể không có quyền truy cập đầy đủ vào máy tính và ứng dụng nhưng có thể có phương tiện để truy cập vào một số phần của bộ nhớ của quá trình. Nó thường được gây ra bởi các lỗi như tràn bộ đệm khi đầu vào được xây dựng đặc biệt có thể khiến ứng dụng lộ hoặc ghi đè lên một phần của bộ nhớ.

Bộ nhớ bị rò rỉ HeartBleed

Lấy Heartbleed làm ví dụ. Các yêu cầu được xây dựng đặc biệt có thể khiến mã hiển thị các phần ngẫu nhiên của bộ nhớ của quá trình cho kẻ tấn công. Kẻ tấn công có thể trích xuất chứng chỉ SSL từ bộ nhớ, nhưng điều duy nhất anh ta cần chỉ là sử dụng một yêu cầu không đúng định dạng.

Trong thế giới của mã được quản lý, lỗi tràn bộ đệm trở thành một vấn đề ít thường xuyên hơn. Và trong trường hợp WinForms, dữ liệu đã được lưu trữ một cách không an toàn và bạn không thể làm gì về nó. Điều này làm cho việc bảo vệ với SecureStringkhá nhiều vô dụng.

Tuy nhiên, GUI có thể được lập trình để sử dụng SecureStringvà trong trường hợp đó, việc giảm cửa sổ khả dụng mật khẩu trong bộ nhớ có thể đáng giá. Ví dụ: PasswordBox.SecurePassword từ WPF thuộc loạiSecureString .


Hmm, chắc chắn thú vị để biết. Thành thật mà nói, tôi thực sự không lo lắng về các loại tấn công cần thiết để thậm chí lấy giá trị System.String ra khỏi bộ nhớ của tôi, tôi chỉ hỏi về sự tò mò thuần túy. Tuy nhiên, cảm ơn thông tin! :)
Steven Jeffries

2

Cách đây một thời gian, tôi đã phải tạo giao diện ac # dựa trên cổng thanh toán thẻ tín dụng java và người ta cần một mã hóa khóa giao tiếp an toàn tương thích. Vì việc triển khai Java khá cụ thể và tôi phải làm việc với dữ liệu được bảo vệ theo một cách nhất định.

Tôi thấy thiết kế này khá dễ sử dụng và dễ sử dụng hơn so với làm việc với SecureString, đối với những người thích sử dụng cảm thấy thoải mái, không có giới hạn pháp lý :-). Lưu ý rằng các lớp này là nội bộ, bạn có thể cần phải công khai chúng.

namespace Cardinity.Infrastructure
{
    using System.Security.Cryptography;
    using System;
    enum EncryptionMethods
    {
        None=0,
        HMACSHA1,
        HMACSHA256,
        HMACSHA384,
        HMACSHA512,
        HMACMD5
    }


internal class Protected
{
    private  Byte[] salt = Guid.NewGuid().ToByteArray();

    protected byte[] Protect(byte[] data)
    {
        try
        {
            return ProtectedData.Protect(data, salt, DataProtectionScope.CurrentUser);
        }
        catch (CryptographicException)//no reason for hackers to know it failed
        {
#if DEBUG
            throw;
#else
            return null;
#endif
        }
    }

    protected byte[] Unprotect(byte[] data)
    {
        try
        {
            return ProtectedData.Unprotect(data, salt, DataProtectionScope.CurrentUser);
        }
        catch (CryptographicException)//no reason for hackers to know it failed
        {
#if DEBUG
            throw;
#else
            return null;
#endif
        }
    }
}


    internal class SecretKeySpec:Protected,IDisposable
    {
        readonly EncryptionMethods _method;

        private byte[] _secretKey;
        public SecretKeySpec(byte[] secretKey, EncryptionMethods encryptionMethod)
        {
            _secretKey = Protect(secretKey);
            _method = encryptionMethod;
        }

        public EncryptionMethods Method => _method;
        public byte[] SecretKey => Unprotect( _secretKey);

        public void Dispose()
        {
            if (_secretKey == null)
                return;
            //overwrite array memory
            for (int i = 0; i < _secretKey.Length; i++)
            {
                _secretKey[i] = 0;
            }

            //set-null
            _secretKey = null;
        }
        ~SecretKeySpec()
        {
            Dispose();
        }
    }

    internal class Mac : Protected,IDisposable
    {
        byte[] rawHmac;
        HMAC mac;
        public Mac(SecretKeySpec key, string data)
        {

            switch (key.Method)
            {
                case EncryptionMethods.HMACMD5:
                    mac = new HMACMD5(key.SecretKey);
                    break;
                case EncryptionMethods.HMACSHA512:
                    mac = new HMACSHA512(key.SecretKey);
                    break;
                case EncryptionMethods.HMACSHA384:
                    mac = new HMACSHA384(key.SecretKey);
                    break;
                case EncryptionMethods.HMACSHA256:
                    mac = new HMACSHA256(key.SecretKey);

                break;
                case EncryptionMethods.HMACSHA1:
                    mac = new HMACSHA1(key.SecretKey);
                    break;

                default:                    
                    throw new NotSupportedException("not supported HMAC");
            }
            rawHmac = Protect( mac.ComputeHash(Cardinity.ENCODING.GetBytes(data)));            

        }

        public string AsBase64()
        {
            return System.Convert.ToBase64String(Unprotect(rawHmac));
        }

        public void Dispose()
        {
            if (rawHmac != null)
            {
                //overwrite memory address
                for (int i = 0; i < rawHmac.Length; i++)
                {
                    rawHmac[i] = 0;
                }

                //release memory now
                rawHmac = null;

            }
            mac?.Dispose();
            mac = null;

        }
        ~Mac()
        {
            Dispose();
        }
    }
}
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.