답변:
문자열은 변경할 수 없습니다 . 당신이 만들어지면 그 수단 String
다른 프로세스가 메모리를 덤프 할 수있는 경우는, (옆에서있는 방법은 없습니다 반사 이전에 데이터를 제거 할 수) 가비지 컬렉션 에서 차기.
배열을 사용하면 데이터를 완성한 후에 데이터를 명시 적으로 지울 수 있습니다. 배열을 원하는 것으로 덮어 쓸 수 있으며 가비지 수집 전에도 시스템의 어느 곳에도 암호가 표시되지 않습니다.
그래서 그래,이 인 보안 문제 -하지만도 사용은 char[]
공격자 만 기회의 창을 줄이고, 그것은 단지 공격이 특정 유형입니다.
주석에서 언급했듯이 가비지 수집기에 의해 이동되는 배열은 메모리에 부유 한 데이터 복사본을 남길 수 있습니다. 나는 이것이 구현에 특정 적이 라고 믿습니다. 가비지 수집기 는 이런 종류의 것을 피하기 위해 모든 메모리를 지울 수 있습니다 . 그렇더라도 char[]
실제 문자를 공격 창으로 포함하는 시간은 여전히 남아 있습니다.
char[]
위치에 저장된 암호를 지우면 해당 공격 라인이 차단됩니다 String
.
여기에 다른 제안이 유효 해 보이지만 다른 좋은 이유가 있습니다. 평범하게 실수로 로그 , 모니터 또는 다른 안전하지 않은 장소에 암호를 인쇄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
toString
입니다 classname@hashcode
. [C
은 char[]
나머지는 16 진 해시 코드입니다.
Password
클래스 유형을 작성합니다. 덜 애매하고 실수로 어딘가로 통과하기가 더 어렵습니다.
공식 문서를 인용하기 위해 Java Cryptography Architecture 안내서 는 암호 char[]
와 String
암호에 대해 설명합니다 (암호 기반 암호화에 관한 것이지만 암호에 대해서는 더 일반적입니다).
유형의 객체에 암호를 수집하고 저장하는 것이 논리적으로 보입니다
java.lang.String
. 그러나주의해야 할 점은 다음과 같습니다.Object
s 유형String
은 변경할 수 없습니다. 즉,String
사용 후 내용을 변경 (덮어 쓰기)하거나 제로화 할 수있는 정의 된 메소드가 없습니다 . 이 기능은String
개체가 사용자 암호와 같은 보안에 민감한 정보를 저장하기에 적합하지 않게합니다.char
대신 보안에 민감한 정보를 항상 배열로 수집하고 저장해야 합니다.
Java 프로그래밍 언어 용 보안 코딩 지침, 버전 4.0의 지침 2-2 에도 비슷한 내용이 나와 있습니다 (원래 로깅 컨텍스트에 있음).
지침 2-2 : 매우 민감한 정보를 기록하지 마십시오
사회 보장 번호 (SSN) 및 비밀번호와 같은 일부 정보는 매우 중요합니다. 이 정보는 필요 이상으로 보거나 관리자가 볼 수있는 위치에 보관해서는 안됩니다. 예를 들어, 로그 파일로 보내지 않아야하며 검색을 통해 존재 여부를 감지 할 수 없습니다. 일부 일시적인 데이터는 char 배열과 같은 가변 데이터 구조로 유지 될 수 있으며 사용 후 즉시 지워질 수 있습니다. 데이터 구조를 지우면 객체가 메모리에서 프로그래머에게 투명하게 이동함에 따라 일반적인 Java 런타임 시스템의 효율성이 떨어집니다.
이 지침은 또한 다루고있는 데이터에 대한 의미 지식이없는 하위 레벨 라이브러리의 구현 및 사용에 영향을 미칩니다. 예를 들어, 하위 수준 문자열 구문 분석 라이브러리는 작동하는 텍스트를 기록 할 수 있습니다. 애플리케이션은 라이브러리와 SSN을 구문 분석 할 수 있습니다. 이로 인해 로그 파일에 액세스 할 수있는 관리자가 SSN을 사용할 수있는 상황이 발생합니다.
String
JVM에서 어떻게 작동하는지 잘 이해하고 있습니다 ... 암호를 안전한 방식으로 처리 할 때 char[]
대신 사용해야하는 이유가 있습니다 String
.
At which point
응용 프로그램에 따라 다르지만 일반적인 규칙은 암호 (일반 텍스트 또는 기타)로 간주되는 항목을 가져 오면 즉시 수행하는 것입니다. 예를 들어 HTTP 요청의 일부로 브라우저에서 가져옵니다. 전달을 제어 할 수는 없지만 자신의 스토리지를 제어 할 수 있으므로 즉시 저장하여 char []에 넣고 필요한 작업을 수행 한 다음 모두 '0'으로 설정하고 gc를 허용하십시오. 그것을 되 찾아라.
문자 배열 ( char[]
)은 사용 후 각 문자를 0으로 설정하고 문자열을 설정하지 않음으로써 지울 수 있습니다. 누군가가 어떻게 메모리 이미지를 볼 수 있다면, 문자열을 사용하면 일반 텍스트로 암호를 볼 수 있지만, 사용하면 char[]
0으로 데이터를 제거한 후 암호는 안전합니다.
HttpServletRequest
일반 텍스트 로 객체에 전달합니다 . JVM 버전이 1.6 이하이면 permgen 공간에 있습니다. 1.7에 있으면 수집 될 때까지 계속 읽을 수 있습니다. (언제나
intern()
임의로 문자열을 호출하지 않아야하는 이유 입니다. 그러나 String
인스턴스가 처음에 (수집 될 때까지) 존재하고 char[]
나중에 배열로 바꾸어도 변경되지 않는다는 것이 맞습니다 .
new String()
또는 StringBuilder.toString()
나는 문자열 상수의 많은 애플리케이션을 관리, 우리는 결과로 permgen 크리프을 많이했다. 1.7까지.
intern()
에 임의의 문자열을 호출 하면 permgen 공간에 동등한 문자열이 할당 될 수 있습니다. 같은 객체의 문자 그대로 그 객체를 공유하는 문자열이 없다면 후자는 GC를받을 수있다.
어떤 사람들은 더 이상 필요하지 않은 암호를 저장하는 데 사용 된 메모리를 덮어 써야한다고 생각합니다. 이를 통해 공격자가 시스템에서 암호를 읽는 데 걸리는 시간이 단축되고 공격자가 이미이를 수행하기 위해 JVM 메모리를 하이재킹 할 수있는 충분한 액세스가 필요하다는 사실을 완전히 무시합니다. 많은 액세스 권한을 가진 공격자는 주요 이벤트를 포착하여이를 완전히 쓸모 없게 만들 수 있습니다 (AFAIK, 내가 틀렸다면 바로 정정하십시오).
최신 정보
의견 덕분에 답변을 업데이트해야합니다. 암호가 하드 드라이브에 도달 할 수있는 시간을 줄임으로써 약간의 보안 향상을 추가 할 수있는 두 가지 경우가 있습니다. 여전히 대부분의 사용 사례에서는 과잉이라고 생각합니다.
가능하면 코어 덤프와 스왑 파일을 비활성화하면 두 가지 문제가 모두 해결됩니다. 그러나 관리자 권한이 필요하고 기능 (사용할 메모리가 적음)이 줄어들고 실행중인 시스템에서 RAM을 가져 오는 것이 여전히 유효한 문제입니다.
나는 이것이 유효한 제안이라고 생각하지 않지만 적어도 그 이유를 추측 할 수 있습니다.
동기 부여는 메모리에서 암호의 모든 흔적을 즉시 사용하고 확실하게 지울 수 있기를 원한다고 생각합니다. 를 사용 char[]
하면 배열의 각 요소를 빈 칸으로 덮어 쓸 수 있습니다. String
그런 식으로 내부 값을 편집 할 수 없습니다 .
그러나 이것만으로는 좋은 대답이 아닙니다. 왜 char[]
또는 String
탈출하지 않는지 확인 하지 않습니까? 그런 다음 보안 문제가 없습니다. 그러나 문제는 이론적으로 String
객체를 intern()
편집하고 일정한 수영장 안에 살 수 있다는 것입니다 . 나는 char[]
이것을 사용하는 것이이 가능성 을 금지 한다고 생각 합니다.
char[]
수정 될 수 있으며 수집 여부와 관계가 없습니다. 리터럴이 아닌 경우 문자열 인터 닝을 명시 적으로 수행해야 char[]
하므로 정적 필드에서 a를 참조 할 수 있다는 것을 말하는 것과 같습니다 .
답변이 이미 제공되었지만 최근에 Java 표준 라이브러리에서 발견 한 문제를 공유하고 싶습니다. 암호 문자열을 char[]
모든 곳 (물론 좋은 것) 으로 바꾸는 데 많은주의를 기울이는 반면 , 메모리에서 지우려면 보안에 중요한 다른 데이터를 간과하는 것 같습니다.
예를 들어 PrivateKey 클래스를 생각하고 있습니다. PKCS # 12 파일에서 개인 RSA 키를 사용하여 일부 작업을 수행하는 시나리오를 고려하십시오. 이제이 경우, 비밀번호 만 스니핑해도 키 파일에 대한 물리적 액세스가 제대로 제한되는 한 도움이되지 않습니다. 공격자는 암호 대신 키를 직접 입수하면 훨씬 나을 것입니다. 원하는 정보가 유출 될 수있는 것은 매니 폴드, 코어 덤프, 디버거 세션 또는 스왑 파일입니다.
결과적으로 PrivateKey
해당 정보를 형성하는 바이트를 지우는 API가 없기 때문에 메모리에서 개인 정보를 지울 수있는 것은 없습니다 .
이 백서 에서는 이러한 상황이 어떻게 악용 될 수 있는지 설명하기 때문에 이는 나쁜 상황 입니다.
예를 들어 OpenSSL 라이브러리는 개인 키가 해제되기 전에 중요한 메모리 섹션을 덮어 씁니다. Java는 가비지 수집되므로 Java 키에 대한 개인 정보를 지우고 무효화하는 명시적인 방법이 필요합니다.이 키는 사용 후 즉시 적용됩니다.
PrivateKey
은 PKCS # 11 하드웨어 토큰을 통해 메모리에 개인 콘텐츠를 실제로로드하지 않는 구현을 사용하는 것입니다 . PKCS # 11의 소프트웨어 구현으로 메모리를 수동으로 정리할 수 있습니다. 아마도 NSS 저장소와 같은 것을 사용하는 PKCS11
것이 좋습니다 (Java 의 저장소 유형 과 대부분의 구현을 공유 함 ). KeychainStore
(OSX 키 저장소)는 부하의에 개인 키의 전체 내용 PrivateKey
인스턴스를 있지만 필요가 없습니다. ( WINDOWS-MY
KeyStore가 Windows에서 무엇을하는지 확실 하지 않습니다.)
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주기의 일부로 복사 된 경우 이전 사본이 메모리에있을 수 있습니다.
이 오래된 사본은 힙 덤프에 나타나지 않지만 프로세스의 원시 메모리에 직접 액세스 할 수있는 경우이를 볼 수 있습니다. 일반적으로 그러한 액세스 권한을 가진 사람은 피해야합니다.
'***********'
좋습니다.
Scanner
내부 버퍼에 있고 사용하지 않았기 때문에 System.console().readPassword()
콘솔 창에서 읽을 수있는 형식으로되어 있을 가능성이 높습니다 . 그러나 대부분의 실제 사용 사례에서 usePassword
의 실행 기간은 실제 문제입니다. 예를 들어, 다른 컴퓨터에 연결할 때 시간이 오래 걸리고 공격자에게 힙에서 암호를 검색하기에 적합한 시간이라고 알려줍니다. 유일한 해결책은 공격자가 힙 메모리를 읽지 못하게하는 것입니다.
이러한 모든 이유 때문에 암호 대신 문자열 대신 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
편집 : 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;
}
타이밍 공격에 대한 추가 자료 :
Arrays.equals
을 위해은 char[]
을위한 높은 같다 String.equals
. 사람이 걱정하는 경우, 전용 키 클래스 실제 암호를 캡슐화하고 돌보는 문제 - 오 실제 보안 패키지는, 잠깐 있었다 가 전용 키 클래스를,이 Q & A 단지에 관한 습관 등 그 밖에, 말, JPasswordField
사용, char[]
대신 String
(실제 알고리즘은 byte[]
어쨌든 사용 합니다).
sleep(secureRandom.nextInt())
어쨌든 로그인 시도를 거부하기 전에 타이밍 공격의 가능성을 제거 할뿐만 아니라 무차별 대입 공격을 방지하는 것과 같은 작업을 수행해야 합니다.
char 배열은 사용 후 수동으로 정리하지 않는 한 vs 문자열을 제공하지 않습니다. 실제로 그렇게하는 사람을 보지 못했습니다. 그래서 char [] vs String의 선호도는 약간 과장되었습니다.
여기 에서 널리 사용되는 Spring Security 라이브러리를 살펴보십시오. 스스로에게 물어보십시오. Spring Security 사용자가 무능하거나 char [] 비밀번호가 그다지 의미가 없습니다. 불쾌한 해커가 RAM의 메모리 덤프를 가져 오면 정교한 암호를 숨기는 방법을 사용하더라도 모든 암호를 얻을 수 있어야합니다.
그러나 Java는 항상 변경되며 Java 8의 String Deduplication 기능 과 같은 일부 무서운 기능은 사용자 모르게 String 객체를 인턴 할 수 있습니다. 그러나 그것은 다른 대화입니다.
문자열은 변경할 수 없으며 일단 생성 된 후에는 변경할 수 없습니다. 문자열을 암호로 만들면 힙 또는 문자열 풀의 암호에 대한 참조를 흩뜨립니다. 이제 누군가 Java 프로세스의 힙 덤프를 가져 와서 신중하게 스캔하면 암호를 추측 할 수 있습니다. 물론 이러한 사용되지 않는 문자열은 가비지 수집되지만 GC가 시작되는 시점에 따라 다릅니다.
다른 한편으로, 인증이 완료 되 자마자 char []는 변경 가능합니다. 모든 M 또는 백 슬래시와 같은 문자로 덮어 쓸 수 있습니다. 누군가 누군가 힙 덤프를하더라도 현재 사용하지 않는 비밀번호를 얻지 못할 수 있습니다. 이를 통해 Object 컨텐츠를 직접 지우는 것과 GC가 기다리는 것을보다 강력하게 제어 할 수 있습니다.
strings
AND String
풀 (이전에 사용 된 문자열 풀)은 1.7 이전에 Permgen에 저장되었습니다. 또한 섹션 5.1 : docs.oracle.com/javase/specs/jvms/se6/html/…Strings
을 참조하십시오 . JVM은 항상 동일한 참조 값인지 확인 하여 호출해야 String.intern()
합니다. 결과적으로 JVM이 constant_pool
또는 힙 에서 동일한 문자열을 감지 할 때마다 문자열 을 permgen으로 옮겼습니다. 그리고 1.7까지 "크림 퍼머 겐"을 사용하여 여러 응용 프로그램을 작업했습니다. 진짜 문제였습니다.
constant_pool
IN permgen에있는 문자열에 넣은 다음 문자열을 두 번 이상 사용하면 인터 닝됩니다.
문자열은 변경할 수 없으며 문자열 풀로 이동합니다. 일단 작성된 후에는 덮어 쓸 수 없습니다.
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이 충돌하고 메모리 덤프를 생성 할 때 충돌 덤프입니다. 암호를 볼 수 있습니다.
반드시 악의적 인 외부 공격자는 아닙니다. 모니터링 목적으로 서버에 액세스 할 수있는 지원 사용자 일 수 있습니다. 그는 충돌 덤프를 들여다보고 암호를 찾을 수 있습니다.
request.getPassword()
아직 문자열을 만들어 풀에 추가하지 않습니까?
ch = '0'
지역 변수를 변경합니다 ch
. 배열에는 영향을 미치지 않습니다. 어쨌든 예제는 toCharArray()
의미가 없으며 호출하는 문자열 인스턴스로 시작 하여 새 배열을 만들고 새 배열을 올바르게 덮어 쓰더라도 문자열 인스턴스를 변경하지 않으므로 문자열을 사용하는 것보다 이점이 없습니다. 예.
Arrays.fill(pass, '0');
짧고 간단한 대답은 객체가 아닌 char[]
동안 변경할 수 있기 때문 입니다 String
.
Strings
자바에서는 불변의 객체입니다. 그렇기 때문에 파일을 만든 후에는 수정할 수 없으므로 메모리에서 내용을 제거하는 유일한 방법은 가비지 수집을 수행하는 것입니다. 그러면 개체가 사용 가능한 메모리를 덮어 쓸 수 있고 데이터가 사라질 때만됩니다.
이제 Java의 가비지 콜렉션은 보장 된 간격으로 발생하지 않습니다. 는 String
이렇게 오랜 시간 동안 메모리에 유지 수 있으며, 프로세스가이 시간 동안 충돌하는 경우, 문자열의 내용이 메모리 덤프 또는 일부 로그에 끝낼 수 있습니다.
문자형 배열을 사용하면 암호를 읽고 최대한 빨리 작업을 마친 후 즉시 내용을 변경할 수 있습니다.
Java의 문자열은 변경할 수 없습니다. 따라서 문자열이 생성 될 때마다 가비지 수집 될 때까지 메모리에 남아 있습니다. 따라서 메모리에 액세스 할 수있는 사람은 누구나 문자열 값을 읽을 수 있습니다.
문자열 값이 수정되면 새 문자열이 만들어집니다. 따라서 원래 값과 수정 된 값은 모두 가비지 수집 될 때까지 메모리에 유지됩니다.
문자 배열을 사용하면 암호의 목적이 제공되면 배열의 내용을 수정하거나 지울 수 있습니다. 배열의 원래 내용은 수정 된 후 및 가비지 수집이 시작되기 전에도 메모리에서 찾을 수 없습니다.
보안 문제 때문에 암호를 문자 배열로 저장하는 것이 좋습니다.
이 목적을 위해 String을 사용해야하는지 또는 Char []를 사용해야하는지에 대해서는 논쟁의 여지가 있습니다. 사용자가 원하는 것에 달려 있습니다.
Java의 문자열은 변경할 수 없으므로 일부 문자열을 조작하려고 할 때마다 새 객체가 생성되고 기존 문자열은 영향을받지 않습니다. 이것은 암호를 문자열로 저장하는 이점으로 보일 수 있지만 사용 후에도 개체는 메모리에 남아 있습니다. 따라서 누군가 어떻게 든 객체의 메모리 위치를 확보 한 경우 해당 위치에 저장된 비밀번호를 쉽게 추적 할 수 있습니다.
Char []는 변경 가능하지만, 사용 후 프로그래머가 명시 적으로 배열을 정리하거나 값을 대체 할 수 있다는 장점이 있습니다. 따라서 사용이 완료되면 정리되어 저장된 정보에 대해 아무도 알 수 없습니다.
위의 상황에 따라 요구 사항에 대해 String과 함께 갈 것인지 Char []와 함께 갈 것인지에 대한 아이디어를 얻을 수 있습니다.
케이스 문자열 :
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