Translate

2018년 11월 18일 일요일

[Java] JDK의 암호화 알고리즘 키 제한 이슈 (Illegal key size 오류)





Java에서 (특히 256비트 이상)암호화 처리를 할 시 java.security.InvalidKeyException: Illegal key size 예외가 발생하는 경우가 있는데..


Java는 아래의 버전 이상에서만 unlimited 암호화를 지원한다.
- JDK 6u181 (개인은 6u45 버전까지 다운로드 가능)
- JDK 7u171 (개인은 7u80 버전까지 다운로드 가능)
- JDK 8u161
1004lucifer
주의
 - JDK 6u45 리눅스 버전은 unlimited 로 설정이 되어있음.
 - 따라서 JDK 6u45 사용 시 리눅스와 윈도우환경의 결과가 다르다.
(삽질 한참하다가 JDK소스 디컴파일해서 까보고 디버깅모드 돌려가며 겨우 알아냄..ㅠ)


위의 Java 버전보다 낮은경우에는 아래와 같이 암호화를 지원한다.
(언급되지 않은 알고리즘은 기본적으로 128비트까지 지원)
<JAVA_HOME>/jre/lib/security/local_policy.jar!/default_local.policy
1004lucifer
grant {
    permission javax.crypto.CryptoPermission "DES", 64;
    permission javax.crypto.CryptoPermission "DESede", *;
    permission javax.crypto.CryptoPermission "RC2", 128, 
              "javax.crypto.spec.RC2ParameterSpec", 128;
    permission javax.crypto.CryptoPermission "RC4", 128;
    permission javax.crypto.CryptoPermission "RC5", 128, 
          "javax.crypto.spec.RC5ParameterSpec", *, 12, *;
    permission javax.crypto.CryptoPermission "RSA", *;
    permission javax.crypto.CryptoPermission *, 128;
};




또한 SSL/TLS 프로토콜 이용시 사용하는 Cipher Suites 에서도
ECDSA, ECDH, ECDHE, ECDH_anon
위의 키교환방식에 대해서는 Java가 limited 상황에서는 사용할 수 없다.




JDK업데이트 없이 해결방법

1. JCE(Java Cryptography Extension)를 다운
 - Java 1.2
 - Java 1.4
 - Java 5
 - Java 6, 7, 8
 - Java 9 부터는 디폴트 unlimit 정책

2. <JAVA_HOME>/jre/lib/security/ 디렉토리에

3. local_policy.jar, US_export_policy.jar 파일 덮어쓰기


PS.
JDK 8u151 이상 버전에서는 아래와 같이 두개의 디렉토리로 나뉘어져 있다.
 - <JAVA_HOME>/jre/lib/security/policy/limited
 - <JAVA_HOME>/jre/lib/security/policy/unlimited

JDK 8u151 이상에서는 JCE 다운로드 없이
<JAVA_HOME>/jre/lib/security/java.security 파일에
crypto.policy=unlimited 옵션 지정 시 바로 사용가능하다.






아래의 JDK의 JCE 소스 발췌를 통해 JDK 8u161 부터 unlimited 가 디폴트로 지정된것을 알 수 있다.

JDK 8u151 - jce.jar!/javax.crypto/JceSecurity.class
private static void setupJurisdictionPolicies()
  throws Exception
{
 String str1 = System.getProperty("java.home");
 String str2 = Security.getProperty("crypto.policy");

 Path localPath = str2 == null ? null : Paths.get(str2, new String[0]);
 if ((localPath != null) && ((localPath.getNameCount() != 1) ||
   (localPath.compareTo(localPath.getFileName()) != 0))) {
  throw new SecurityException("Invalid policy directory name format: " + str2);
 }
 if (localPath == null) {
  localPath = Paths.get(str1, new String[] { "lib", "security" });
 } else {
  localPath = Paths.get(str1, new String[] { "lib", "security", "policy", str2 });
 }
 if (debug != null) {
  debug.println("crypto policy directory: " + localPath);
 }
 File localFile1 = new File(localPath.toFile(), "US_export_policy.jar");
 File localFile2 = new File(localPath.toFile(), "local_policy.jar");
 if ((str2 == null) && ((!localFile1.exists()) ||
   (!localFile2.exists())))
 {
  localPath = Paths.get(str1, new String[] { "lib", "security", "policy", "limited" });

  localFile1 = new File(localPath.toFile(), "US_export_policy.jar");
  localFile2 = new File(localPath.toFile(), "local_policy.jar");
 }
 URL localURL = ClassLoader.getSystemResource("javax/crypto/Cipher.class");
 if ((localURL == null) ||
   (!localFile1.exists()) || (!localFile2.exists())) {
  throw new SecurityException("Cannot locate policy or framework files!");
 }
 CryptoPermissions localCryptoPermissions1 = new CryptoPermissions();
 CryptoPermissions localCryptoPermissions2 = new CryptoPermissions();
 loadPolicies(localFile1, localCryptoPermissions1, localCryptoPermissions2);

 CryptoPermissions localCryptoPermissions3 = new CryptoPermissions();
 CryptoPermissions localCryptoPermissions4 = new CryptoPermissions();
 loadPolicies(localFile2, localCryptoPermissions3, localCryptoPermissions4);
 if ((localCryptoPermissions1.isEmpty()) || (localCryptoPermissions3.isEmpty())) {
  throw new SecurityException("Missing mandatory jurisdiction policy files");
 }
 defaultPolicy = localCryptoPermissions1.getMinimum(localCryptoPermissions3);
 if (localCryptoPermissions2.isEmpty()) {
  exemptPolicy = localCryptoPermissions4.isEmpty() ? null : localCryptoPermissions4;
 } else {
  exemptPolicy = localCryptoPermissions2.getMinimum(localCryptoPermissions4);
 }
}


JDK 8u161 - jce.jar!/javax.crypto/JceSecurity.class
private static void setupJurisdictionPolicies()
  throws Exception
{
 String str1 = System.getProperty("java.home");
 String str2 = Security.getProperty("crypto.policy");

 Path localPath = str2 == null ? null : Paths.get(str2, new String[0]);
 if ((localPath != null) && ((localPath.getNameCount() != 1) ||
   (localPath.compareTo(localPath.getFileName()) != 0))) {
  throw new SecurityException("Invalid policy directory name format: " + str2);
 }
 if (localPath == null) {
  localPath = Paths.get(str1, new String[] { "lib", "security" });
 } else {
  localPath = Paths.get(str1, new String[] { "lib", "security", "policy", str2 });
 }
 if (debug != null) {
  debug.println("crypto policy directory: " + localPath);
 }
 File localFile1 = new File(localPath.toFile(), "US_export_policy.jar");
 File localFile2 = new File(localPath.toFile(), "local_policy.jar");
 if ((str2 == null) && ((!localFile1.exists()) ||
   (!localFile2.exists())))
 {
  localPath = Paths.get(str1, new String[] { "lib", "security", "policy", "unlimited" });

  localFile1 = new File(localPath.toFile(), "US_export_policy.jar");
  localFile2 = new File(localPath.toFile(), "local_policy.jar");
 }
 URL localURL = ClassLoader.getSystemResource("javax/crypto/Cipher.class");
 if ((localURL == null) ||
   (!localFile1.exists()) || (!localFile2.exists())) {
  throw new SecurityException("Cannot locate policy or framework files!");
 }
 CryptoPermissions localCryptoPermissions1 = new CryptoPermissions();
 CryptoPermissions localCryptoPermissions2 = new CryptoPermissions();
 loadPolicies(localFile1, localCryptoPermissions1, localCryptoPermissions2);

 CryptoPermissions localCryptoPermissions3 = new CryptoPermissions();
 CryptoPermissions localCryptoPermissions4 = new CryptoPermissions();
 loadPolicies(localFile2, localCryptoPermissions3, localCryptoPermissions4);
 if ((localCryptoPermissions1.isEmpty()) || (localCryptoPermissions3.isEmpty())) {
  throw new SecurityException("Missing mandatory jurisdiction policy files");
 }
 defaultPolicy = localCryptoPermissions1.getMinimum(localCryptoPermissions3);
 if (localCryptoPermissions2.isEmpty()) {
  exemptPolicy = localCryptoPermissions4.isEmpty() ? null : localCryptoPermissions4;
 } else {
  exemptPolicy = localCryptoPermissions2.getMinimum(localCryptoPermissions4);
 }
}





아래링크의 AES256 암호화 자바 샘플을 통해 디버깅모드로 실행하게되면 jce.jar 내부적으로 돌아가는 모습을 볼 수 있다.
 - https://dukeom.wordpress.com/2013/01/08/aes256-%EC%95%94%ED%98%B8%ED%99%94-java-%EC%83%98%ED%94%8C/
(JDK 6은 클래스,변수명 난독화가 되어있어 디버깅모드로 한줄한줄 제대로 볼 수 없었음)





참고
 - https://docs.oracle.com/javase/6/docs/technotes/guides/security/crypto/CryptoSpec.html#AppC
 - https://hongsii.github.io/2018/04/05/aes256-java-security-invalidkeyexception-illegal-key-size-%ED%95%B4%EA%B2%B0-%EB%B0%A9%EB%B2%95/
 - https://dukeom.wordpress.com/2013/01/08/aes256-%EC%95%94%ED%98%B8%ED%99%94-java-%EC%83%98%ED%94%8C/
 - https://rsec.kr/?p=455

댓글 2개 :

  1. 안녕하세요. 문의사항이 있어 댓글을 남기게 되었습니다. 이 글에서 말하는 암호화 알고리즘이 서버 암호화 알고리즘을 말 하는건지 아니면 jdk 자체 알고리즘인지 궁금합니다. 서버 암호화 알고리즘을 변경(linux) 하려고 하거든요... 영향도 파악이 잘 안되서 혹시나....문의 드립니다.

    답글삭제
    답글
    1. 이 글에서 설명하는 암호화 알고리즘은 톰캣<=>브라우저 간에 SSL/TLS 통신할때 사용하는 알고리즘 이예요.
      Linux 서버 암호화 알고리즘과는 상관이 없습니다.

      삭제