비밀번호에 대해 char []가 String보다 선호되는 이유는 무엇입니까?


3422

스윙에서 암호 필드는이 getPassword()(반환 char[]대신에 보통의) 방법 getText()(반환 String) 방법을. 마찬가지로 String암호를 처리하는 데 사용하지 말 것을 제안했습니다 .

String암호와 관련하여 보안에 위협이되는 이유는 무엇 입니까? 사용하기 불편하다 char[].

답변:


4289

문자열은 변경할 수 없습니다 . 당신이 만들어지면 그 수단 String다른 프로세스가 메모리를 덤프 할 수있는 경우는, (옆에서있는 방법은 없습니다 반사 이전에 데이터를 제거 할 수) 가비지 컬렉션 에서 차기.

배열을 사용하면 데이터를 완성한 후에 데이터를 명시 적으로 지울 수 있습니다. 배열을 원하는 것으로 덮어 쓸 수 있으며 가비지 수집 전에도 시스템의 어느 곳에도 암호가 표시되지 않습니다.

그래서 그래,이 보안 문제 -하지만도 사용은 char[]공격자 만 기회의 창을 줄이고, 그것은 단지 공격이 특정 유형입니다.

주석에서 언급했듯이 가비지 수집기에 의해 이동되는 배열은 메모리에 부유 한 데이터 복사본을 남길 수 있습니다. 나는 이것이 구현에 특정 적이 라고 믿습니다. 가비지 수집기 이런 종류의 것을 피하기 위해 모든 메모리를 지울 수 있습니다 . 그렇더라도 char[]실제 문자를 공격 창으로 포함하는 시간은 여전히 ​​남아 있습니다.


3
프로세스가 애플리케이션의 메모리에 액세스 할 수 있다면 이미 보안 위반에 해당합니까?
Yeti

5
@Yeti : 그렇습니다. 그러나 그것은 흑백이 아닙니다. 메모리의 스냅 샷 만 얻을 수있는 경우 스냅 샷의 손상 정도를 줄이거 나 실제로 심각한 스냅 샷을 찍을 수있는 기간을 줄이려고합니다.
Jon Skeet

11
일반적인 공격 방법은 많은 메모리를 할당 한 다음 암호와 같은 유용한 데이터를 검색하는 프로세스를 실행하는 것입니다. 프로세스는 다른 프로세스의 메모리 공간에 마법으로 액세스 할 필요가 없습니다. 중요한 데이터를 먼저 지우지 않고 죽어가는 다른 프로세스에 의존하고 OS는 새 프로세스에 사용 가능하게하기 전에 메모리 (또는 페이지 버퍼)를 지우지 않습니다. char[]위치에 저장된 암호를 지우면 해당 공격 라인이 차단됩니다 String.
Ted Hopp

OS가 다른 프로세스에 메모리를주기 전에 메모리를 지우지 않으면 OS에 주요 보안 문제가 있습니다! 그러나 기술적으로 지우기는 종종 보호 모드 트릭으로 수행되며 CPU가 손상된 경우 (예 : Intel Meldown) 여전히 오래된 메모리 내용을 읽을 수 있습니다.
Mikko Rantalainen

1221

여기에 다른 제안이 유효 해 보이지만 다른 좋은 이유가 있습니다. 평범하게 실수로 로그 , 모니터 또는 다른 안전하지 않은 장소에 암호를 인쇄String 할 가능성이 훨씬 높습니다 . 덜 취약합니다.char[]

이걸 고려하세요:

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

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

인쇄물:

String: Password
Array: [C@5829428e

40
@voo,하지만 스트림 및 연결에 직접 쓰기를 통해 기록 할 것 같지는 않습니다. 로깅 프레임 워크는 char []를 좋은 결과물로
바꾼다

41
@ Thr4wn의 기본 구현은 toString입니다 classname@hashcode. [Cchar[]나머지는 16 진 해시 코드입니다.
Konrad Garus 2012 년

15
재미있는 생각. 이것이 배열에 의미있는 toString이있는 Scala로 전치되지 않는다는 것을 지적하고 싶습니다.
mauhiz

37
이를 위해 Password클래스 유형을 작성합니다. 덜 애매하고 실수로 어딘가로 통과하기가 더 어렵습니다.
rightfold

8
왜 누군가 char 배열이 Object로 캐스팅 될 것이라고 생각합니까? 왜 모든 사람이이 답변을 좋아하는지 잘 모르겠습니다. 이 작업을 수행했다고 가정합니다. System.out.println ( "Password".toCharArray ());
GC_

680

공식 문서를 인용하기 위해 Java Cryptography Architecture 안내서 는 암호 char[]String암호에 대해 설명합니다 (암호 기반 암호화에 관한 것이지만 암호에 대해서는 더 일반적입니다).

유형의 객체에 암호를 수집하고 저장하는 것이 논리적으로 보입니다 java.lang.String. 그러나주의해야 할 점은 다음과 같습니다. Objects 유형 String은 변경할 수 없습니다. 즉, String 사용 후 내용을 변경 (덮어 쓰기)하거나 제로화 할 수있는 정의 된 메소드가 없습니다 . 이 기능은 String개체가 사용자 암호와 같은 보안에 민감한 정보를 저장하기에 적합하지 않게합니다. char대신 보안에 민감한 정보를 항상 배열로 수집하고 저장해야 합니다.

Java 프로그래밍 언어 용 보안 코딩 지침, 버전 4.0의 지침 2-2 에도 비슷한 내용이 나와 있습니다 (원래 로깅 컨텍스트에 있음).

지침 2-2 : 매우 민감한 정보를 기록하지 마십시오

사회 보장 번호 (SSN) 및 비밀번호와 같은 일부 정보는 매우 중요합니다. 이 정보는 필요 이상으로 보거나 관리자가 볼 수있는 위치에 보관해서는 안됩니다. 예를 들어, 로그 파일로 보내지 않아야하며 검색을 통해 존재 여부를 감지 할 수 없습니다. 일부 일시적인 데이터는 char 배열과 같은 가변 데이터 구조로 유지 될 수 있으며 사용 후 즉시 지워질 수 있습니다. 데이터 구조를 지우면 객체가 메모리에서 프로그래머에게 투명하게 이동함에 따라 일반적인 Java 런타임 시스템의 효율성이 떨어집니다.

이 지침은 또한 다루고있는 데이터에 대한 의미 지식이없는 하위 레벨 라이브러리의 구현 및 사용에 영향을 미칩니다. 예를 들어, 하위 수준 문자열 구문 분석 라이브러리는 작동하는 텍스트를 기록 할 수 있습니다. 애플리케이션은 라이브러리와 SSN을 구문 분석 할 수 있습니다. 이로 인해 로그 파일에 액세스 할 수있는 관리자가 SSN을 사용할 수있는 상황이 발생합니다.


4
이것은 Jon의 답변 아래에서 이야기하는 결함 / 가짜 참조입니다. 많은 비평을 가진 잘 알려진 출처입니다.
bestsss

37
@bestass 당신은 또한 참조를 인용 할 수 있습니까?
user961954

10
@bestass 죄송하지만 StringJVM에서 어떻게 작동하는지 잘 이해하고 있습니다 ... 암호를 안전한 방식으로 처리 할 때 char[]대신 사용해야하는 이유가 있습니다 String.
SnakeDoc 2016 년

3
비밀번호가 브라우저에서 요청으로 문자열로 전달되었지만 문자가 아닌 'string'으로 다시 한 번? 그래서 당신이 무엇을 하든지 문자열입니다.이 시점에서 행동하고 버려야하며 결코 메모리에 저장되지 않습니까?
Dawesi

3
@Dawesi-- At which point응용 프로그램에 따라 다르지만 일반적인 규칙은 암호 (일반 텍스트 또는 기타)로 간주되는 항목을 가져 오면 즉시 수행하는 것입니다. 예를 들어 HTTP 요청의 일부로 브라우저에서 가져옵니다. 전달을 제어 할 수는 없지만 자신의 스토리지를 제어 할 수 있으므로 즉시 저장하여 char []에 넣고 필요한 작업을 수행 한 다음 모두 '0'으로 설정하고 gc를 허용하십시오. 그것을 되 찾아라.
luis.espinal

354

문자 배열 ( char[])은 사용 후 각 문자를 0으로 설정하고 문자열을 설정하지 않음으로써 지울 수 있습니다. 누군가가 어떻게 메모리 이미지를 볼 수 있다면, 문자열을 사용하면 일반 텍스트로 암호를 볼 수 있지만, 사용하면 char[]0으로 데이터를 제거한 후 암호는 안전합니다.


13
기본적으로 안전하지 않습니다. 웹 응용 프로그램을 이야기하는 경우 대부분의 웹 컨테이너는 암호를 HttpServletRequest일반 텍스트 로 객체에 전달합니다 . JVM 버전이 1.6 이하이면 permgen 공간에 있습니다. 1.7에 있으면 수집 될 때까지 계속 읽을 수 있습니다. (언제나
그렇습니다

4
@avgvstvs : 문자열은 인턴 된 문자열에만 적용되는 permgen 공간으로 자동 이동되지 않습니다. 그 외에도, permgen 공간은 더 낮은 비율로 가비지 수집 대상이됩니다. permgen 공간의 실제 문제는 크기가 고정되어 있기 때문에 아무도 intern()임의로 문자열을 호출하지 않아야하는 이유 입니다. 그러나 String인스턴스가 처음에 (수집 될 때까지) 존재하고 char[]나중에 배열로 바꾸어도 변경되지 않는다는 것이 맞습니다 .
Holger

3
@Holger docs.oracle.com/javase/specs/jvms/se6/html/…을 참조하십시오. "그렇지 않으면 CONSTANT_String_info 구조에 의해 제공된 유니 코드 문자 시퀀스를 포함하는 새로운 String 클래스 인스턴스가 작성됩니다.이 클래스 인스턴스는 다음의 결과입니다. 마지막으로, 새로운 String 인스턴스의 인턴 메소드가 호출됩니다. " 1.6에서 JVM은 동일한 시퀀스를 감지하면 인턴을 호출합니다.
avgvstvs

3
@Holger, 당신은 올바른 내가 정수 풀 및 문자열 수영장으로 융합되어 있지만, permgen 공간이 것 또한 거짓 만을 억류 문자열에 적용. 1.7 이전에는 constant_pool과 string_pool이 모두 permgen 공간에있었습니다. 당신이 말한대로 의미 힙에 할당 된 문자열의 유일한 클래스는 있었다, new String()또는 StringBuilder.toString() 나는 문자열 상수의 많은 애플리케이션을 관리, 우리는 결과로 permgen 크리프을 많이했다. 1.7까지.
avgvstvs

5
@avgvstvs : 음, 문자열 상수는 JLS 명령에 따라 항상 인터 닝되므로 인터 닝 된 문자열은 permgen 공간에서 끝나고 문자열 상수에 암시 적으로 적용됩니다. 유일한 차이점은 문자열 상수가 처음에 permgen 공간에서 생성되었다는 것입니다. 반면 intern()에 임의의 문자열을 호출 하면 permgen 공간에 동등한 문자열이 할당 될 수 있습니다. 같은 객체의 문자 그대로 그 객체를 공유하는 문자열이 없다면 후자는 GC를받을 수있다.
Holger

220

어떤 사람들은 더 이상 필요하지 않은 암호를 저장하는 데 사용 된 메모리를 덮어 써야한다고 생각합니다. 이를 통해 공격자가 시스템에서 암호를 읽는 데 걸리는 시간이 단축되고 공격자가 이미이를 수행하기 위해 JVM 메모리를 하이재킹 할 수있는 충분한 액세스가 필요하다는 사실을 완전히 무시합니다. 많은 액세스 권한을 가진 공격자는 주요 이벤트를 포착하여이를 완전히 쓸모 없게 만들 수 있습니다 (AFAIK, 내가 틀렸다면 바로 정정하십시오).

최신 정보

의견 덕분에 답변을 업데이트해야합니다. 암호가 하드 드라이브에 도달 할 수있는 시간을 줄임으로써 약간의 보안 향상을 추가 할 수있는 두 가지 경우가 있습니다. 여전히 대부분의 사용 사례에서는 과잉이라고 생각합니다.

  • 대상 시스템이 잘못 구성되었거나 구성되어 있다고 가정해야하며 코어 덤프에 대해 편집증이 있어야합니다 (관리자가 시스템을 관리하지 않는 경우 유효 할 수 있음).
  • 공격자가 TrueCrypt (단종), VeraCrypt 또는 CipherShed 와 같은 것을 사용하여 하드웨어에 액세스 할 때 데이터 유출을 방지하려면 소프트웨어가 과도하게 편집증 상태 여야 합니다.

가능하면 코어 덤프와 스왑 파일을 비활성화하면 두 가지 문제가 모두 해결됩니다. 그러나 관리자 권한이 필요하고 기능 (사용할 메모리가 적음)이 줄어들고 실행중인 시스템에서 RAM을 가져 오는 것이 여전히 유효한 문제입니다.


33
"완전히 쓸모없는"을 "경미한 보안 향상"으로 바꿉니다. 예를 들어, tmp 디렉토리에 대한 읽기 액세스 권한, 잘못 구성된 시스템 및 응용 프로그램 충돌이 발생하면 메모리 덤프에 액세스 할 수 있습니다. 이 경우 키로거를 설치할 수 없지만 핵심 덤프를 분석 할 있습니다.
Joachim Sauer

44
암호화되지 않은 데이터를 처리하자마자 메모리에서 지우는 것은 완전하지 않기 때문에 최선의 방법으로 간주되지 않습니다. 위협 노출 수준이 낮아지기 때문입니다. 이렇게하면 실시간 공격을 막을 수 없습니다. 그러나 메모리 스냅 샷에 대한 소급 적 공격에 노출되는 데이터 양 (예 : 스왑 파일에 기록되었거나 메모리에서 읽혀진 메모리에서 읽은 앱 메모리 복사본)을 크게 줄임으로써 손상 완화 도구를 제공하기 때문에 실행중인 서버에서 상태가 실패하기 전에 다른 서버로 이동).
Dan은 불을 피우고있다

9
나는이 반응의 태도에 동의하는 경향이있다. 나는 결과에 대한 대부분의 보안 침해가 메모리의 비트보다 훨씬 높은 추상화 수준에서 발생한다고 제안하려고했다. 물론 초 보안 방어 시스템에는 아마도 이것이 우려되는 시나리오가있을 수 있지만이 수준에서 진지하게 생각하는 것은 .NET 또는 Java가 활용되는 응용 프로그램의 99 % (가비지 수집과 관련하여)에 대해 과도하게 생각하는 것입니다.
kingdango

10
서버 메모리의 Heartbleed 침투 후 비밀번호를 밝힌 후 문자열 "비밀 한 보안 향상"을 "비밀번호에 문자열을 사용하지 않고 char []를 대신 사용하는 것이 절대적으로 필요합니다."
Peter vdL

9
@PetervdL heartbleed는 재사용이 불가능한 특정 버퍼 모음 (보안상의 데이터와 네트워크 I / O 모두에 대해 성능상의 이유로 지우지 않고 사용)을 읽을 수만 있었으므로 재사용 할 수 없기 때문에 Java String과 결합 할 수 없습니다 . 또한 Java를 사용하여 임의의 메모리를 읽어 문자열의 내용을 가져올 수도 없습니다. Heartbleed로 이어지는 언어 및 디자인 문제는 Java Strings로는 불가능합니다.
josefx

86

나는 이것이 유효한 제안이라고 생각하지 않지만 적어도 그 이유를 추측 할 수 있습니다.

동기 부여는 메모리에서 암호의 모든 흔적을 즉시 사용하고 확실하게 지울 수 있기를 원한다고 생각합니다. 를 사용 char[]하면 배열의 각 요소를 빈 칸으로 덮어 쓸 수 있습니다. String그런 식으로 내부 값을 편집 할 수 없습니다 .

그러나 이것만으로는 좋은 대답이 아닙니다. 왜 char[]또는 String탈출하지 않는지 확인 하지 않습니까? 그런 다음 보안 문제가 없습니다. 그러나 문제는 이론적으로 String객체를 intern()편집하고 일정한 수영장 안에 살 수 있다는 것입니다 . 나는 char[]이것을 사용하는 것이이 가능성 을 금지 한다고 생각 합니다.


4
나는 문제가 당신의 참고 문헌이 "탈출"하거나하지 않을 것이라고 말하고 싶지 않습니다. 단지 문자열이 메모리에서 추가 시간 동안 수정되지 않은 채로 유지되는 동안 char[]수정 될 수 있으며 수집 여부와 관계가 없습니다. 리터럴이 아닌 경우 문자열 인터 닝을 명시 적으로 수행해야 char[]하므로 정적 필드에서 a를 참조 할 수 있다는 것을 말하는 것과 같습니다 .
Groo

1
양식 게시물의 문자열로 메모리에 비밀번호가 없습니까?
Dawesi

66

답변이 이미 제공되었지만 최근에 Java 표준 라이브러리에서 발견 한 문제를 공유하고 싶습니다. 암호 문자열을 char[]모든 곳 (물론 좋은 것) 으로 바꾸는 데 많은주의를 기울이는 반면 , 메모리에서 지우려면 보안에 중요한 다른 데이터를 간과하는 것 같습니다.

예를 들어 PrivateKey 클래스를 생각하고 있습니다. PKCS # 12 파일에서 개인 RSA 키를 사용하여 일부 작업을 수행하는 시나리오를 고려하십시오. 이제이 경우, 비밀번호 만 스니핑해도 키 파일에 대한 물리적 액세스가 제대로 제한되는 한 도움이되지 않습니다. 공격자는 암호 대신 키를 직접 입수하면 훨씬 나을 것입니다. 원하는 정보가 유출 될 수있는 것은 매니 폴드, 코어 덤프, 디버거 세션 또는 스왑 파일입니다.

결과적으로 PrivateKey해당 정보를 형성하는 바이트를 지우는 API가 없기 때문에 메모리에서 개인 정보를 지울 수있는 것은 없습니다 .

백서 에서는 이러한 상황이 어떻게 악용 될 수 있는지 설명하기 때문에 이는 나쁜 상황 입니다.

예를 들어 OpenSSL 라이브러리는 개인 키가 해제되기 전에 중요한 메모리 섹션을 덮어 씁니다. Java는 가비지 수집되므로 Java 키에 대한 개인 정보를 지우고 무효화하는 명시적인 방법이 필요합니다.이 키는 사용 후 즉시 적용됩니다.


이 문제를 해결하는 한 가지 방법 PrivateKey은 PKCS # 11 하드웨어 토큰을 통해 메모리에 개인 콘텐츠를 실제로로드하지 않는 구현을 사용하는 것입니다 . PKCS # 11의 소프트웨어 구현으로 메모리를 수동으로 정리할 수 있습니다. 아마도 NSS 저장소와 같은 것을 사용하는 PKCS11것이 좋습니다 (Java 의 저장소 유형 과 대부분의 구현을 공유 함 ). KeychainStore(OSX 키 저장소)는 부하의에 개인 키의 전체 내용 PrivateKey인스턴스를 있지만 필요가 없습니다. ( WINDOWS-MYKeyStore가 Windows에서 무엇을하는지 확실 하지 않습니다.)
Bruno

@Bruno Sure, 하드웨어 기반 토큰은이 문제를 겪지 않지만 소프트웨어 키를 사용하도록 강요받는 상황은 어떻습니까? 모든 배포에 HSM을 제공 할 예산이있는 것은 아닙니다. 소프트웨어 키 저장소는 어느 시점에서 키를 메모리에로드해야하므로 IMO에는 최소한 메모리를 다시 지울 수있는 옵션이 제공되어야합니다.
엠 보스

물론 HSM과 동등한 일부 소프트웨어 구현이 메모리 정리 측면에서 더 잘 작동하는지 궁금합니다. 예를 들어, Safari / OSX에서 클라이언트 인증을 사용할 때 Safari 프로세스는 실제로 개인 키를 볼 수 없으며 OS에서 제공하는 기본 SSL 라이브러리는 보안 데몬과 직접 통신하여 키 체인의 키를 사용하라는 메시지를 표시합니다. 이 작업은 모두 소프트웨어에서 수행되지만 이와 유사한 분리는 서명이 메모리를 더 잘 언로드하거나 지우는 다른 엔터티 (소프트웨어 기반)에 위임되는 경우 도움이 될 수 있습니다.
Bruno

@Bruno : 흥미로운 아이디어, 메모리를 정리하는 추가 간접 레이어는 실제로 이것을 투명하게 해결합니다. 소프트웨어 키 저장소에 PKCS # 11 랩퍼를 작성하면 이미 트릭을 수행 할 수 있습니까?
엠 보스

51

Jon Skeet이 말했듯이 반사를 사용하는 것 외에는 방법이 없습니다.

그러나 리플렉션이 적합한 경우이 작업을 수행 할 수 있습니다.

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);
    }
}

실행될 때

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

참고 : 문자열의 char []가 GC주기의 일부로 복사 된 경우 이전 사본이 메모리에있을 수 있습니다.

이 오래된 사본은 힙 덤프에 나타나지 않지만 프로세스의 원시 메모리에 직접 액세스 할 수있는 경우이를 볼 수 있습니다. 일반적으로 그러한 액세스 권한을 가진 사람은 피해야합니다.


2
우리가 얻는 암호 길이를 인쇄하지 못하게하는 것도 '***********'좋습니다.
chux-복원 Monica Monica

@chux는 너비가 0 인 문자를 사용할 수 있지만 유용한 것보다 더 혼란 스러울 수 있습니다. Unsafe를 사용하지 않고 char 배열의 길이를 변경할 수 없습니다. ;)
Peter Lawrey

5
Java 8의 String Deduplication 때문에 이와 같은 작업을 수행하는 것은 상당히 파괴적이라고 생각합니다. 프로그램에서 다른 문자열을 사용하여 암호 String의 값이 동일하게 지워질 수 있습니다. 가능하지는 않지만 ...
jamp

1
@PeterLawrey JVM 인수로 활성화해야하지만 거기에 있습니다. 여기에서 읽을 수 있습니다 : blog.codecentric.de/en/2014/08/…
jamp

1
암호가 여전히 Scanner내부 버퍼에 있고 사용하지 않았기 때문에 System.console().readPassword()콘솔 창에서 읽을 수있는 형식으로되어 있을 가능성이 높습니다 . 그러나 대부분의 실제 사용 사례에서 usePassword의 실행 기간은 실제 문제입니다. 예를 들어, 다른 컴퓨터에 연결할 때 시간이 오래 걸리고 공격자에게 힙에서 암호를 검색하기에 적합한 시간이라고 알려줍니다. 유일한 해결책은 공격자가 힙 메모리를 읽지 못하게하는 것입니다.
Holger

42

이러한 모든 이유 때문에 암호 대신 문자열 대신 char [] 배열을 선택해야합니다 .

1. 문자열은 Java에서 변경할 수 없으므로 암호를 일반 텍스트로 저장하면 가비지 콜렉터가 지울 때까지 메모리에서 사용할 수 있으며 재사용을 위해 문자열이 문자열 풀에서 사용되므로 문자열은 재사용 가능성이 매우 높습니다. 보안 상 위협이되는 메모리에 오랫동안 남아 있습니다.

메모리 덤프에 액세스 할 수있는 사람은 누구나 일반 텍스트로 비밀번호를 찾을 수 있기 때문에 항상 일반 텍스트 대신 암호화 된 비밀번호를 사용해야합니다. 문자열은 변경할 수 없으므로 변경하면 새 문자열이 생성되므로 문자열의 내용을 변경할 수있는 방법은 없지만 char []를 사용하면 모든 요소를 ​​공백 또는 0으로 설정할 수 있습니다. 따라서 문자 배열에 암호를 저장하면 암호를 훔칠 보안 위험이 분명히 완화됩니다.

2. Java 자체는 보안상의 이유로 명확한 텍스트로 비밀번호를 리턴하는 사용되지 않는 getText () 메소드 대신 char []를 리턴하는 JPasswordField의 getPassword () 메소드 사용을 권장합니다. Java 팀의 조언을 따르고 표준에 반하는 것이 아니라 표준을 따르는 것이 좋습니다.

3. 문자열을 사용하면 항상 로그 파일이나 콘솔에 일반 텍스트를 인쇄 할 위험이 있지만 배열을 사용하면 배열의 내용을 인쇄하지 않고 대신 메모리 위치가 인쇄됩니다. 실제 이유는 아니지만 여전히 의미가 있습니다.

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

이 블로그 에서 참조 하십시오 . 이게 도움이 되길 바란다.


10
중복입니다. 이 답변은 @SrujanKumarGulla stackoverflow.com/a/14060804/1793718에 의해 작성된 정확한 답변 버전입니다 . 붙여 넣기를 복사하거나 동일한 답변을 두 번 복제하지 마십시오.
Lucky

1의 차이점은 무엇입니까? System.out.println ( "문자 암호 :"+ charPassword); 및 2.) System.out.println (charPassword); 출력과 동일한 "알 수 없음"을 제공하기 때문입니다.
Vaibhav_Sharma

3
@Lucky 불행히도 귀하가 연결 한 이전 답변이이 답변과 동일한 블로그에서 표절되어 삭제되었습니다. meta.stackoverflow.com/questions/389144/…를 참조하십시오 . 이 답변은 단순히 동일한 블로그에서 잘라내어 붙여 넣었으며 아무것도 추가하지 않았으므로 원래 소스로 연결되는 주석 일뿐입니다.
skomisa

41

편집 : 1 년의 보안 연구 후이 답변으로 돌아와서 실제로 평문 암호를 비교할 때 불행한 결과를 초래한다는 것을 알았습니다. 제발 하지마 소금과 합리적인 반복 횟수로 안전한 단방향 해시를 사용하십시오 . 라이브러리 사용을 고려하십시오.이 물건은 제대로 얻기가 어렵습니다!

원래 답변 : String.equals ()가 단락 평가 를 사용하여 타이밍 공격에 취약 하다는 사실은 어떻 습니까? 가능하지 않을 수도 있지만 이론적 으로 올바른 문자 순서를 결정하기 위해 암호 비교 시간을 정할 수 있습니다.

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;
}

타이밍 공격에 대한 추가 자료 :


그러나 이것은 char [] 비교에서도 가능합니다. 어딘가에서 암호 유효성 검사에서도 동일하게 수행됩니다. 그렇다면 char []는 어떻게 string보다 낫습니까?
Mohit Kanwar

3
당신은 절대적으로 맞습니다, 실수는 어느 쪽이든 할 수 있습니다. Java에 String 기반 또는 char [] 기반 암호에 대한 명시적인 암호 비교 방법이 없다는 점을 고려하면이 문제에 대해 아는 것이 가장 중요합니다. Strings에 compare ()를 사용하려는 유혹이 char []와 함께하는 좋은 이유라고 말하고 싶습니다. 그렇게하면 최소한 비교 방법을 제어 할 수 있습니다 (스트링을 확장하지 않고도 고통을 겪습니다).
그래프 이론

일반 텍스트 암호를 비교하는 것은 어쨌든 옳은 일 아니다 외에도, 사용하는 유혹 Arrays.equals을 위해은 char[]을위한 높은 같다 String.equals. 사람이 걱정하는 경우, 전용 키 클래스 실제 암호를 캡슐화하고 돌보는 문제 - 오 실제 보안 패키지는, 잠깐 있었다 전용 키 클래스를,이 Q & A 단지에 관한 습관 등 그 밖에, 말, JPasswordField사용, char[]대신 String(실제 알고리즘은 byte[]어쨌든 사용 합니다).
Holger

보안 관련 소프트웨어는 sleep(secureRandom.nextInt())어쨌든 로그인 시도를 거부하기 전에 타이밍 공격의 가능성을 제거 할뿐만 아니라 무차별 대입 공격을 방지하는 것과 같은 작업을 수행해야 합니다.
Holger

36

char 배열은 사용 후 수동으로 정리하지 않는 한 vs 문자열을 제공하지 않습니다. 실제로 그렇게하는 사람을 보지 못했습니다. 그래서 char [] vs String의 선호도는 약간 과장되었습니다.

여기 에서 널리 사용되는 Spring Security 라이브러리를 살펴보십시오. 스스로에게 물어보십시오. Spring Security 사용자가 무능하거나 char [] 비밀번호가 그다지 의미가 없습니다. 불쾌한 해커가 RAM의 메모리 덤프를 가져 오면 정교한 암호를 숨기는 방법을 사용하더라도 모든 암호를 얻을 수 있어야합니다.

그러나 Java는 항상 변경되며 Java 8의 String Deduplication 기능 과 같은 일부 무서운 기능은 사용자 모르게 String 객체를 인턴 할 수 있습니다. 그러나 그것은 다른 대화입니다.


문자열 중복 제거가 왜 무서운가요? 내용이 같은 문자열이 두 개 이상인 경우에만 적용되므로 이미 동일한 두 문자열이 동일한 배열을 공유하게하면 어떤 위험이 발생합니까? 또는 다른 방법으로 물어보십시오. 문자열 중복 제거가없는 경우 두 문자열에 동일한 내용의 고유 한 배열이 있다는 사실에서 어떤 이점이 있습니까? 어느 경우 든, 그 내용의 가장 긴 현 문자열이 살아있는 한 그 내용의 배열이 적어도있을 것입니다…
Holger

@Holger 통제 할 수없는 것은 잠재적 인 위험입니다. 예를 들어 두 명의 사용자가 동일한 비밀번호를 사용하는 경우이 멋진 기능을 사용하면 두 문자를 모두 단일 문자로 저장합니다 []. 큰 위험이지만 여전히
Oleg Mikheev

힙 메모리와 두 문자열 인스턴스 모두에 액세스 할 수있는 경우 문자열이 동일한 배열을 가리키는 지 또는 동일한 내용의 두 배열을 가리키는지는 중요하지 않습니다. 특히 어쨌든 관련이 없습니다. 이 시점에 있다면, 동일한 암호에 상관없이 두 암호를 모두 얻을 수 있습니다. 실제 오류는 솔트 해시 대신 일반 텍스트 비밀번호를 사용하는 데 있습니다.
Holger

@Holger 암호를 확인하려면 소금에 절인 해시를 만들더라도 10ms 동안 메모리에 일반 텍스트로 있어야합니다. 그런 다음 10ms 동안 두 개의 동일한 암호가 메모리에 유지되면 중복 제거 기능이 작동 할 수 있습니다. 실제로 문자열을 인턴하면 훨씬 더 오랫동안 메모리에 보관됩니다. 몇 달 동안 다시 시작하지 않는 시스템은이 중 많은 것을 수집합니다. 그냥 이론.
Oleg Mikheev 2016 년

String Deduplication에 대한 근본적인 오해가있는 것 같습니다. 그것은하지 "인턴 문자열", 그것은하지 모든 일은 실제로 같은 배열을 가리 같은 내용 문자열을시키는됩니다 감소 하나 개를 제외한 모든 배열 인스턴스가 재생 및 덮어 쓰기 수 있듯이, 일반 텍스트 암호를 포함하는 배열 인스턴스의 수를 다른 물체에 의해 즉시. 이 문자열은 여전히 ​​다른 문자열처럼 수집됩니다. 중복 제거가 실제로 가비지 수집기에서 수행된다는 것을 이해하면 여러 GC주기에서만 살아남은 문자열에 도움이 될 수 있습니다.
Holger

30

문자열은 변경할 수 없으며 일단 생성 된 후에는 변경할 수 없습니다. 문자열을 암호로 만들면 힙 또는 문자열 풀의 암호에 대한 참조를 흩뜨립니다. 이제 누군가 Java 프로세스의 힙 덤프를 가져 와서 신중하게 스캔하면 암호를 추측 할 수 있습니다. 물론 이러한 사용되지 않는 문자열은 가비지 수집되지만 GC가 시작되는 시점에 따라 다릅니다.

다른 한편으로, 인증이 완료 되 자마자 char []는 변경 가능합니다. 모든 M 또는 백 슬래시와 같은 문자로 덮어 쓸 수 있습니다. 누군가 누군가 힙 덤프를하더라도 현재 사용하지 않는 비밀번호를 얻지 못할 수 있습니다. 이를 통해 Object 컨텐츠를 직접 지우는 것과 GC가 기다리는 것을보다 강력하게 제어 할 수 있습니다.


문제의 JVM이 1.6보다 큰 경우에만 GC됩니다. 1.7 이전에는 모든 스트링이 permgen에 저장되었습니다.
avgvstvs

@avgvstvs :“모든 문자열은 permgen에 저장되었습니다”는 잘못되었습니다. 내부 문자열 만 저장되었으며 코드에서 참조하는 문자열 리터럴에서 생성되지 않은 경우 여전히 가비지 수집되었습니다. 생각 해봐. 1.7 이전의 JVM에서 문자열이 일반적으로 GC되지 않은 경우 Java 애플리케이션이 몇 분 이상 어떻게 생존 할 수 있습니까?
Holger

@Holger 이것은 거짓입니다. 내부 stringsAND String풀 (이전에 사용 된 문자열 풀)은 1.7 이전에 Permgen에 저장되었습니다. 또한 섹션 5.1 : docs.oracle.com/javase/specs/jvms/se6/html/…Strings 을 참조하십시오 . JVM은 항상 동일한 참조 값인지 확인 하여 호출해야 String.intern()합니다. 결과적으로 JVM이 constant_pool또는 힙 에서 동일한 문자열을 감지 할 때마다 문자열 을 permgen으로 옮겼습니다. 그리고 1.7까지 "크림 퍼머 겐"을 사용하여 여러 응용 프로그램을 작업했습니다. 진짜 문제였습니다.
avgvstvs

요약하자면, 1.7까지 문자열은 힙에서 시작하여 사용되었을 때 constant_poolIN permgen에있는 문자열에 넣은 다음 문자열을 두 번 이상 사용하면 인터 닝됩니다.
avgvstvs

@avgvstvs :“이전에 사용 된 문자열의 풀”이 없습니다. 당신은 완전히 다른 것들을 함께 던지고 있습니다. 문자열 리터럴과 명시 적으로 삽입 된 문자열을 포함하는 하나의 런타임 문자열 풀이 있지만 다른 것은 없습니다. 그리고 각 클래스에는 컴파일 타임 상수를 포함하는 상수 풀이 있습니다 . 이 문자열은 런타임 풀에 자동으로 추가되지만 모든 문자열 아닌이 문자열 에만 추가됩니다 .
Holger

21

문자열은 변경할 수 없으며 문자열 풀로 이동합니다. 일단 작성된 후에는 덮어 쓸 수 없습니다.

char[] 암호를 사용한 후에 덮어 써야하는 배열이며 다음과 같이 수행해야합니다.

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');
}

공격자가이를 사용할 수있는 한 가지 시나리오는 JVM이 충돌하고 메모리 덤프를 생성 할 때 충돌 덤프입니다. 암호를 볼 수 있습니다.

반드시 악의적 인 외부 공격자는 아닙니다. 모니터링 목적으로 서버에 액세스 할 수있는 지원 사용자 일 수 있습니다. 그는 충돌 덤프를 들여다보고 암호를 찾을 수 있습니다.


2
ch = null; 당신은 이것을 할 수 없습니다
Yugerten

그러나 request.getPassword()아직 문자열을 만들어 풀에 추가하지 않습니까?
Tvde1

1
ch = '0'지역 변수를 변경합니다 ch. 배열에는 영향을 미치지 않습니다. 어쨌든 예제는 toCharArray()의미가 없으며 호출하는 문자열 인스턴스로 시작 하여 새 배열을 만들고 새 배열을 올바르게 덮어 쓰더라도 문자열 인스턴스를 변경하지 않으므로 문자열을 사용하는 것보다 이점이 없습니다. 예.
Holger

@Holger 감사합니다. 문자 배열 정리 코드를 수정했습니다.
ACV

3
당신은 간단하게 사용할 수 있습니다Arrays.fill(pass, '0');
Holger

18

짧고 간단한 대답은 객체가 아닌 char[]동안 변경할 수 있기 때문 입니다 String.

Strings자바에서는 불변의 객체입니다. 그렇기 때문에 파일을 만든 후에는 수정할 수 없으므로 메모리에서 내용을 제거하는 유일한 방법은 가비지 수집을 수행하는 것입니다. 그러면 개체가 사용 가능한 메모리를 덮어 쓸 수 있고 데이터가 사라질 때만됩니다.

이제 Java의 가비지 콜렉션은 보장 된 간격으로 발생하지 않습니다. 는 String이렇게 오랜 시간 동안 메모리에 유지 수 있으며, 프로세스가이 시간 동안 충돌하는 경우, 문자열의 내용이 메모리 덤프 또는 일부 로그에 끝낼 수 있습니다.

문자형 배열을 사용하면 암호를 읽고 최대한 빨리 작업을 마친 후 즉시 내용을 변경할 수 있습니다.


@fallenidol 전혀 아닙니다. 주의 깊게 읽으면 차이점이 있습니다.
Pritam Banerjee

12

Java의 문자열은 변경할 수 없습니다. 따라서 문자열이 생성 될 때마다 가비지 수집 될 때까지 메모리에 남아 있습니다. 따라서 메모리에 액세스 할 수있는 사람은 누구나 문자열 값을 읽을 수 있습니다.
문자열 값이 수정되면 새 문자열이 만들어집니다. 따라서 원래 값과 수정 된 값은 모두 가비지 수집 될 때까지 메모리에 유지됩니다.

문자 배열을 사용하면 암호의 목적이 제공되면 배열의 내용을 수정하거나 지울 수 있습니다. 배열의 원래 내용은 수정 된 후 및 가비지 수집이 시작되기 전에도 메모리에서 찾을 수 없습니다.

보안 문제 때문에 암호를 문자 배열로 저장하는 것이 좋습니다.


2

이 목적을 위해 String을 사용해야하는지 또는 Char []를 사용해야하는지에 대해서는 논쟁의 여지가 있습니다. 사용자가 원하는 것에 달려 있습니다.

Java의 문자열은 변경할 수 없으므로 일부 문자열을 조작하려고 할 때마다 새 객체가 생성되고 기존 문자열은 영향을받지 않습니다. 이것은 암호를 문자열로 저장하는 이점으로 보일 수 있지만 사용 후에도 개체는 메모리에 남아 있습니다. 따라서 누군가 어떻게 든 객체의 메모리 위치를 확보 한 경우 해당 위치에 저장된 비밀번호를 쉽게 추적 할 수 있습니다.

Char []는 변경 가능하지만, 사용 후 프로그래머가 명시 적으로 배열을 정리하거나 값을 대체 할 수 있다는 장점이 있습니다. 따라서 사용이 완료되면 정리되어 저장된 정보에 대해 아무도 알 수 없습니다.

위의 상황에 따라 요구 사항에 대해 String과 함께 갈 것인지 Char []와 함께 갈 것인지에 대한 아이디어를 얻을 수 있습니다.


0

케이스 문자열 :

    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

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
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.