Tại sao char [] được ưa thích hơn String cho mật khẩu?


3422

Trong Swing, trường mật khẩu có phương thức getPassword()(trả về char[]) thay vì phương thức getText()(trả về String) thông thường . Tương tự, tôi đã gặp một đề nghị không sử dụng Stringđể xử lý mật khẩu.

Tại sao lại Stringgây ra mối đe dọa cho bảo mật khi nói đến mật khẩu? Nó cảm thấy bất tiện khi sử dụng char[].

Câu trả lời:


4290

Dây là bất biến . Điều đó có nghĩa là một khi bạn đã tạo String, nếu một quá trình khác có thể kết xuất bộ nhớ, không có cách nào (ngoài phản xạ ), bạn có thể thoát khỏi dữ liệu trước khi bộ sưu tập rác bắt đầu.

Với một mảng, bạn có thể xóa sạch dữ liệu sau khi hoàn thành. Bạn có thể ghi đè lên mảng bằng bất cứ thứ gì bạn thích và mật khẩu sẽ không xuất hiện ở bất cứ đâu trong hệ thống, ngay cả trước khi thu gom rác.

Vì vậy, có, đây một vấn đề bảo mật - nhưng ngay cả việc sử dụng char[]chỉ làm giảm cơ hội cho kẻ tấn công và nó chỉ dành cho loại tấn công cụ thể này.

Như đã lưu ý trong các bình luận, có thể các mảng được di chuyển bởi trình thu gom rác sẽ để lại các bản sao dữ liệu đi lạc trong bộ nhớ. Tôi tin rằng đây là đặc thù của việc triển khai - trình thu gom rác có thể xóa tất cả bộ nhớ khi có, để tránh loại điều này. Ngay cả nếu có, vẫn có thời gian trong đó các char[]nhân vật thực sự là một cửa sổ tấn công.


3
Nếu một quá trình có quyền truy cập vào bộ nhớ của ứng dụng của bạn, thì đó đã là một vi phạm bảo mật, phải không?
Yeti

5
@Yeti: Vâng, nhưng nó không giống như màu đen và trắng. Nếu họ chỉ có thể có được một ảnh chụp nhanh của bộ nhớ thì bạn muốn giảm mức độ thiệt hại mà ảnh chụp có thể gây ra hoặc giảm cửa sổ trong đó có thể chụp ảnh nhanh thực sự nghiêm trọng.
Jon Skeet

11
Một phương thức tấn công phổ biến là chạy một quy trình phân bổ nhiều bộ nhớ và sau đó quét nó để tìm dữ liệu còn lại, hữu ích như mật khẩu. Quá trình không cần bất kỳ quyền truy cập kỳ diệu nào vào không gian bộ nhớ của quy trình khác; nó chỉ dựa vào các tiến trình khác mà không xóa dữ liệu nhạy cảm trước và HĐH cũng không xóa bộ nhớ (hoặc bộ đệm trang) trước khi cung cấp cho quy trình mới. Xóa mật khẩu được lưu trữ trong các char[]vị trí sẽ cắt đứt đường tấn công đó, điều không thể khi sử dụng String.
Ted Hopp

Nếu HĐH không xóa bộ nhớ trước khi đưa nó vào quy trình khác, HĐH có vấn đề bảo mật lớn! Tuy nhiên, về mặt kỹ thuật, việc xóa thường được thực hiện với các thủ thuật chế độ được bảo vệ và nếu CPU bị hỏng (ví dụ Intel Meldown), bạn vẫn có thể đọc nội dung bộ nhớ cũ.
Mikko Rantalainen

1222

Trong khi các đề xuất khác ở đây có vẻ hợp lệ, có một lý do tốt khác. Với đồng bằng, Stringbạn có nhiều khả năng vô tình in mật khẩu vào nhật ký , màn hình hoặc một số nơi không an toàn khác. char[]ít bị tổn thương.

Xem xét điều này:

public static void main(String[] args) {
    Object pw = "Password";
    System.out.println("String: " + pw);

    pw = "Password".toCharArray();
    System.out.println("Array: " + pw);
}

Bản in:

String: Password
Array: [C@5829428e

40
@voo, nhưng tôi nghi ngờ bạn sẽ đăng nhập thông qua viết trực tiếp để truyền phát và ghép nối. khung ghi nhật ký sẽ biến char [] thành đầu ra tốt
bán chạy nhất vào

41
@ Thr4wn Mặc định thực hiện toStringclassname@hashcode. [Cđại diện char[], phần còn lại là mã băm thập lục phân.
Konrad Garus

15
Ý tưởng thú vị. Tôi muốn chỉ ra rằng điều này không chuyển sang Scala có toString có ý nghĩa cho mảng.
mauhiz

37
Tôi sẽ viết một Passwordloại lớp cho việc này. Nó ít mơ hồ hơn và khó hơn để vô tình đi qua một nơi nào đó.
đúng vào

8
Tại sao ai đó cho rằng mảng char sẽ được chọn làm Object? Tôi không chắc tại sao mọi người thích câu trả lời này. Giả sử bạn đã làm điều này: System.out.println ("Mật khẩu" .toCharArray ());
GC_

680

Để trích dẫn một tài liệu chính thức, hướng dẫn Kiến trúc mã hóa Java nói điều này về char[]so với Stringmật khẩu (về mã hóa dựa trên mật khẩu, nhưng tất nhiên đây là nói chung về mật khẩu):

Việc thu thập và lưu trữ mật khẩu trong một đối tượng thuộc loại có vẻ hợp lý java.lang.String. Tuy nhiên, đây là cảnh báo: Objectloại Stringkhông thay đổi, nghĩa là, không có phương thức nào được xác định cho phép bạn thay đổi (ghi đè) hoặc bỏ qua nội dung của mộtString sau khi sử dụng. Tính năng này làm cho Stringcác đối tượng không phù hợp để lưu trữ thông tin nhạy cảm về bảo mật như mật khẩu người dùng. charThay vào đó, bạn nên luôn thu thập và lưu trữ thông tin nhạy cảm bảo mật trong một mảng.

Hướng dẫn 2-2 của Nguyên tắc mã hóa an toàn cho ngôn ngữ lập trình Java, Phiên bản 4.0 cũng cho biết điều tương tự (mặc dù ban đầu nó nằm trong bối cảnh ghi nhật ký):

Hướng dẫn 2-2: Không đăng nhập thông tin có độ nhạy cao

Một số thông tin, chẳng hạn như số An sinh xã hội (SSN) và mật khẩu, rất nhạy cảm. Thông tin này không nên được lưu giữ lâu hơn mức cần thiết cũng như nơi có thể nhìn thấy, ngay cả bởi các quản trị viên. Chẳng hạn, nó không nên được gửi đến các tệp nhật ký và không thể phát hiện sự hiện diện của nó thông qua các tìm kiếm. Một số dữ liệu thoáng qua có thể được giữ trong các cấu trúc dữ liệu có thể thay đổi, chẳng hạn như mảng char và bị xóa ngay sau khi sử dụng. Việc xóa các cấu trúc dữ liệu đã làm giảm hiệu quả trên các hệ thống thời gian chạy Java điển hình khi các đối tượng được chuyển trong bộ nhớ trong suốt đến trình lập trình.

Hướng dẫn này cũng có ý nghĩa đối với việc triển khai và sử dụng các thư viện cấp thấp hơn không có kiến ​​thức ngữ nghĩa về dữ liệu mà họ đang xử lý. Ví dụ, một thư viện phân tích cú pháp chuỗi mức thấp có thể ghi lại văn bản mà nó hoạt động. Một ứng dụng có thể phân tích SSN với thư viện. Điều này tạo ra một tình huống trong đó SSN có sẵn cho quản trị viên có quyền truy cập vào tệp nhật ký.


4
đây chính xác là tài liệu tham khảo thiếu sót / không có thật mà tôi nói về bên dưới câu trả lời của Jon, đây là một nguồn nổi tiếng với rất nhiều lời chỉ trích.
bestsss

37
@bestass bạn có thể vui lòng trích dẫn một tài liệu tham khảo?
dùng961954

10
@bestass xin lỗi, nhưng Stringđược hiểu khá rõ và cách nó hoạt động trong JVM ... có những lý do chính đáng để sử dụng char[]thay thế Stringkhi xử lý mật khẩu một cách an toàn.
SnakeDoc

3
một lần nữa mặc dù mật khẩu được truyền dưới dạng một chuỗi từ trình duyệt đến yêu cầu là 'chuỗi' không phải là char? Vì vậy, bất kể bạn làm gì, đó là một chuỗi, tại thời điểm đó, nó nên được hành động và loại bỏ, không bao giờ được lưu trữ trong bộ nhớ?
Dawesi

3
@Dawesi - At which point- đó là ứng dụng cụ thể, nhưng quy tắc chung là phải làm như vậy ngay khi bạn nhận được một thứ được cho là mật khẩu (bản rõ hoặc cách khác). Bạn lấy nó từ trình duyệt như một phần của yêu cầu HTTP chẳng hạn. Bạn không thể kiểm soát việc phân phối, nhưng bạn có thể kiểm soát bộ nhớ của riêng mình, vì vậy ngay khi bạn nhận được nó, hãy đặt nó vào một char [], làm những gì bạn cần làm với nó, sau đó đặt tất cả thành '0' và để gc đòi lại nó
luis.espinal

354

Mảng ký tự ( char[]) có thể bị xóa sau khi sử dụng bằng cách đặt từng ký tự thành 0 và Chuỗi không. Nếu ai đó có thể thấy hình ảnh bộ nhớ bằng cách nào đó, họ có thể thấy mật khẩu ở dạng văn bản đơn giản nếu Chuỗi được sử dụng, nhưng nếu char[]được sử dụng, sau khi xóa dữ liệu bằng 0, mật khẩu được bảo mật.


13
Không an toàn theo mặc định. Nếu chúng ta đang nói về một ứng dụng web, hầu hết các bộ chứa web sẽ chuyển mật khẩu vào HttpServletRequestđối tượng trong bản rõ. Nếu phiên bản JVM là 1.6 hoặc thấp hơn, nó sẽ ở trong không gian permgen. Nếu là 1.7, nó vẫn có thể đọc được cho đến khi được thu thập. (Bất cứ khi nào có.)
avgvstvs

4
@avgvstvs: chuỗi không được tự động chuyển sang không gian permgen, mà chỉ áp dụng cho chuỗi nội bộ. Bên cạnh đó, không gian permgen cũng chịu sự thu gom rác, chỉ ở mức thấp hơn. Vấn đề thực sự với không gian permgen là kích thước cố định, đó chính xác là lý do tại sao không ai nên vô tâm gọi intern()các chuỗi tùy ý. Nhưng bạn đã đúng khi các Stringthể hiện tồn tại ở vị trí đầu tiên (cho đến khi được thu thập) và biến chúng thành char[]mảng sau đó không thay đổi nó.
Holger

3
@Holger xem docs.oracle.com/javase/specs/jvms/se6/html/ tựa "Nếu không, một phiên bản mới của Chuỗi lớp được tạo có chứa chuỗi ký tự Unicode được cung cấp bởi cấu trúc CONSTANT_String_info; chuỗi dẫn xuất bằng chữ. Cuối cùng, phương thức intern của thể hiện String mới được gọi. " Trong 1.6, JVM sẽ gọi intern cho bạn khi phát hiện các chuỗi giống hệt nhau.
avgvstvs

3
@Holger, bạn đúng là tôi đã kết hợp nhóm liên tục và nhóm chuỗi, nhưng cũng sai khi không gian permgen chỉ áp dụng cho các chuỗi được liên kết. Trước 1.7, cả hằng số_pool và chuỗi_pool đều nằm trong không gian permgen. Điều đó có nghĩa là lớp Chuỗi duy nhất được phân bổ cho heap như bạn đã nói, new String()hoặc StringBuilder.toString() tôi đã quản lý các ứng dụng có nhiều hằng chuỗi và kết quả là chúng tôi đã có rất nhiều permgen creep. Cho đến 1.7.
avgvstvs

5
@avgvstvs: tốt, các hằng chuỗi là, như các lệnh JLS, luôn luôn được thực hiện, do đó, câu lệnh mà các chuỗi được kết thúc đã kết thúc trong không gian permgen, được áp dụng cho các hằng chuỗi. Sự khác biệt duy nhất là các hằng chuỗi được tạo trong không gian permgen ở vị trí đầu tiên, trong khi việc gọi intern()một chuỗi tùy ý có thể gây ra sự phân bổ của một chuỗi tương đương trong không gian permgen. Cái sau có thể nhận được GC'ed, nếu không có chuỗi nghĩa đen của cùng một nội dung chia sẻ đối tượng đó
Holger

220

Một số người tin rằng bạn phải ghi đè lên bộ nhớ được sử dụng để lưu mật khẩu một khi bạn không còn cần nó nữa. Điều này làm giảm cửa sổ thời gian kẻ tấn công phải đọc mật khẩu từ hệ thống của bạn và hoàn toàn bỏ qua thực tế là kẻ tấn công đã cần đủ quyền truy cập để chiếm quyền điều khiển bộ nhớ JVM để thực hiện việc này. Kẻ tấn công có nhiều quyền truy cập có thể nắm bắt các sự kiện quan trọng của bạn khiến việc này hoàn toàn vô dụng (AFAIK, vì vậy hãy sửa cho tôi nếu tôi sai).

Cập nhật

Nhờ các ý kiến ​​tôi phải cập nhật câu trả lời của tôi. Rõ ràng có hai trường hợp điều này có thể thêm một cải tiến bảo mật nhỏ (rất) vì nó làm giảm thời gian mật khẩu có thể hạ cánh trên ổ cứng. Tuy nhiên, tôi nghĩ rằng nó quá mức cần thiết cho hầu hết các trường hợp sử dụng.

  • Hệ thống mục tiêu của bạn có thể được cấu hình kém hoặc bạn phải thừa nhận nó và bạn phải hoang tưởng về các bãi rác cốt lõi (có thể hợp lệ nếu hệ thống không được quản trị viên quản lý).
  • Phần mềm của bạn phải quá hoang tưởng để ngăn chặn rò rỉ dữ liệu khi kẻ tấn công giành quyền truy cập vào phần cứng - sử dụng những thứ như TrueCrypt (đã ngừng sử dụng), VeraCrypt hoặc CodesShed .

Nếu có thể, vô hiệu hóa các bãi chứa lõi và tệp hoán đổi sẽ giải quyết cả hai vấn đề. Tuy nhiên, họ sẽ yêu cầu quyền quản trị viên và có thể giảm chức năng (sử dụng ít bộ nhớ hơn) và kéo RAM từ hệ thống đang chạy vẫn là mối quan tâm hợp lệ.


33
Tôi sẽ thay thế "hoàn toàn vô dụng" bằng "chỉ là một cải tiến bảo mật nhỏ". Ví dụ: bạn có thể có quyền truy cập vào kết xuất bộ nhớ nếu bạn tình cờ có quyền truy cập đọc vào thư mục tmp, máy có cấu hình kém và sự cố trong ứng dụng của bạn. Trong trường hợp đó, bạn sẽ không thể cài đặt keylogger, nhưng bạn có thể phân tích kết xuất lõi.
Joachim Sauer

44
Xóa sạch dữ liệu không được mã hóa khỏi bộ nhớ ngay khi bạn hoàn thành nó được coi là một cách thực hành tốt nhất không phải vì nó không thể thực hiện được (không phải vậy); nhưng bởi vì nó làm giảm mức độ tiếp xúc với mối đe dọa của bạn. Các cuộc tấn công thời gian thực không được ngăn chặn bằng cách làm điều này; nhưng bởi vì nó phục vụ công cụ giảm thiểu thiệt hại bằng cách giảm đáng kể lượng dữ liệu bị lộ trong một cuộc tấn công hồi tố trên ảnh chụp nhanh bộ nhớ (ví dụ: bản sao bộ nhớ ứng dụng của bạn được ghi vào tệp hoán đổi hoặc được đọc ra khỏi bộ nhớ từ một máy chủ đang chạy và chuyển sang một máy chủ khác trước khi trạng thái của nó không thành công).
Dan đang loay hoay bởi Firelight

9
Tôi có xu hướng đồng ý với thái độ của phản ứng này. Tôi muốn mạo hiểm đề xuất hầu hết các vi phạm bảo mật về hậu quả xảy ra ở mức độ trừu tượng cao hơn nhiều so với các bit trong bộ nhớ. Chắc chắn, có thể có các kịch bản trong các hệ thống phòng thủ siêu an toàn, nơi điều này có thể đáng quan ngại nhưng suy nghĩ nghiêm túc ở cấp độ này là quá mức cần thiết cho 99% các ứng dụng mà .NET hoặc Java đang được sử dụng (vì nó liên quan đến việc thu gom rác).
kingdango

10
Sau khi Heartbleed thâm nhập vào bộ nhớ máy chủ, tiết lộ mật khẩu, tôi sẽ thay thế chuỗi "chỉ là một cải tiến bảo mật nhỏ" bằng "hoàn toàn cần thiết để không sử dụng String cho mật khẩu, mà thay vào đó sử dụng char []."
Peter vdL

9
@PetervdL chỉ cho phép đọc bộ sưu tập bộ đệm được sử dụng lại cụ thể (được sử dụng cho cả dữ liệu quan trọng bảo mật và I / O mạng mà không xóa giữa - vì lý do hiệu năng), bạn không thể kết hợp điều này với Chuỗi Java vì chúng không thể sử dụng lại được bởi Chuỗi Java . Bạn cũng không thể đọc vào bộ nhớ ngẫu nhiên bằng cách sử dụng Java để lấy nội dung của Chuỗi. Các vấn đề về ngôn ngữ và thiết kế dẫn đến sự đau lòng chỉ là không thể với Chuỗi Java.
josefx

86

Tôi không nghĩ rằng đây là một đề nghị hợp lệ, nhưng, ít nhất tôi có thể đoán được lý do.

Tôi nghĩ rằng động lực là muốn đảm bảo rằng bạn có thể xóa tất cả dấu vết của mật khẩu trong bộ nhớ kịp thời và chắc chắn sau khi sử dụng nó. Với một char[]bạn có thể ghi đè lên từng phần tử của mảng bằng một khoảng trống hoặc một cái gì đó chắc chắn. Bạn không thể chỉnh sửa giá trị nội bộ Stringtheo cách đó.

Nhưng đó không phải là một câu trả lời hay; Tại sao không chỉ đảm bảo một tham chiếu đến char[]hoặc Stringkhông thoát? Sau đó, không có vấn đề bảo mật. Nhưng điều quan trọng là Stringcác vật thể có thể được chỉnh sửa intern()trong lý thuyết và được duy trì sự sống bên trong nhóm liên tục. Tôi cho rằng sử dụng char[]cấm khả năng này.


4
Tôi sẽ không nói rằng vấn đề là tài liệu tham khảo của bạn sẽ hoặc không "thoát". Chỉ là các chuỗi sẽ vẫn không được sửa đổi trong bộ nhớ trong một thời gian bổ sung, trong khi char[]có thể được sửa đổi, và sau đó nó không liên quan cho dù nó có được thu thập hay không. Và vì thực tập chuỗi cần phải được thực hiện rõ ràng cho những người không biết chữ, nên nó giống như nói rằng một trường char[]có thể được tham chiếu bởi một trường tĩnh.
Groo

1
không phải mật khẩu trong bộ nhớ là một chuỗi từ bài mẫu?
Dawesi

66

Câu trả lời đã được đưa ra, nhưng tôi muốn chia sẻ một vấn đề mà tôi đã phát hiện ra gần đây với các thư viện chuẩn Java. Mặc dù bây giờ họ rất quan tâm đến việc thay thế các chuỗi mật khẩu bằng char[]mọi nơi (tất nhiên đó là một điều tốt), các dữ liệu quan trọng về bảo mật khác dường như bị bỏ qua khi xóa nó khỏi bộ nhớ.

Tôi đang nghĩ về ví dụ PrivateKey lớp . Hãy xem xét một kịch bản trong đó bạn sẽ tải khóa RSA riêng từ tệp PKCS # 12, sử dụng nó để thực hiện một số thao tác. Bây giờ trong trường hợp này, việc đánh hơi mật khẩu một mình sẽ không giúp bạn chừng nào quyền truy cập vật lý vào tệp chính bị hạn chế. Là một kẻ tấn công, bạn sẽ tốt hơn nhiều nếu lấy được khóa trực tiếp thay vì mật khẩu. Thông tin mong muốn có thể bị rò rỉ đa dạng, bãi chứa lõi, phiên gỡ lỗi hoặc tệp hoán đổi chỉ là một số ví dụ.

Và hóa ra, không có gì cho phép bạn xóa thông tin cá nhân PrivateKeykhỏi bộ nhớ, bởi vì không có API nào cho phép bạn xóa các byte tạo thành thông tin tương ứng.

Đây là một tình huống xấu, vì bài viết này mô tả làm thế nào tình huống này có thể được khai thác.

Ví dụ, thư viện OpenSSL ghi đè lên các phần bộ nhớ quan trọng trước khi các khóa riêng được giải phóng. Vì Java được thu thập rác, chúng ta sẽ cần các phương thức rõ ràng để xóa và làm mất hiệu lực thông tin cá nhân cho các khóa Java, được áp dụng ngay sau khi sử dụng khóa.


Một cách khác là sử dụng một triển khai PrivateKeythực sự không tải nội dung riêng tư của nó vào bộ nhớ: ví dụ như thông qua mã thông báo phần cứng PKCS # 11. Có lẽ việc triển khai phần mềm PKCS # 11 có thể đảm nhiệm việc dọn dẹp bộ nhớ theo cách thủ công. Có lẽ sử dụng một cái gì đó như cửa hàng NSS (chia sẻ hầu hết việc triển khai với PKCS11loại cửa hàng trong Java) là tốt hơn. KeychainStore( Kho khóa OSX) tải toàn bộ nội dung của khóa riêng vào các PrivateKeythể hiện của nó , nhưng không cần thiết. (Không chắc chắn WINDOWS-MYKeyStore làm gì trên Windows.)
Bruno

@Bruno Chắc chắn, các mã thông báo dựa trên phần cứng không gặp phải vấn đề này, nhưng còn những tình huống mà bạn ít nhiều bị buộc phải sử dụng khóa phần mềm thì sao? Không phải mọi triển khai đều có ngân sách để chi trả cho một HSM. Các cửa hàng khóa phần mềm tại một số điểm sẽ phải tải khóa vào bộ nhớ, vì vậy IMO ít nhất chúng ta nên được cung cấp tùy chọn để xóa bộ nhớ theo ý muốn.
chạm nổi

Hoàn toàn, tôi chỉ tự hỏi liệu một số triển khai phần mềm tương đương với một HSM có thể hoạt động tốt hơn trong việc làm sạch bộ nhớ hay không. Ví dụ: khi sử dụng client-auth với Safari / OSX, quy trình Safari không bao giờ thực sự nhìn thấy khóa riêng, thư viện SSL cơ bản do HĐH cung cấp nói chuyện trực tiếp với trình nền bảo mật nhắc người dùng sử dụng khóa từ móc khóa. Mặc dù tất cả điều này được thực hiện trong phần mềm, một sự tách biệt tương tự như thế này có thể giúp ích nếu việc ký được ủy quyền cho một thực thể khác (thậm chí dựa trên phần mềm) sẽ giải phóng hoặc xóa bộ nhớ tốt hơn.
Bruno

@Bruno: Ý tưởng thú vị, một lớp bổ sung bổ sung chăm sóc xóa bộ nhớ thực sự sẽ giải quyết điều này một cách minh bạch. Viết một trình bao bọc PKCS # 11 cho kho khóa phần mềm đã có thể thực hiện thủ thuật?
chạm nổi

51

Như Jon Skeet tuyên bố, không có cách nào ngoại trừ bằng cách sử dụng sự phản chiếu.

Tuy nhiên, nếu sự phản chiếu là một lựa chọn cho bạn, bạn có thể làm điều này.

public static void main(String[] args) {
    System.out.println("please enter a password");
    // don't actually do this, this is an example only.
    Scanner in = new Scanner(System.in);
    String password = in.nextLine();
    usePassword(password);

    clearString(password);

    System.out.println("password: '" + password + "'");
}

private static void usePassword(String password) {

}

private static void clearString(String password) {
    try {
        Field value = String.class.getDeclaredField("value");
        value.setAccessible(true);
        char[] chars = (char[]) value.get(password);
        Arrays.fill(chars, '*');
    } catch (Exception e) {
        throw new AssertionError(e);
    }
}

khi chạy

please enter a password
hello world
password: '***********'

Lưu ý: nếu char của String [] đã được sao chép như một phần của chu trình GC, có khả năng bản sao trước đó nằm ở đâu đó trong bộ nhớ.

Bản sao cũ này sẽ không xuất hiện trong một đống heap, nhưng nếu bạn có quyền truy cập trực tiếp vào bộ nhớ thô của quá trình, bạn có thể thấy nó. Nói chung, bạn nên tránh bất cứ ai có quyền truy cập như vậy.


2
Tốt hơn là nên làm gì đó để ngăn việc in độ dài mật khẩu mà chúng tôi nhận được '***********'.
chux - Tái lập lại

@chux bạn có thể sử dụng ký tự có độ rộng bằng không, mặc dù điều này có thể gây nhầm lẫn hơn là hữu ích. Không thể thay đổi độ dài của mảng char mà không sử dụng Unsafe. ;)
Peter Lawrey

5
Do sự trùng lặp Chuỗi của Java 8, tôi nghĩ rằng việc làm một cái gì đó như thế có thể phá hủy khá nhiều ... bạn có thể sẽ xóa các chuỗi khác trong chương trình của mình rằng tình cờ có cùng giá trị của Chuỗi mật khẩu. Không thể, nhưng có thể ...
jamp 8/2/2016

1
@PeterLawrey Nó phải được kích hoạt bằng đối số JVM, nhưng nó ở đó. Có thể đọc về nó ở đây: blog.codecentric.de/en/2014/08/ trên
jamp 8/2/2016

1
Có khả năng cao là mật khẩu vẫn nằm trong Scannerbộ đệm bên trong và, vì bạn không sử dụng System.console().readPassword(), ở dạng có thể đọc được trong cửa sổ giao diện điều khiển. Nhưng đối với hầu hết các trường hợp sử dụng thực tế, thời gian usePasswordthực hiện là vấn đề thực tế. Ví dụ, khi thực hiện kết nối với một máy khác, phải mất một thời gian đáng kể và nói với kẻ tấn công rằng đó là thời điểm thích hợp để tìm kiếm mật khẩu trong heap. Giải pháp duy nhất là ngăn chặn những kẻ tấn công đọc bộ nhớ heap
Holger

42

Đây là tất cả các lý do, người ta nên chọn một mảng char [] thay vì String cho mật khẩu.

1. Vì Chuỗi là bất biến trong Java, nếu bạn lưu mật khẩu dưới dạng văn bản thuần túy, nó sẽ có sẵn trong bộ nhớ cho đến khi Trình thu gom rác xóa nó và vì Chuỗi được sử dụng trong nhóm Chuỗi để có thể sử dụng lại nên có khả năng rất cao nó sẽ lưu lại trong bộ nhớ trong một thời gian dài, điều này gây ra mối đe dọa an ninh.

Vì bất kỳ ai có quyền truy cập vào kết xuất bộ nhớ đều có thể tìm thấy mật khẩu ở dạng văn bản rõ ràng, đó là một lý do khác khiến bạn phải luôn sử dụng mật khẩu được mã hóa thay vì văn bản thuần túy. Vì Chuỗi là bất biến nên không có cách nào thay đổi nội dung của Chuỗi vì mọi thay đổi sẽ tạo ra Chuỗi mới, trong khi nếu bạn sử dụng char [], bạn vẫn có thể đặt tất cả các thành phần là trống hoặc không. Vì vậy, lưu trữ mật khẩu trong một mảng ký tự rõ ràng giảm thiểu rủi ro bảo mật của việc đánh cắp mật khẩu.

2. Bản thân Java khuyên bạn nên sử dụng phương thức getPassword () của JPasswordField, trả về một char [], thay vì phương thức getText () không dùng nữa để trả về mật khẩu trong các lý do bảo mật rõ ràng. Thật tốt khi làm theo lời khuyên từ nhóm Java và tuân thủ các tiêu chuẩn thay vì chống lại chúng.

3. Với String luôn có nguy cơ in văn bản đơn giản trong tệp nhật ký hoặc bảng điều khiển nhưng nếu bạn sử dụng Mảng, bạn sẽ không in nội dung của mảng, mà thay vào đó, vị trí bộ nhớ của nó sẽ được in. Mặc dù không phải là một lý do thực sự, nó vẫn có ý nghĩa.

String strPassword="Unknown";
char[] charPassword= new char[]{'U','n','k','w','o','n'};
System.out.println("String password: " + strPassword);
System.out.println("Character password: " + charPassword);

String password: Unknown
Character password: [C@110b053

Tham khảo từ blog này . Tôi hi vọng cái này giúp được.


10
Điều này là dư thừa. Câu trả lời này là phiên bản chính xác của câu trả lời được viết bởi @SrujanKumarGulla stackoverflow.com/a/14060804/1793718 . Vui lòng không sao chép hoặc sao chép cùng một câu trả lời hai lần.
May mắn

Sự khác biệt giữa 1.) System.out.println ("Mật khẩu ký tự:" + charPassword); và 2.) System.out.println (charPassword); Bởi vì nó đang cho cùng một "ẩn số" như đầu ra.
Vaibhav_Sharma

3
@Lucky Thật không may, câu trả lời cũ hơn mà bạn liên kết đến đã bị ăn cắp từ cùng một blog với câu trả lời này và hiện đã bị xóa. Xem meta.stackoverflow.com/questions/389144/ . Câu trả lời này chỉ đơn giản là cắt và dán từ cùng một blog và không thêm gì, vì vậy nó chỉ đơn giản là một nhận xét liên kết đến nguồn ban đầu.
skomisa

41

Chỉnh sửa: Quay trở lại câu trả lời này sau một năm nghiên cứu bảo mật, tôi nhận ra rằng nó mang hàm ý khá đáng tiếc rằng bạn sẽ thực sự so sánh mật khẩu văn bản gốc. Xin đừng. Sử dụng hàm băm một chiều an toàn với muối và số lần lặp hợp lý . Cân nhắc sử dụng thư viện: công cụ này rất khó để có được ngay!

Câu trả lời gốc: Điều gì về thực tế là String.equals () sử dụng đánh giá ngắn mạch và do đó dễ bị tấn công thời gian? Có thể không chắc chắn, nhưng về mặt lý thuyết bạn có thể so sánh mật khẩu để xác định chuỗi ký tự chính xác.

public boolean equals(Object anObject) {
    if (this == anObject) {
        return true;
    }
    if (anObject instanceof String) {
        String anotherString = (String)anObject;
        int n = value.length;
        // Quits here if Strings are different lengths.
        if (n == anotherString.value.length) {
            char v1[] = value;
            char v2[] = anotherString.value;
            int i = 0;
            // Quits here at first different character.
            while (n-- != 0) {
                if (v1[i] != v2[i])
                    return false;
                i++;
            }
            return true;
        }
    }
    return false;
}

Một số tài nguyên khác về các cuộc tấn công thời gian:


Nhưng điều đó cũng có thể có trong so sánh char [], ở đâu đó chúng ta cũng sẽ làm điều tương tự trong xác thực mật khẩu. vậy làm thế nào là char [] tốt hơn chuỗi?
Mohit Kanwar

3
Bạn hoàn toàn chính xác, sai lầm có thể được thực hiện theo một trong hai cách. Biết về vấn đề này là điều quan trọng nhất ở đây, vì không có phương pháp so sánh mật khẩu rõ ràng nào trong Java đối với mật khẩu dựa trên Chuỗi hoặc char []. Tôi muốn nói rằng sự cám dỗ khi sử dụng so sánh () cho Chuỗi là một lý do chính đáng để sử dụng char []. Bằng cách đó, bạn ít nhất có quyền kiểm soát cách so sánh được thực hiện (mà không mở rộng Chuỗi, đó là một imo đau).
Lý thuyết đồ thị

Bên cạnh đó so sánh mật khẩu chữ thô không phải là điều đúng đắn dù sao, sự cám dỗ để sử dụng Arrays.equalscho char[]là cao như cho String.equals. Nếu bất cứ ai quan tâm, có một lớp khóa chuyên dụng đóng gói mật khẩu thực tế và xử lý các vấn đề. Đợi đã, các gói bảo mật thực sự các lớp khóa chuyên dụng , ví dụ , Q & A này chỉ là về một thói quen bên ngoài chúng. thay vì (nơi mà các thuật toán thực tế sử dụng anyway). JPasswordFieldchar[]Stringbyte[]
Holger

Phần mềm bảo mật có liên quan nên làm một cái gì đó giống như sleep(secureRandom.nextInt())trước khi từ chối một nỗ lực đăng nhập, điều đó không chỉ loại bỏ khả năng tấn công thời gian, mà còn làm cho các nỗ lực chống lại vũ phu.
Holger

36

Không có gì mà mảng char cung cấp cho bạn so với String trừ khi bạn dọn dẹp thủ công sau khi sử dụng và tôi chưa thấy ai thực sự làm điều đó. Vì vậy, với tôi, sở thích của char [] vs String là hơi cường điệu.

Hãy xem thư viện Spring Security được sử dụng rộng rãi ở đây và tự hỏi - những kẻ bảo mật Spring không đủ năng lực hoặc mật khẩu char [] không có ý nghĩa gì nhiều. Khi một số hacker khó chịu chiếm lấy bộ nhớ RAM của bạn, hãy chắc chắn rằng họ sẽ nhận được tất cả mật khẩu ngay cả khi bạn sử dụng các cách tinh vi để ẩn chúng.

Tuy nhiên, Java thay đổi mọi lúc và một số tính năng đáng sợ như tính năng trùng lặp Chuỗi của Java 8 có thể thực hiện các đối tượng Chuỗi mà bạn không biết. Nhưng đó là một cuộc trò chuyện khác.


Tại sao chuỗi trùng lặp đáng sợ? Nó chỉ áp dụng khi có ít nhất hai chuỗi có cùng nội dung, vậy nguy hiểm nào sẽ phát sinh từ việc để hai chuỗi giống hệt nhau này chia sẻ cùng một mảng? Hoặc cho phép hỏi theo cách khác: nếu không có sự trùng lặp chuỗi, lợi thế nào phát sinh từ thực tế là cả hai chuỗi có một mảng riêng biệt (có cùng nội dung)? Trong cả hai trường hợp, sẽ có một mảng các nội dung đó tồn tại ít nhất là Chuỗi tồn tại lâu nhất của nội dung đó còn sống
Holger

@Holger bất cứ điều gì ngoài tầm kiểm soát của bạn đều có nguy cơ tiềm ẩn ... ví dụ: nếu hai người dùng có cùng mật khẩu thì tính năng tuyệt vời này sẽ lưu trữ cả hai trong một char [] làm cho họ thấy giống nhau, không chắc là như vậy một rủi ro lớn nhưng vẫn còn
Oleg Mikheev

Nếu bạn có quyền truy cập vào bộ nhớ heap và cả hai trường hợp chuỗi, việc chuỗi có trỏ đến cùng một mảng hay hai mảng có cùng nội dung hay không, không dễ dàng tìm ra từng mảng. Đặc biệt, vì dù sao nó cũng không liên quan. Nếu bạn đang ở thời điểm này, bạn lấy cả hai mật khẩu, cho dù giống hệt nhau hay không. Lỗi thực tế nằm ở việc sử dụng mật khẩu văn bản thay vì băm muối.
Holger

@Holger để xác minh mật khẩu, nó phải ở dạng văn bản rõ ràng trong bộ nhớ trong một thời gian, 10ms, ngay cả khi đó chỉ là để tạo ra một hàm băm muối từ nó. Sau đó, nếu có hai mật khẩu giống hệt nhau được lưu trong bộ nhớ ngay cả trong 10 ms, sự trùng lặp có thể xuất hiện. Nếu nó thực sự tập trung chuỗi, chúng sẽ được giữ trong bộ nhớ trong thời gian lâu hơn nhiều. Hệ thống không khởi động lại trong nhiều tháng sẽ thu thập rất nhiều trong số này. Chỉ là lý thuyết hóa.
Oleg Mikheev

Dường như, bạn có một sự hiểu lầm cơ bản về trùng lặp chuỗi. Nó không có chuỗi thực tập nào, tất cả những gì nó làm, cho phép các chuỗi có cùng nội dung trỏ đến cùng một mảng, điều này thực sự làm giảm số lượng phiên bản mảng có chứa mật khẩu văn bản gốc, vì tất cả ngoại trừ một phiên bản mảng có thể được thu hồi và ghi đè bởi các đối tượng khác ngay lập tức. Các chuỗi này vẫn được thu thập như bất kỳ chuỗi nào khác. Có lẽ nó giúp, nếu bạn hiểu rằng việc sao chép thực sự được thực hiện bởi trình thu gom rác, đối với các chuỗi chỉ tồn tại trong nhiều chu kỳ GC.
Holger

30

Chuỗi là bất biến và không thể thay đổi một khi chúng đã được tạo. Tạo mật khẩu dưới dạng chuỗi sẽ để lại các tham chiếu đi lạc đến mật khẩu trên heap hoặc trên nhóm String. Bây giờ nếu ai đó thực hiện một đống quá trình Java và quét cẩn thận thông qua anh ta có thể đoán được mật khẩu. Tất nhiên các chuỗi không được sử dụng này sẽ là rác được thu thập nhưng điều đó phụ thuộc vào thời điểm GC khởi động.

Mặt khác, char [] có thể thay đổi ngay khi xác thực xong, bạn có thể ghi đè chúng bằng bất kỳ ký tự nào như tất cả các dấu gạch chéo chữ M hoặc dấu gạch chéo ngược. Bây giờ ngay cả khi ai đó mất một đống, anh ta có thể không có được mật khẩu hiện không được sử dụng. Điều này cho phép bạn kiểm soát nhiều hơn theo nghĩa như tự xóa nội dung Object so với chờ đợi GC thực hiện.


Chúng sẽ chỉ là GC'd nếu JVM trong câu hỏi là> 1.6. Trước 1.7, tất cả các chuỗi được lưu trữ trong permgen.
avgvstvs

@avgvstvs: Tất cả các chuỗi đã được lưu trữ trong permgen, chỉ là sai. Chỉ các chuỗi nội bộ được lưu trữ ở đó và nếu chúng không có nguồn gốc từ các chuỗi ký tự được tham chiếu bởi mã, chúng vẫn là rác được thu thập. Nghĩ về nó đi. Nếu các chuỗi thường không bao giờ được xử lý trong các JVM trước 1.7, làm thế nào bất kỳ ứng dụng Java nào có thể tồn tại hơn một vài phút?
Holger

@Holger Điều này là sai. Đã thực hiện stringsVÀ nhóm String(nhóm các chuỗi được sử dụng trước đó) đã được lưu trữ trong Permgen trước 1.7. Ngoài ra, hãy xem phần 5.1: docs.oracle.com/javase/specs/jvms/se6/html/ợi JVM luôn kiểm tra Stringsxem chúng có cùng giá trị tham chiếu hay không và sẽ gọi cho String.intern()BẠN. Kết quả là mỗi khi JVM phát hiện các Chuỗi giống hệt nhau trong constant_poolhoặc heap, nó sẽ chuyển chúng thành permgen. Và tôi đã làm việc trên một số ứng dụng với "creeping permgen" cho đến 1.7. Đó là một vấn đề thực sự.
avgvstvs

Vì vậy, để tóm tắt lại: Cho đến ngày 1.7, Chuỗi bắt đầu trong heap, khi chúng được sử dụng, chúng được đặt vào constant_poolvị trí IN permgen, và sau đó nếu một chuỗi được sử dụng nhiều lần, nó sẽ được thực hiện.
avgvstvs

@avgvstvs: Không có nhóm sử dụng chuỗi nào được sử dụng trước đó. Bạn đang ném những thứ hoàn toàn khác nhau với nhau. Có một nhóm chuỗi thời gian chạy chứa chuỗi ký tự chuỗi và chuỗi được thực hiện rõ ràng, nhưng không có chuỗi nào khác. Và mỗi lớp có nhóm hằng số chứa các hằng số thời gian biên dịch . Các chuỗi này được tự động thêm vào nhóm thời gian chạy, nhưng chỉ những chuỗi này , không phải mọi chuỗi.
Holger

21

Chuỗi là bất biến và nó đi đến nhóm chuỗi. Sau khi viết, nó không thể được ghi đè.

char[] là một mảng mà bạn nên ghi đè lên một khi bạn đã sử dụng mật khẩu và đây là cách nó nên được thực hiện:

char[] passw = request.getPassword().toCharArray()
if (comparePasswords(dbPassword, passw) {
 allowUser = true;
 cleanPassword(passw);
 cleanPassword(dbPassword);
 passw=null;
}

private static void cleanPassword (char[] pass) {

Arrays.fill(pass, '0');
}

Một kịch bản mà kẻ tấn công có thể sử dụng nó là một sự cố - khi JVM gặp sự cố và tạo kết xuất bộ nhớ - bạn sẽ có thể thấy mật khẩu.

Đó không nhất thiết là một kẻ tấn công độc hại bên ngoài. Đây có thể là người dùng hỗ trợ có quyền truy cập vào máy chủ cho mục đích giám sát. Anh ta có thể nhìn trộm vào một vụ tai nạn và tìm mật khẩu.


2
ch = null; bạn không thể làm điều này
Yugerten

Nhưng không request.getPassword()tạo chuỗi và thêm nó vào nhóm?
Tvde1

1
ch = '0'thay đổi biến cục bộ ch; Nó không có tác dụng trên mảng. Và ví dụ của bạn là vô nghĩa, bạn bắt đầu với một thể hiện chuỗi bạn gọi toCharArray(), tạo một mảng mới và ngay cả khi bạn ghi đè lên mảng mới một cách chính xác, nó không thay đổi thể hiện chuỗi, vì vậy nó không có lợi thế hơn so với việc chỉ sử dụng chuỗi ví dụ
Holger

@Holger cảm ơn. Sửa mã dọn dẹp mảng char.
ACV

3
Bạn chỉ có thể sử dụngArrays.fill(pass, '0');
Holger

18

Câu trả lời ngắn gọn và đơn giản sẽ là vì char[]có thể thay đổi trong khi Stringcác đối tượng thì không.

Stringstrong Java là các đối tượng bất biến. Đó là lý do tại sao chúng không thể được sửa đổi một khi được tạo và do đó cách duy nhất để xóa nội dung của chúng khỏi bộ nhớ là thu gom rác. Nó sẽ chỉ sau đó khi bộ nhớ được giải phóng bởi đối tượng có thể được ghi đè, và dữ liệu sẽ biến mất.

Bây giờ việc thu gom rác trong Java không xảy ra ở bất kỳ khoảng thời gian nào được bảo đảm. CácString do đó có thể tồn tại trong ký ức một thời gian dài, và nếu một quá trình bị treo trong thời gian này, các nội dung của chuỗi có thể kết thúc trong một bãi chứa bộ nhớ hoặc một số bản ghi.

Với một mảng ký tự , bạn có thể đọc mật khẩu, hoàn thành công việc với nó ngay khi bạn có thể, và sau đó ngay lập tức thay đổi nội dung.


@fallenidol Hoàn toàn không. Đọc kỹ và bạn sẽ tìm thấy sự khác biệt.
Pritam Banerjee

12

Chuỗi trong java là bất biến. Vì vậy, bất cứ khi nào một chuỗi được tạo, nó sẽ vẫn còn trong bộ nhớ cho đến khi nó được thu gom rác. Vì vậy, bất cứ ai có quyền truy cập vào bộ nhớ đều có thể đọc giá trị của chuỗi.
Nếu giá trị của chuỗi được sửa đổi thì cuối cùng nó sẽ tạo ra một chuỗi mới. Vì vậy, cả giá trị ban đầu và giá trị được sửa đổi đều nằm trong bộ nhớ cho đến khi nó được thu gom rác.

Với mảng ký tự, nội dung của mảng có thể được sửa đổi hoặc xóa sau khi mục đích của mật khẩu được phục vụ. Nội dung ban đầu của mảng sẽ không được tìm thấy trong bộ nhớ sau khi được sửa đổi và ngay cả trước khi bộ sưu tập rác khởi động.

Vì lo ngại bảo mật, tốt hơn là lưu trữ mật khẩu dưới dạng một mảng ký tự.


2

Điều gây tranh cãi là liệu bạn nên sử dụng String hay sử dụng Char [] cho mục đích này vì cả hai đều có những ưu điểm và nhược điểm. Nó phụ thuộc vào những gì người dùng cần.

Vì các chuỗi trong Java là bất biến, nên bất cứ khi nào một số người cố gắng thao túng chuỗi của bạn, nó sẽ tạo ra một Đối tượng mới và Chuỗi hiện tại vẫn không bị ảnh hưởng. Đây có thể được coi là một lợi thế để lưu trữ mật khẩu dưới dạng Chuỗi, nhưng đối tượng vẫn còn trong bộ nhớ ngay cả sau khi sử dụng. Vì vậy, nếu bất cứ ai bằng cách nào đó có được vị trí bộ nhớ của đối tượng, người đó có thể dễ dàng theo dõi mật khẩu của bạn được lưu trữ tại vị trí đó.

Char [] có thể thay đổi, nhưng nó có lợi thế là sau khi sử dụng, lập trình viên có thể xóa sạch các giá trị mảng hoặc ghi đè. Vì vậy, khi nó được sử dụng xong, nó được làm sạch và không ai có thể biết về thông tin bạn đã lưu trữ.

Dựa trên các trường hợp trên, người ta có thể biết được nên đi với String hay đi với Char [] cho các yêu cầu của họ.


0

Chuỗi trường hợp:

    String password = "ill stay in StringPool after Death !!!";
    // some long code goes
    // ...Now I want to remove traces of password
    password = null;
    password = "";
    // above attempts wil change value of password
    // but the actual password can be traced from String pool through memory dump, if not garbage collected

Trường hợp CHAR ARRAY:

    char[] passArray = {'p','a','s','s','w','o','r','d'};
    // some long code goes
    // ...Now I want to remove traces of password
    for (int i=0; i<passArray.length;i++){
        passArray[i] = 'x';
    }
    // Now you ACTUALLY DESTROYED traces of password form memory
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.